Skip to content

Declare the policy the browser enforces, not only the one the build refuses #72

Description

@iderex

Depends on the external origin issue and on the cookie and storage issue.

#37 refuses a request to a domain the project does not control, and #50 refuses a
cookie, a storage write and a beacon. Both read the built output. Both are
therefore statements about the bytes this repository produced, and neither of
them is enforced anywhere a reader can benefit from, because the reader's
browser has never been told any of it.

Nothing is declared today, in either place it could be:

curl -sSI https://flowfin.dev/ | grep -i -E 'content-security-policy|referrer-policy|permissions-policy|x-frame-options'
exit=1
curl -sS https://flowfin.dev/ | grep -c -i 'Content-Security-Policy'
0

Run 2026-08-08. No header and nothing in the document.

The gap this closes is not hypothetical for a site published by a build. What
the build refuses is what the build produced. What a browser loads is whatever
answers at that address, which includes anything served alongside it that this
repository did not write, and anything a later change adds between one audit of
the output and the next. A declared policy is what turns the promise on the
privacy page from a description of one build into a rule about every load.

The static host does not let this repository set a response header, so the lever
is a policy element in the document, and it is important that the issue says what
that does not reach rather than implying a header's worth of cover. Directives
about who may frame the page are refused from a document and work only as a
header, so they are named here as not covered and go in the parity ledger as a
gap with the host rather than as a decision.

What the policy says. Content comes from this site's own origin and from nowhere
else, which is the same rule #37 checks from the other end. No script is
permitted at all, which the site can afford because #6 puts the required script
at zero bytes, and which makes an inline handler a refusal rather than a review
comment. No plugin content, no framing of other origins, and form submission
nowhere, since there is no form.

The awkward directive is the stylesheet, and it is awkward for a reason worth
writing down. The budget requires the CSS to be inlined in the document, so a
policy that forbids inline styles would forbid the site's own design. Permitting
inline styles generally would leave the policy saying almost nothing. So the
build computes the hash of what it inlined and names that hash, which permits
exactly the bytes it produced and nothing else. That is also why the policy is
generated rather than typed: the hash changes whenever the stylesheet does, and a
policy somebody has to remember to update is a policy that gets loosened the
first time it breaks a build.

Two legs, in the shape the rest of the plan uses. The greppable gate refuses a
produced page carrying no policy. The headless browser loads every page and reds
on any violation the browser reports, which is what catches a policy that is
present and wrong, since a page whose own stylesheet is refused looks broken to a
reader and green to a static check.

The privacy page cites this rather than restating it, so a claim on that page
points at the thing that refuses its violation.

The element goes in the frame in #73, so a page cannot be produced without the policy on it.

Done when

Every produced page carries the policy; the policy permits this site's origin
and no other, permits no script, and permits the inlined stylesheet by hash
rather than by allowing inline styles generally; the greppable gate refuses a
produced page without it; the headless run loads every produced page and reds on
any violation the browser reports, including a deliberately introduced reference
to another origin; a browser that cannot start reds the run; and the issue
records which directives a document cannot carry, with the parity ledger holding
that as a gap owed to the host rather than to this repository.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions