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

forked from trevato/cs

The security floor gains the two headers it is missing: COOP and CORP on every response #392

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Main already sends both headers, so this pull only adds test coverage. `cross-origin-opener-policy: same-origin` and `cross-origin-resource-policy: same-origin` came in with the upstream merge in #384. They are built in `floorWith` in `src/server.ts`, the same place as the rest of the security headers, so every route gets them. The README's security paragraph already lists them and explains them.…

shipped production is serving it deploy ae07c2d

opened this

Main already sends both headers, so this pull only adds test coverage. cross-origin-opener-policy: same-origin and cross-origin-resource-policy: same-origin came in with the upstream merge in #384. They are built in floorWith in src/server.ts, the same place as the rest of the security headers, so every route gets them. The README's security paragraph already lists them and explains them. The isolation test in src/server.test.ts already covered each game page, the bundle, a match post, and the 404, 403, 413, 429 and 500 responses. The WebSocket 101 test checks them as well.

What changed: the isolation test predates #391, so it didn't cover the routes and files that came after. It now also checks /weapons, /engine, the policy weights (/static/weights.<hash>.json) and the engine archive (/engine/<pkg>-<version>.<hash>.tar.gz). The weights and the archive are the year-cached files the issue names. With the CORP line removed from floorWith, the test fails; with it restored, bun run test passes (1633 tests, typecheck included). No other file changed: the CSP and the caching are the same.

What each header refuses:

  • COOP same-origin: a page from another site that opens one of ours with window.open gets no window.opener handle to it. Our pages keep no handle on anything they navigate to.
  • CORP same-origin: another site cannot load any of our responses as a resource in its page (script, image, fetch without CORS). That includes the bundle, the weights and the archive.

Nothing opens, or is opened in, a cross-origin window. I checked src/: there is no window.open, no target=, no opener and no postMessage. The only links to other origins are https://bun.sh and https://typesafe.ai, and both are plain same-tab links.

Why same-origin everywhere, with nothing relaxed:

  • The only file meant to leave this origin is the engine archive, and it is taken as a download (content-disposition: attachment). Downloading is a navigation, which CORP does not affect.
  • This will break anyone hotlinking the bundle or the archive as a script. Nobody should be: #225 makes the engine something you take as a package, not a script you link to.
  • COOP only takes effect over https, so a local http://localhost run looks the same in the browser. It is sent anyway, and the preview and production, which are https, enforce it.

To try it: I booted the app locally. /, /range and /example (the three games), plus /engine and /weapons, answer 200, and /nope answers 404, all with both headers. That local run is not the preview; confirm the three games boot there with curl -sI <preview>/range | grep -i cross-origin.

🤖 Generated with Claude Code

Closes #230

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs