Skip to content

feat(csp): promote Trusted Types from report-only to enforcing (#130)#718

Draft
millsmillsymills wants to merge 2 commits into
mainfrom
feat/130-trusted-types-enforce-r2
Draft

feat(csp): promote Trusted Types from report-only to enforcing (#130)#718
millsmillsymills wants to merge 2 commits into
mainfrom
feat/130-trusted-types-enforce-r2

Conversation

@millsmillsymills

@millsmillsymills millsmillsymills commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Replaces #712 (same change, rebased onto main 2fc8e5f post-#715; the old branch could not be force-pushed under the repo's git guardrails).

Promotes Trusted Types from the parallel Content-Security-Policy-Report-Only header into the enforcing CSP, closing #130:

  • infra/cloudfront.tf: folds require-trusted-types-for 'script'; trusted-types default unifi-demo into local.html_csp; removes the html_tt_report_only local and both -Report-Only header blocks. Both allowlisted policy names survive the flip — default (site bundle, src/scripts/util/trusted-types.ts) and unifi-demo (public/apps/unifi-demo/app.js).
  • src/data/security-controls.ts: the single trusted-types entry flips to "enforced", citing cloudfront.tf, trusted-types.ts, mail-pow.ts, and the unifi-demo app.
  • src/scripts/util/trusted-types.ts: comment scoped — the default policy defines no createHTML; the unifi-demo asset mints its markup via its own allowlisted policy.

terraform fmt/validate, npm run check, and a full npm run build pass.

Draft until the report-only soak completes (~2026-06-23; clock started 2026-06-09 23:39:45 UTC — the #715/2fc8e5f deploy that shipped the fixed unifi-demo app.js; the earlier 18:33 UTC deploy predated that fix, so the 22:53 UTC 06-09 reports are stale pre-#715 noise, not enforce-blockers — see soak-status comment). Before merging: re-query s3://millsymills.com-csp-reports/reports/ and confirm zero post-soak-start require-trusted-types-for reports, then mark ready, merge, deploy, terraform apply.

Related: #716 (CI assert coupling TT policy names between directive and createPolicy call sites).

🤖 Generated with Claude Code

Fold `require-trusted-types-for 'script'; trusted-types default` into both
enforcing CSP headers (site + webauthn policies) and drop the standalone
`Content-Security-Policy-Report-Only` TT slices. The runtime is enforce-clean
(#711 removed the last injection sinks); the one legitimate sink — the PoW
Worker URL — is wrapped by the fail-closed `default` policy in
src/scripts/util/trusted-types.ts. Updates the /security/ control entry to
match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
millsmillsymills added a commit that referenced this pull request Jun 10, 2026
Add scripts/assert-tt-policy-names.sh: extract the policy names from the
`trusted-types` directive in infra/cloudfront.tf locals and compare them
bidirectionally against every createPolicy('<name>') call site under src/
and public/. Fails when a name is declared without a call site, or a call
site uses a name the directive does not allowlist — drift that ships green
today and throws at runtime once the CSP flips to enforce (#130/#718).

Wired into scripts/ci-local.sh and the Astro CI job in lockstep.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@millsmillsymills

Copy link
Copy Markdown
Owner Author

Soak status check — 2026-06-11

Re-queried s3://millsymills.com-csp-reports/reports/ and cross-referenced against deploy history. The soak anchor in the description (2026-06-09 18:33 UTC) is too early and should be corrected to 2026-06-09 23:39:45 UTC.

Why: 18:33 UTC (9beffc1) was the deploy that made the report-only header strict (trusted-types default unifi-demo), but the old unifi-demo app.js (raw innerHTML) was still live. The app.js fix (ttHTML via the unifi-demo policy) shipped in #715 / 2fc8e5f, which only deployed at 23:39:45 UTC. #711's sink removal (the 18:33 anchor) never touched unifi-demo's sinks.

Consequence: three reports landed at 22:53–22:54 UTC 06-09, all effectiveDirective: require-trusted-types-for, sourceFile: .../apps/unifi-demo/app.js, originalPolicy: ...trusted-types default (no unifi-demo). These are the pre-#715 app.js firing against the new strict header — stale, not enforce-blockers. Under the 18:33 anchor a merge session would wrongly read the soak as dirty.

Since the real anchor (23:39:45 UTC): zero CSP reports of any kind in the bucket (last object 22:54:27 UTC 06-09), plus a clean fdadbe0 deploy on 06-10. ~1.6 days clean so far.

Verified live: report-only header serves require-trusted-types-for 'script'; trusted-types default unifi-demo, and live app.js carries createPolicy('unifi-demo') + ttHTML — i.e. the fixed asset and the matching allowlist are both in production, so the soak now measures the real enforce target.

Revised earliest merge: ~2026-06-23 (still ≥2 weeks from 06-09; the corrected anchor is only ~5h later, so the date is unchanged). Gate unchanged otherwise: re-query at soak end, merge latest main into the branch first (#719 landed after the rebase), then mark ready → merge → deploy → terraform apply.

@millsmillsymills

Copy link
Copy Markdown
Owner Author

Merged origin/main (11 commits) into the branch to keep it current during the TT enforce soak — branch was 11 behind. Resolved clean (no conflicts). Verified post-merge: scripts/assert-tt-policy-names.sh green (default + unifi-demo in sync), npm run build exit 0, npm run check 0 errors/0 warnings/0 hints. Still draft — soaking until ~2026-06-23 per #715 deploy clock. No soak-clock impact (clock tracks the deployed report-only policy on prod, not this branch).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant