feat(web): baseline browser security headers on HTTP responses#39
Merged
Conversation
Adds a CSP, X-Frame-Options, X-Content-Type-Options and Permissions-Policy layer to every plain-HTTP response so the ZAP baseline scan passes. The CSP keeps the SPA working: inline styles for MUI/Emotion/xterm, data: images and fonts, and connect-src https: so WebTransport to the (different, configurable) WT origin is permitted. Triages the two non-actionable ZAP findings into .zap/rules.tsv: Suspicious Comments (false positive in the minified bundle) and Non-Storable Content (the SPA shell is no-store by design).
passcod
added a commit
that referenced
this pull request
Jun 19, 2026
🤖 The nightly ZAP baseline surfaced a second tier of findings once the initial security headers (#39) were in place — ZAP runs deeper passive checks once a CSP exists. This resolves the real ones and triages the rest. Verified by running the ZAP baseline locally against the stub stack: **0 WARN-NEW**. ## Fixed in code - **CSP: Wildcard Directive [10055]** — `connect-src` no longer uses the `https:` scheme wildcard. It now names the exact WebTransport origin (the request host on `wt_port`, reconstructed the same way `auth::handle_connect` builds `wt_url`), so the header is built per-request. - **Cross-Origin-* headers [90004]** — added `Cross-Origin-Embedder-Policy: require-corp`, `Cross-Origin-Opener-Policy: same-origin`, and `Cross-Origin-Resource-Policy: same-origin`. The console embeds no third-party content, so full site isolation is safe. ## Triaged as IGNORE in `.zap/rules.tsv` - **style-src unsafe-inline [10055]** — required by the Emotion/MUI toolkit, which injects runtime inline styles and `style=` attributes. Can't be dropped without server-side nonce templating of the embedded SPA. The rest of the CSP stays tight in code and is guarded by a unit test. - **Timestamp Disclosure [10096]** and **Private IP Disclosure [2]** — false positives in the minified bundle; the `10.0.0.1` is an example placeholder in a host-entry form field (`Services.tsx`). - **Modern Web Application [10109]** — informational SPA detection. ## Notes `COEP: require-corp` changes resource-loading semantics in the browser; the Playwright e2e suite exercises the real app and will catch any regression. Spec item `transport.http.security-headers` updated accordingly, with the unit test extended to assert the precise `connect-src` origin and the isolation headers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 The ZAP baseline DAST scan failed on the plain-HTTP
seedling-websurface because several browser security headers were missing. This sets them on every response and triages the two non-actionable findings.Headers now set (
crates/web/src/http.rs)X-Content-Type-Options: nosniffX-Frame-Options: DENY(+ CSPframe-ancestors 'none')Content-Security-PolicyPermissions-PolicyThe CSP is intentionally not maximally strict, because a naive policy would break the SPA:
style-src 'self' 'unsafe-inline'— MUI/Emotion and xterm inject runtime<style>blocks.img-src/font-src 'self' data:— the favicon is adata:SVG.connect-src 'self' https:— the SPA opens WebTransport tohttps://{wt_hostname}:{wt_port}/wt, a different and operator-configurable origin;'self'would block it. A test locks this so it can't regress.Triaged as IGNORE in
.zap/rules.tsv10027Suspicious Comments — false positive matching tokens in the minified frontend bundle.10049Non-Storable Content — the SPA shell is servedno-storeby design (seespa.delivery).HSTS (10035) stays Caddy-owned as before; the new content headers correctly are not.
Spec-first: adds
transport.http.security-headerstodocs/spec/web.md, annotated with impl and a verifying test.