Skip to content

The day my own security fix hid every image on the site

I spent a day hardening the site, shipped a Content-Security-Policy, and quietly blocked every logo and photo from loading. Here is what the audit missed.

SD
Shubham Datarkar
· 2 min read

I spent most of yesterday making the site safer, and by the end of it I had made every image disappear. Not with an error. Not with a crash. Just blank space where my logo, my face, and my whole /link page used to be.

The day started with a security audit. I went through the usual suspects -- clickjacking, MIME sniffing, referrer leaks, the SSRF guard, the contact form getting hammered. Real fixes, the kind you put off for months because nothing is on fire. I added proper security headers in next.config.ts, including a Content-Security-Policy that tells the browser exactly which origins are allowed to load things. Razorpay for checkout, Supabase for data and storage, YouTube and OpenStreetMap for the embeds. Tight, deliberate, audited.

I checked it the way you are supposed to. Headers served correctly. No CSP violations screaming in the console on the pages I opened. tsc clean, 382 tests green. I merged it and moved on to the next thing, feeling responsible.

The part I did not check

A CSP has a separate rule for images -- img-src. I had listed Supabase storage and OpenStreetMap there, because those were the origins I remembered. What I forgot is that almost none of my actual images live on Supabase. They sit on two CDNs I set up ages ago and stopped thinking about: company-assets.bookasloth.in and website-assets.shubhamdatarkar.com. Logos, the profile photo, every tile on the /link page. All served from hosts my brand-new policy did not know about.

So the browser did exactly what I told it to. It blocked them. Silently. A CSP image block does not throw a red error in your face the way a broken script does -- it just refuses the request and leaves a gap. If you do not scroll to the pages that lean on images, you never notice.

Verified served plus no CSP refusals is not the same as verified the site still works.

a note to my future self

The fix was three lines -- add both CDN hosts to img-src. The lesson was bigger. My verification checked that the header existed and that the pages I happened to open were clean. It never checked the thing that actually matters to a visitor: does an image render, is naturalWidth greater than zero. Those are different questions, and I had answered the easy one.

What I am taking from it

Now when I touch the CSP, I have one rule written down: both asset CDNs stay in img-src, always. And when I verify a change, I try to check the outcome a real person would see, not just the mechanism I changed. It is slower. It is also the whole point.

Security work has this quiet trap. The change looks correct, the tests pass, the headers are perfect -- and you have locked yourself out of your own house. Yesterday it was images. I would rather it stay that cheap.

Reactions

How was this article?

Newsletter

Never miss the next one

Get the latest playbooks, build logs, and the occasional unpublished idea straight to your inbox. One signal a week — no noise.