docs: zeg bij connect-src wat CORS niet tegenhoudt #546

Merged
brenno merged 1 commit from docs/csp-connect-src-redenering into main 2026-07-22 12:05:21 +00:00
Owner

Summary

Third item from #516: the connect-src 'self' https: directive, which the issue flags as permitting a no-cors POST to any host.

No directive changed. What changed is the reasoning next to it, which was subtly wrong in a way that mattered.

What was wrong

The comment justified connect-src https: like this: the browser's CORS policy gates every cross-origin read, so a non-cooperating host cannot be read through it. That is true, and it is a good argument — about reads.

CORS does not stop a no-cors POST. The request is sent; only the response is opaque. So the directive leaves a one-way write channel to any https host. That is an exfiltration path, and CORS was never the control for it — the comment attributed the protection to the wrong mechanism.

Why the directive stays

Closing it means removing URL-import on web, a feature the user asks for and initiates. And reaching the channel requires script execution in this origin, which is what script-src 'self' without 'unsafe-inline'/'unsafe-eval' actually prevents.

So: the control here is script-src, not connect-src. connect-src narrows the blast radius if script-src ever fails, and the no-referrer meta below keeps the deck's own URL out of the request. All of that is now written down, along with when to revisit — if URL-import ever moves behind a fixed set of hosts, connect-src can name them instead of allowing https: wholesale.

Why this is worth a commit on its own

A security comment that credits the wrong mechanism is worse than no comment: the next reader sees a justification, believes the case is covered, and moves on. That is how a real gap survives three reviews.

Verification

make check green. make check-web green — the CSP itself is byte-identical, and tool/check_web_hardening.dart still pins connect-src to exactly 'self' https:.

Part of #516; six items left there.

## Summary Third item from #516: the `connect-src 'self' https:` directive, which the issue flags as permitting a no-cors POST to any host. **No directive changed.** What changed is the reasoning next to it, which was subtly wrong in a way that mattered. ## What was wrong The comment justified `connect-src https:` like this: the browser's CORS policy gates every cross-origin read, so a non-cooperating host cannot be read through it. That is true, and it is a good argument — about **reads**. CORS does not stop a `no-cors` POST. The request is sent; only the response is opaque. So the directive leaves a one-way **write** channel to any https host. That is an exfiltration path, and CORS was never the control for it — the comment attributed the protection to the wrong mechanism. ## Why the directive stays Closing it means removing URL-import on web, a feature the user asks for and initiates. And reaching the channel requires script execution in this origin, which is what `script-src 'self'` without `'unsafe-inline'`/`'unsafe-eval'` actually prevents. So: **the control here is `script-src`, not `connect-src`.** `connect-src` narrows the blast radius if `script-src` ever fails, and the `no-referrer` meta below keeps the deck's own URL out of the request. All of that is now written down, along with when to revisit — if URL-import ever moves behind a fixed set of hosts, `connect-src` can name them instead of allowing `https:` wholesale. ## Why this is worth a commit on its own A security comment that credits the wrong mechanism is worse than no comment: the next reader sees a justification, believes the case is covered, and moves on. That is how a real gap survives three reviews. ## Verification `make check` green. `make check-web` green — the CSP itself is byte-identical, and `tool/check_web_hardening.dart` still pins `connect-src` to exactly `'self' https:`. Part of #516; six items left there.
docs(csp): zeg wat CORS níet tegenhoudt bij connect-src
Some checks failed
CI / Gate (Linux) · Format · Analyze · Coverage (pull_request) Has been cancelled
CI / Test (macos-latest) (pull_request) Has been cancelled
CI / Test (windows-latest) (pull_request) Has been cancelled
CI / Web hardening (pull_request) Has been cancelled
CI / Docs links (pull_request) Has been cancelled
CI / Supply-chain (Trivy · advisory) (pull_request) Has been cancelled
CI / Gate (Linux) · Format · Analyze · Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Web hardening (push) Has been cancelled
CI / Docs links (push) Has been cancelled
CI / Supply-chain (Trivy · advisory) (push) Has been cancelled
c21c6e2bb7
De redenering bij connect-src ging over lézen, en klopt daarvoor: CORS
poort elke cross-origin read, dus een niet-meewerkende host is er niet
door uit te lezen.

Wat er niet stond: CORS houdt een no-cors POST niet tegen. Zo'n verzoek
gaat wél de deur uit, alleen het antwoord is ondoorzichtig. Dat is een
eenrichtings-schrijfkanaal naar elke https-host — een exfiltratiepad, en
CORS was daar nooit de maatregel voor.

Bewust geaccepteerd, met de reden erbij: dichtzetten betekent URL-import
op web opheffen, en het pad bereiken vereist scriptuitvoering in deze
oorsprong — wat script-src 'self' zonder unsafe-inline/unsafe-eval juist
tegenhoudt. De maatregel hier is dus script-src; connect-src beperkt de
schade als die ooit faalt.

Een beveiligingscommentaar dat een maatregel toeschrijft aan het
verkeerde mechanisme is erger dan geen commentaar: de volgende lezer
denkt dat het afgedekt is.

Onderdeel van #516.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit 724e807937 into main 2026-07-22 12:05:21 +00:00
brenno deleted branch docs/csp-connect-src-redenering 2026-07-22 12:05:21 +00:00
Sign in to join this conversation.
No description provided.