Flow
Checks for a change like mine
1Rules sent as headers
Look at a real response from the live site, not the source files.
2Short source lists
Name the hosts you load from instead of allowing any secure site.
3Three requests
Your own site works, a stranger's site fails in the browser, and no Origin still hits the login check.
4One list of sites
Keep the allowed sites in one place, or expect to edit two files each time.
Browsers won't obey a rule against being framed in a meta tag, and that's where the marketing site's rule went.
A security review of my telemetry product found several problems. Two of them: the API took requests from any site, and the web pages sent almost no safety headers. I have three parts: an API, an admin dashboard and a marketing site. I fixed all three in one go, and the first try at the marketing site was wrong.
A policy that showed up too late
The marketing site is plain files on a host. My code never touches its responses. So my first commit used a build step that wrote the safety rules into the page itself, as meta tags.
That looks like it stops other sites from framing my page. It doesn't. A browser reads only some rules from a meta tag, and "who may frame me" isn't one of them. The frame decision happens before the page is read, so the rule shows up too late. The old frame-blocking header doesn't work as a meta tag either. Probably the script rules were fine. The framing rule did nothing.
The build step even had a note saying production should set these on the server. It shipped anyway.
Moving it to the wire
The last commit deleted the build step and put the same rules in the host's header file. Now every response carries them as real headers. Images also narrowed from any secure site to the analytics hosts the page uses. A comment explains why the build step is gone.
The dashboard: widen, then narrow
The dashboard started strict. The middle commit loosened analytics to match the vendor's advice and kept risky eval scripts out of production. The last one cut images to a named list and dropped a loose allowance and a dead header. Inline scripts are still allowed, which is a weak spot.
The hole I left in the allowlist on purpose
The API used to accept every site, with cookies allowed. That's the bad combo. Now, in production, it only accepts my own sites, from a setting with defaults in code. Development stays open.
A request with no Origin header at all is let through. My command-line tool and my server jobs don't send one. They also have no browser cookies to steal. Browsers send Origin when a page calls another site. The real lock is the API key, not a cookie. And anything outside a browser can fake the header anyway.
If you build something like this, check three requests by hand:
- one from your own site, which should work
- one from a stranger's site, which should fail in the browser
- one with no Origin, which should still hit your login check
That last branch is safe only while no important route logs you in with a cookie. The branch now has a long comment explaining why, for whoever adds the next route.
Two copies of one list
Adding my blog took one commit for the default in code and another for the deploy config. If production reads the config, the code list never runs. Next time I add a site, I'll be editing two files again.