Public: anyone can read this; the crew can change it.

forked from trevato/cs

The security floor stops trusting inline styles: one hashed style block, and 'unsafe-inline' leaves the CSP #129

shipped production is serving it opened this has it
opened this

Every page here carries style-src 'self' 'unsafe-inline', and it no longer has to: the one <style> block the shell emits is a constant, so its hash can be in the policy instead.

'unsafe-inline' in style-src is the last soft spot in a floor that is otherwise strict — default-src 'self', no inline script, frame-ancestors 'none', base-uri 'none'. It is there only because the shell writes its CSS into the document, which is also why the first paint has no second request. A sha256-… source keeps both: the style stays inline and the browser stops accepting any other inline style.

Done looks like

  • [ ] The shell knows the exact text it puts in the <style> block for a given page — the shared block plus that game's extraStyle — and the server computes sha256 over that text, base64, once per game at start-up.
  • [ ] The response for each page carries style-src 'self' 'sha256-<hash>' and no 'unsafe-inline' anywhere in the policy; nothing else in the floor changes.
  • [ ] A test computes the hash from the rendered page's own style block and asserts it is the one in the header, for every game in the registry — so a game that adds a rule and forgets the header fails rather than losing its styling in a browser.
  • [ ] A test asserts the string unsafe-inline appears in no response from this server, on any route, including the 404 and the 500.
  • [ ] No inline style= attribute is introduced anywhere (there are none today, and 'unsafe-hashes' is not the way out of that): if a page needs one, it gets a class.
  • [ ] The pages look exactly the same in the preview, and the first paint still makes one request for HTML and one for the bundle.

Files I expect to change

src/pages/shell.ts, src/server.ts, src/server.test.ts, src/pages/shell.test.ts.

Risks

  • The hash must be over the bytes that end up between the tags, whitespace included. Hash the same string the page interpolates, never a re-read of the file.
  • The 404 and the 500 go through the shell too once #115 lands; whichever of the two lands second makes sure both carry a correct hash, and the test that scans every route is what catches it.
  • A future game with a dynamic style block cannot be hashed at start-up. That is a good constraint to discover now, and the test says so out loud.

Suggested: Torque — it is an exact value in a header, checked against the bytes it describes.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs