- Blog/
One Missing Config Flag Made a Hospital Pharmacy App's Login Page Optional
My sister runs a chemo pharmacy at a hospital and needed software to track drug stock and destruction records — the kind of accounting that has to be airtight for audit reasons. She doesn’t code, so she used AI to build a first version. I offered to take it from “works on my machine” to “safe to put in front of a hospital network,” tune it, and deploy it. That review turned up a bug worth writing about, because it’s not specific to AI-generated code — it’s a Cloudflare Workers footgun that catches experienced developers too.
The setup #
The app is a small Cloudflare Worker: static HTML/JS served from public/,
a worker.js that handles auth and the stock/destruction API, D1 as the
database. Standard shape for anything you’d deploy cheap and fast on
Cloudflare’s free tier.
Auth looked correct on paper: a login page, session tokens in D1, HttpOnly/ Secure/SameSite cookies, rate-limited login attempts. The Worker checked for a valid session before returning any protected data. All the pieces you’d want were there.
The bug #
None of that mattered, because the protected page itself never reached the Worker.
Cloudflare Workers with Static Assets has two ways to route a request: let
the platform serve a matching static file directly from the edge, or run
your Worker first and let it decide what to serve. The default is the
first one. Unless you explicitly opt into the second, Cloudflare will serve
index.html — or any other static file — straight from cache, and your
Worker’s auth check never executes at all.
The fix is one line in wrangler.toml:
[assets]
directory = "./public"
binding = "ASSETS"
run_worker_first = true
Without run_worker_first = true, every static asset — including the page
containing the actual stock/destruction data, if it’s served as a static
file rather than through an API call — bypasses auth entirely. Anyone who
knew (or guessed) the URL got the page. Not because the auth logic was wrong.
Because the auth logic never ran.
Why this is worth knowing even if you didn’t write the code #
This isn’t an “AI wrote bad code” story. The session handling, the password hashing (PBKDF2-SHA256, 100k iterations), the rate limiting — all of that was fine. The bug lived one layer up, in platform routing config that has nothing to do with application logic and everything to do with how Workers + Static Assets decides who gets to look at a request before serving it. You can write perfect auth middleware and still ship an app where that middleware is optional.
The general lesson: when your auth lives in a Worker and your content lives in static assets on the same platform, check whether the platform’s default routing gives the Worker first look at every request — or just the ones that don’t match a static file. Test it directly: hit a protected route with no session cookie and confirm you get bounced, not served.
Two smaller fixes from the same hardening pass, worth a mention:
- D1 has a row/column size cap. The app stored full app state as one JSON blob; past a certain size, saves started failing silently at the storage layer. Fix was compressing the blob before writing. If you’re storing anything blob-shaped in D1, know the cap before you hit it in production.
Response.redirect()requires an absolute URL on Workers — a relative path throws at runtime instead of redirecting. Easy to miss in local dev if your test requests happen to already carry the right base URL.
TL;DR #
- Cloudflare Workers + Static Assets serves matching files directly by
default — your Worker (and any auth in it) doesn’t run unless you set
run_worker_first = true. - “The auth code is correct” and “the auth code always runs” are different claims. Verify the second one by testing the actual routing, not just the logic inside the Worker.
- Review AI-generated app logic for correctness, but don’t stop there — platform config is where auth silently stops mattering, and it’s not something a code read of the Worker file alone will catch.
I do web app pentests on the side — auth logic, access control, and the usual OWASP top 10 territory, including Cloudflare Workers-specific gotchas like this one. If you’re deploying something with real data behind a login and want a second pair of eyes: [email protected].