
Hardening My Personal Site: Sanitization, Cookies and JWT
It all started when I noticed an old version of my site had a known vulnerability. While closing it, instead of just patching, I designed security from the ground up. Here are the four layers I used.
1. HTML sanitization
The blog editor produces rich HTML. Saving it as-is means someone could inject <script> into the content. On the server I use Rust-based nh3 to let through only the tags I allow:
nh3.clean(dirty,
tags={"p","h2","ul","li","a","img","pre","code", ...},
url_schemes={"http","https","mailto"},
link_rel="noopener noreferrer nofollow")
SVG is intentionally off the list — since scripts can be embedded inside it, uploads reject SVG too.
2. Passwords: never plaintext
The admin password is stored in the database with bcrypt. At login the plain password is compared against the hash; the password itself is never stored anywhere.
3. httpOnly cookies and JWT
The session token lives in an encrypted cookie JavaScript can't read. I covered this in detail in the BFF post. In short: the token never touches browser code.
4. Security headers
As a final layer I added defensive headers to every response:
X-Frame-Options: DENY— against clickjacking.X-Content-Type-Options: nosniff— blocks MIME sniffing.Referrer-PolicyandPermissions-Policy— shrink the leak surface.
Security isn't a single feature; it's many small decisions stacked on top of each other. None is enough alone; together they form a solid wall.
Result
I also updated the dependencies and finished with zero vulnerabilities after a scan. Even for a personal site, building security in from the start is far more comfortable than patching it later.