sec: tighten style-src CSP (SEC-29, partial)#945
Conversation
The API serves JSON (Swagger HTML is excluded from CSP via ExcludeSwaggerFromContentSecurityPolicy), so no API response needs inline styles. Tightening style-src closes an inline-style injection vector for the API surface. Adds a regression test so future edits cannot silently reintroduce 'unsafe-inline' into the API CSP. Refs #855
Adds a style-src-elem 'self' directive to the nginx reverse proxy (and the Terraform single-node user_data template) so that modern browsers block inline <style> blocks and externally-injected stylesheets served from the SPA origin. The main style-src keeps 'unsafe-inline' as a fallback required for Vue's reactive :style bindings (label color swatches, dynamic progress widths, virtualization offsets) and for browsers without -elem/-attr support. This is a scoped hardening step. Follow-up work will migrate dynamic :style bindings to CSS custom properties on static classes so 'unsafe-inline' can be dropped from style-src entirely. Refs #855
Updates the OWASP baseline, configuration reference, audit table, and hardening/performance doc to reflect: - API CSP no longer includes 'unsafe-inline' in style-src - reverse-proxy CSP gains style-src-elem 'self' while retaining 'unsafe-inline' in style-src as a fallback for Vue reactive :style Refs #855
Convention is comment-above-attribute. Moves the SEC-29 rationale comment to the conventional position; no behavior change. Refs #855
Adversarial self-reviewWhat actually changed (double-check)
Risks I considered
What I didn't do (deliberate)
Honest assessmentNo real findings. The PR does what it claims: removes |
There was a problem hiding this comment.
Code Review
This pull request hardens the Content Security Policy (CSP) by removing 'unsafe-inline' from the style-src directive for the API, as it primarily serves JSON. For the frontend, the reverse proxy configuration is updated to include style-src-elem 'self', which blocks inline style blocks in modern browsers while retaining 'unsafe-inline' in style-src as a fallback for Vue's reactive style bindings. Corresponding updates were made to the application settings, documentation, and a new test case was added to ensure the API CSP remains strict. I have no feedback to provide.
Round 2 review finding: the existing comments/docs claim the API serves only JSON, but in the single-container production topology (Dockerfile.production / Railway / Render) the API also serves the Vue SPA's index.html from wwwroot/ via UseStaticFiles + MapFallbackToFile. The SEC-29 CSP tightening still applies correctly (Vue's :style bindings mutate element.style properties via JavaScript, which CSP style-src-attr does not govern), but the reasoning in the code/test comments and OWASP baseline doc was incomplete. Update them to reflect the real topology so future contributors understand why removing 'unsafe-inline' is safe on the actual attack surface (SPA HTML), not only JSON responses.
Round 2 adversarial reviewBot triage
New findings1. The round-1 claim "API serves JSON" is incomplete — the API also serves the Vue SPA HTML. In the single-container production topology ( Consequence: the API CSP change is NOT cosmetic — it DOES land on the real browser attack surface in cloud deployments. That's actually a stronger security outcome than the PR advertises. But it also means the existing justification ("API serves JSON; Swagger excluded") is wrong about scope. Why the change is still safe: Vue's runtime Action taken: commit
2. Regression test only exercises
3. Substring match robustness (minor, non-blocking).
4. Reverse-proxy CSP has both 5. Terraform user_data idempotency (verified).
6. Cross-doc consistency (verified).
CIAt briefing time 15/15 checks pass. Round 2 commit is additive (doc/comment only) — should not change CI outcome. Will monitor after push. VerdictApprove with merge. The PR delivers real, measurable hardening on the production attack surface (SPA HTML served by the API). The only substantive issue was self-documentation that understated the scope of the change, now corrected. The deferred 27-file |
Adversarial Security Review -- PR #945 (SEC-29: style-src CSP tightening)CI status: all 15 checks pass. No prior review comments. Files reviewed
P1 -- Important (confidence 85-89)1. Single-container topology: Vue
|
Combine PR branch's original SEC-29 CSP documentation with main's docs sweep updates. Keep the more detailed version with 27-file count and specific migration guidance.
Summary
Partial resolution of SEC-29 (
#855). Moves the CSP offstyle-src 'unsafe-inline'on the API side and narrows the reverse-proxy CSP without breaking Vue's reactive:stylebindings.appsettings.json,SecurityHeadersSettings.cs): removed'unsafe-inline'fromstyle-srcentirely. The API serves JSON and Swagger (Swagger is already excluded from CSP viaExcludeSwaggerFromContentSecurityPolicy), so no API response needs inline styles.deploy/nginx/reverse-proxy.conf,deploy/terraform/aws/modules/single_node/user_data.sh.tftpl): addedstyle-src-elem 'self'so modern browsers block inline<style>blocks and injected external stylesheets served from the SPA origin (the main XSS sink).style-src 'self' 'unsafe-inline'remains as a fallback for (a) browsers without-elemsupport and (b) Vue's reactive:stylebindings which set DOM style properties at runtime.SecurityHeadersApiTests.SecurityHeaders_CspStyleSrc_ShouldNotAllowUnsafeInlineasserts the API CSP does not re-introduce'unsafe-inline'instyle-src.docs/security/SECURITY_OWASP_BASELINE.md,docs/platform/CONFIGURATION_REFERENCE.md,docs/AUDIT.md,docs/HARDENING_AND_PERFORMANCE.mdto reflect the current baseline and flag the remaining follow-up.Why partial
There are 27 files with dynamic
:stylebindings in production Vue components — label color swatches (CardItem,CardModal,FilterPanel,LabelManagerModal,SavedViewsView), computed progress/height/width (MetricsView,DevToolsView), virtualization offsets (InboxListPanel,ActivityResults), and themed status badges (AutomationQueueView). These inject runtime style property values via Vue'sel.style.x = ycode path, which falls understyle-src-attr 'unsafe-inline'in CSP3. Removing'unsafe-inline'without migrating these first would break drag/drop, virtualization, and label coloring.The scoped strategy here preserves review-first trust (no visible regressions) while closing the inline
<style>injection vector in modern browsers viastyle-src-elem.Follow-up
A separate issue should migrate the remaining
:stylebindings to CSS custom properties applied via static classes (e.g.style="--tag-color: ..."set once on a wrapper, consumed by the scoped stylesheet) so'unsafe-inline'can be dropped entirely. Called out in theHARDENING_AND_PERFORMANCE.mdandAUDIT.mdupdates.Test plan
dotnet build backend/Taskdeck.sln -c Release— builds clean (0 errors)dotnet test backend/Taskdeck.sln --filter "FullyQualifiedName~SecurityHeadersApiTests"— 5/5 pass (4 existing + 1 new)npm run typecheck(frontend) — passesnpm run lint(frontend) — no new warningsnpx vitest --run(frontend) — 2607/2607 passRefs #855