Deep Dives

How a static page can collect data without leaking it

Your Bailey site has no server and no secret key baked into it — yet it can remember and collect, safely. Here's the model that makes that work, in plain terms.

A Bailey site is just static files — HTML, CSS, a bit of JavaScript. There’s no server you manage, no database you configure, and crucially, no secret API key hidden in the page. And yet that page can remember things and collect form submissions, safely, with one creator’s data never bleeding into another’s.

People who’ve built this kind of thing before tend to raise an eyebrow at that. If there’s no secret in the page, what stops anyone from writing to your data? If it’s all static, where does the form submission even go? Fair questions. Here’s the answer.

The thing we refuse to do

The naive way to let a static page talk to a backend is to embed an API key in the JavaScript. Don’t. Anyone can open dev tools, read the key, and now they own your data plane. “Static fronts hold a secret” is the original sin of a lot of no-code tooling.

Bailey’s first rule: a static front holds no secret. Access isn’t bounded by key secrecy — because there is no key to keep secret. It’s bounded by something else.

Serve-time tokens, not baked-in keys

When Bailey serves your page, it mints a short-lived token scoped to that page, that visitor, right now. The token carries a tight set of constraints:

  • a short time-to-live, so a copied token is worthless within minutes;
  • a one-time nonce, so it can’t be replayed;
  • an Origin/CORS binding, so it only works when called from your actual site.

The page uses that token to write to the store. It was never handed the keys to the kingdom — only a narrow, expiring, single-use pass. Pair that with insert-only visibility (a form can add a submission but can’t read back what others submitted) and a rate limit, and a hostile copy of your page can do essentially nothing useful.

Isolation is enforced twice

The non-negotiable invariant is A ≠ B: project A never reads or serves project B’s files or data. We don’t trust a single layer to guarantee that, so we enforce it in two places that both have to agree:

  1. At the kernel. Files are served through an OS-level sandbox (os.Root) that physically can’t escape a project’s own directory. Path tricks don’t get you out.
  2. At the policy layer. Every read and write to the store is checked against the owner stamped on the request — an Authorize call backed by a per-project identifier. No match, no access.

Belt and suspenders. Either layer would stop a leak; both have to hold, and both are tested.

Who you are comes from the token, never the payload

Here’s a subtle one that matters a lot. When a request says “save this for project X,” we do not believe the “project X” part if it’s in the request body. The owner identity is read from the validated token the server issued — never from client-supplied data. Otherwise spoofing would be a one-line attack: just claim to be someone else. Identity is stamped server-side, full stop.

The GDPR golden chain

Last piece. A page that collects personal data has to declare why — its purpose. If a form wants to collect emails and doesn’t say what for, Bailey refuses to publish it. Not a warning, a refusal. Purposes are declared up front, reconciled every time you publish, and data buckets are never silently deleted out from under you.

The payoff for you: when a visitor asks “what do you have on me, and can you delete it?”, the answer is one click, because the system was built to answer that question from day one.


None of this is something you configure. You ask your assistant to publish a page, and all of the above is just on. That’s the whole idea: governance you don’t have to think about, because it’s the floor, not a feature.

Hosted on Bailey