Public: anyone can read this; the crew can change it.
Every HTML, JSON and plain-text answer carries Cross-Origin-Resource-Policy: same-origin, and public assets and feeds say cross-origin on purpose #10975
Check · Review · Land · Live done — Landed and live in production.
Every answer the forge writes now carries `Cross-Origin-Resource-Policy: same-origin`. Another site can no longer load a page, an API answer or an error into itself as a no-cors subresource (images, scripts and the like). A short public set says `cross-origin` on purpose, so embeds, feed readers and link previews keep working.
shipped production is serving it deploy 114667f
Every answer the forge writes now carries Cross-Origin-Resource-Policy: same-origin. Another site can no longer load a page, an API answer or an error into itself as a no-cors subresource (images, scripts and the like). A short public set says cross-origin on purpose, so embeds, feed readers and link previews keep working.
What changed
- The floor:
securityFloorinsrc/http/respond.tssetscross-origin-resource-policy: same-originwhenever the response doesn't already have one. - The public set: a new module,
src/http/corp.ts, lists the public paths, and the server marks their answerscross-originjust before the floor runs. I did this in one place, where every answer passes (answeredinsrc/http/server.ts), rather than in each asset and feed handler, so a new handler can't forget it. The set is:
- Always cross-origin, because the bytes are the same for everyone: /assets/, /icons/, /favicon.ico, the apple-touch icons, /robots.txt, /llms.txt, /sitemap.xml, /manifest.webmanifest and /.well-known/.
- Cross-origin only when the request carried no cookie or token: feeds (.atom) and share cards (/og/*). When asked for with a session they can show private content, so they stay same-origin. Feed readers, link previews and cross-site embeds send no session (the session cookie is SameSite=Lax), so nothing they use breaks.
- Any error (status 400 or above) on a public path stays same-origin.
- Not touched: answers that apps send back through the proxy don't pass through the floor, so their own headers are left as they are.
Tests: the new test/corp.test.ts fails without the change (2 of its 3 tests fail). It checks:
- Pages, API answers (signed in and out) and 404s are
same-origin, as are a feed and a share card asked for with a session. - Assets, the changelog and repository feeds, the crawler and agent files, the manifest,
/.well-known/security.txt, the favicon and the share cards (site svg and png, repository svg) arecross-origin, as the issue's risk note asked. - A unit check of the path rules.
docs/SECURITY-RUNBOOK.md has a new section that names the header, explains why the public set is exempt, and says how to check a broken link preview with curl.
Checks run: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named 1839 test files, too many to run here, so I chose these myself and they pass: corp, both security-floor suites, security-controls, web, webspec, scaffold-webspec, atom-feeds, feeds, cache-private, caching-doc, compress-serve, og-private, og-route and llms-anonymous. The forge's full check runs on the push.
Try it: run curl -sI <preview>/og/site.png and curl -sI <preview>/api/v1/user. The first should answer cross-origin, the second same-origin.
🤖 Generated with Claude Code
Every answer the forge writes now carries Cross-Origin-Resource-Policy: same-origin, so another site can no longer load a page, an API answer or an error into itself as a no-cors subresource. A short list of public paths says cross-origin on purpose, so embeds, feed readers and link previews keep working.
What changed
- The floor:
securityFloorinsrc/http/respond.tssetscross-origin-resource-policy: same-originwhenever a response has none. - The public set: a new module,
src/http/corp.ts, lists the public paths, andansweredinsrc/http/server.tsmarks their successful answerscross-originbefore the floor runs. It is done in that one place, where every answer passes, so a new asset or feed handler can't forget it.
- Always cross-origin, because the bytes are the same for everyone: /assets/, /icons/, /favicon.ico, the apple-touch icons, /robots.txt, /llms.txt, /sitemap.xml, /manifest.webmanifest and /.well-known/.
- cross-origin only when the request carried no cookie or token: feeds (.atom) and share cards (/og/*, svg and png). Asked for with a session, they can hold private content, so they stay same-origin. Feed readers, link previews and cross-site embeds send no session, because the cookie is SameSite=Lax.
- An error (status 400 or above) on a public path stays same-origin.
- Apps: what an app sends back through the proxy doesn't pass through the floor, so it keeps its own headers.
- Runbook:
docs/SECURITY-RUNBOOK.mdhas a new section that names the header, explains why the public set is exempt, and shows how to check a broken link preview withcurl.
Tests: the new test/corp.test.ts fails without the change (two of its three tests fail). It checks:
- Pages, API answers (signed in and out) and 404s are
same-origin, and so are a feed and a share card asked for with a session. - Assets, the changelog and repository feeds, the crawler and agent files, the manifest,
/.well-known/security.txt, the favicon and the share cards arecross-origin, as the issue's risk note asked. - The path rules themselves, as a unit check.
The first check failure: mcp-write-scope and mcp-system-stalls failed on this pull, but they also fail at the commit the branch started from, and they pass on current main. I merged main into the branch, and both now pass along with the CORP and security-floor tests. bun scripts/gate.ts and bun x tsc --noEmit pass.
Try it: curl -sI <preview>/og/site.png should say cross-origin, and curl -sI <preview>/api/v1/user should say same-origin.
🤖 Generated with Claude Code
Clean. The floor sets same-origin by default and the public set is marked once, where every answer passes. Feeds and share cards go cross-origin only with no cookie or token, and errors stay same-origin. No findings. Tests cover page, API, 404, asset and feed. The runbook section is present. The preview answers cross-origin on /og/site.png.
Every answer the forge writes now carries
Cross-Origin-Resource-Policy: same-origin. Another site can no longer load a page, an API answer or an error into itself as a no-cors subresource (images, scripts and the like). A short public set sayscross-originon purpose, so embeds, feed readers and link previews keep working.What changed
securityFloorinsrc/http/respond.tssetscross-origin-resource-policy: same-originwhenever the response doesn't already have one.src/http/corp.ts, lists the public paths, and the server marks their answerscross-originjust before the floor runs. I did this in one place, where every answer passes (answeredinsrc/http/server.ts), rather than in each asset and feed handler, so a new handler can't forget it. The set is:- Always cross-origin, because the bytes are the same for everyone:
/assets/,/icons/,/favicon.ico, the apple-touch icons,/robots.txt,/llms.txt,/sitemap.xml,/manifest.webmanifestand/.well-known/. - Cross-origin only when the request carried no cookie or token: feeds (.atom) and share cards (/og/*). When asked for with a session they can show private content, so they staysame-origin. Feed readers, link previews and cross-site embeds send no session (the session cookie isSameSite=Lax), so nothing they use breaks. - Any error (status 400 or above) on a public path stayssame-origin.Tests: the new
test/corp.test.tsfails without the change (2 of its 3 tests fail). It checks:same-origin, as are a feed and a share card asked for with a session./.well-known/security.txt, the favicon and the share cards (site svg and png, repository svg) arecross-origin, as the issue's risk note asked.docs/SECURITY-RUNBOOK.mdhas a new section that names the header, explains why the public set is exempt, and says how to check a broken link preview withcurl.Checks run:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 1839 test files, too many to run here, so I chose these myself and they pass: corp, both security-floor suites, security-controls, web, webspec, scaffold-webspec, atom-feeds, feeds, cache-private, caching-doc, compress-serve, og-private, og-route and llms-anonymous. The forge's full check runs on the push.Try it: run
curl -sI <preview>/og/site.pngandcurl -sI <preview>/api/v1/user. The first should answercross-origin, the secondsame-origin.🤖 Generated with Claude Code
Closes #10965