Conversation
Ship require-trusted-types-for 'script' on a separate, always-report-only Content-Security-Policy-Report-Only header so it collects sink-write violations without touching the enforcing policy. Report-only can only report, never block, so no un-migrated DOM sink throws on Chromium; other browsers ignore the directive. No named createHTML policy is registered and no trusted-types allowlist is emitted — this is telemetry only. Enforcement (the single sanitize() chokepoint), bc3/haystack, and the trix/lexxy policy hooks stay parked on Avenue B. A dedicated middleware, inserted outside the Rails CSP middleware, stacks the directive as an additional report-only policy so it never clobbers an app policy that is itself report-only.
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.
Trusted Types report-only pilot (XSS counteroffensive — Tier C)
Report-only telemetry pilot only — the greenlit option 1 from the Security Hardening card. Adds Trusted Types violation reporting to fizzy with zero enforcement and zero app-code migration, so we get real signal on which DOM sinks write raw strings before committing to the enforcement design.
What ships
A new
TrustedTypesReportOnlyRack middleware inconfig/initializers/content_security_policy.rbemits a separateContent-Security-Policy-Report-Onlyheader on HTML responses carrying:plus
report-uri <uri>when a report-uri is configured (reusing the existingCSP_REPORT_URI/config.x.content_security_policy.report_uriknob).Why a separate header, not the app policy
Fizzy ships a single Rails-managed CSP whose whole header flips between enforcing and report-only via one
report_onlyflag (default: enforcing). Adding the Trusted Types directive into that policy would put it in the enforcing header wherever fizzy runs enforcing — and, with no namedcreateHTMLpolicy registered, every one of the un-migrated first-party sinks wouldthrowon Chromium. That is the parked enforcement phase, not this PR.So Trusted Types rides its own always-report-only header instead:
Content-Security-Policyheader is byte-for-byte unchanged.ActionDispatch::ContentSecurityPolicy::Middleware, so when a deployment already runs the app policy as report-only, the Trusted Types policy stacks as a second report-only policy (newline-joined → two header lines) rather than clobbering it. Each reports independently.Why only
require-trusted-types-for 'script'(notrusted-typesallowlist)The goal is sink-write telemetry.
require-trusted-types-for 'script'is what makes a raw-string write to a DOM sink surface as a violation report. Atrusted-types <policy-name>allowlist does something different — it restricts which policy names may be created — and since this pilot registers no namedcreateHTMLpolicy, adding an allowlist would only generate policy-creation noise (including from lexxy's isolated DOMPurify policy). Omitting it leaves policy creation unrestricted, which is exactly right for report-only.Verification
test/integration/static_csp_test.rb: the enforcing CSP carries norequire-trusted-types-for; a report-only header carriesrequire-trusted-types-for 'script'; notrusted-typesallowlist is emitted.Parked follow-ons (Avenue B — the enforcement phase)
Everything below stays parked, gated on the shared
sanitize()chokepoint landing:createHTML → sanitize()policy and flip to enforcing, only after the report-only window enumerates every real sink and the 6 first-party fizzy sinks are migrated onto the chokepoint. Human sign-off required; not auto-PR-eligible.Design doc: trusted-types.md.