Skip to content

fix: deployment-surface hardening β€” Permissions-Policy, font pipeline, CSP headers - #278

Merged
qnbs merged 5 commits into
mainfrom
fix/deployment-surface-csp-fonts
Jul 28, 2026
Merged

fix: deployment-surface hardening β€” Permissions-Policy, font pipeline, CSP headers#278
qnbs merged 5 commits into
mainfrom
fix/deployment-surface-csp-fonts

Conversation

@qnbs

@qnbs qnbs commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Three P0 fixes, all in the gap between build artifact and deployment surface (no existing CI gate β€” E2E/Lighthouse run against vite preview, where none of _headers/nginx.conf/Tauri CSP apply):

  • Permissions-Policy: microphone=() (empty allowlist) silently killed Voice (Whisper STT, push-to-talk, mic-level meter) on Cloudflare Pages + the Docker/nginx image, since it blocks same-origin getUserMedia/SpeechRecognition too. Fixed to microphone=(self) in both hosts.
  • Font pipeline: Noto Sans GR doesn't exist at Google Fonts β€” the combined CSS2 request 400'd, silently dropping the JP/KR requests alongside it, and it would've been CSP-blocked in Tauri regardless. Self-hosts CJK (JP/KR/SC) + Greek via @fontsource, matching the existing Arabic/Hebrew pattern; drops the Google Fonts CDN entirely.
  • CSP response headers + ADR-0004: ADR-0004 claimed "the host tightens CSP further via HTTP response headers in production" β€” false, none of vercel.json/_headers/nginx.conf set one. Adds a real Content-Security-Policy header (identical to the meta CSP) to all three; the actual new hardening is frame-ancestors 'none', inert as a meta tag. Corrects the ADR and SECURITY-THREAT-MODEL.md, and documents that GitHub Pages (the canonical mirror) cannot set any response header at all.

Each fix carries its own regression test: tests/unit/deploymentHeaders.test.ts (new), tests/unit/fontPipeline.test.ts (new), tests/unit/csp.test.ts (extended).

Test plan

  • pnpm run lint β€” 0 diagnostics
  • node scripts/check-suppressions.mjs β€” 52/52, unchanged
  • npx tsgo --project tsconfig.tsgo.json --noEmit --checkers 4 β€” 0 errors
  • pnpm run token:audit β€” 160 ≀ 165 baseline
  • pnpm run test:run β€” 527 files / 6310 tests passing (incl. the 3 new/extended files, 19 tests)
  • pnpm run build && pnpm run bundle:budget β€” green, fonts don't inflate JS chunks
  • CI (pending)

πŸ€– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Continued QNBS-v3 rollout with self-hosted Greek and CJK fonts and added matching fontsource packages.
    • Strengthened security headers across deployment targets, including CSP enforcement and microphone permissions limited to same origin.
  • Documentation

    • Added β€œHeader invariants per host” and updated CSP/host-specific enforcement notes in threat-model and ADR guidance.
  • Tests

    • Added/expanded regression coverage to keep CSP and Permissions-Policy consistent across deployment surfaces and to enforce the self-hosted font/CSP contract.
  • Chores

    • Refreshed README CI health/test metrics baselines.

qnbs added 3 commits July 28, 2026 19:03
…on CF Pages + Docker

Permissions-Policy: microphone=() is the empty allowlist β€” it disallows the
microphone for every origin, including same-origin. Voice
(hooks/useMicLevel.ts, hooks/useSpeechRecognition.ts) calls
getUserMedia/SpeechRecognition from same-origin code, so this silently killed
Whisper STT, push-to-talk, and the mic-level meter on Cloudflare Pages and the
Docker/nginx image. Fixed to microphone=(self) in both public/_headers and
nginx.conf, with a regression test (tests/unit/deploymentHeaders.test.ts) that
locks the value and asserts parity between the two hosts. Documented the full
per-host header matrix (including GitHub Pages' inability to set any response
header at all) in docs/DEPLOYMENT.md.
…to Sans GR does not exist)

"Noto Sans GR" does not exist at Google Fonts β€” the combined CSS2 request for
Noto+Sans+JP/KR/GR 400'd as a whole, silently dropping the JP and KR font
requests alongside the invalid GR one. Japanese, Korean, and Greek got no
webfont in the web build, and the request would have been CSP-blocked in the
Tauri build regardless (font-src there never allowed fonts.gstatic.com).

Self-hosts CJK/Greek via @fontsource, matching the existing Arabic/Hebrew
pattern: @fontsource/noto-sans-jp, noto-sans-kr, noto-sans-sc (zh is
Simplified Chinese per i18n/locales.ts), and the base @fontsource/noto-sans
package's `greek` subset for el (replacing the nonexistent "Noto Sans GR"
family entirely). Removes the Google Fonts <link>/preconnect tags and the
fonts.googleapis.com/fonts.gstatic.com CSP allowances from index.html.

New tests/unit/fontPipeline.test.ts asserts no live reference to a Google
Fonts host or the dead family name remains, and that every --font-ui-* family
in index.css has a matching @fontsource import. Verified pnpm run build +
bundle:budget stay green β€” @fontsource CSS registers @font-face rules against
separate .woff2 assets, so it doesn't inflate the JS chunks the budget checks.
…rrect the ADR

ADR-0004's Consequences section claimed "the host (Vercel/CF) tightens CSP
further via HTTP response headers in production" β€” this was false: none of
vercel.json, public/_headers, or nginx.conf set a Content-Security-Policy
header, so the accepted connect-src residual risk had no compensating
control, and GitHub Pages (the canonical upstream mirror) cannot set one at
all regardless.

Adds a real Content-Security-Policy header to all three hosts that can set
one, identical to the index.html meta CSP so there's nothing to diverge on.
connect-src is unchanged (ADR-0004's BYOK tradeoff still applies there); the
actual new hardening is frame-ancestors 'none', which only takes effect as a
header and was previously inert in the meta tag.

Corrects ADR-0004 and docs/SECURITY-THREAT-MODEL.md to state the real
control (and the GitHub Pages limitation) instead of the false claim, and
extends tests/unit/csp.test.ts to assert the three header CSPs exist, match
each other, and are never looser than the meta CSP for any shared directive.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldscript-studio Ready Ready Preview, Comment Jul 28, 2026 7:31pm

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3e3bfaa-8107-4e33-a1ec-43d7734ef92e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
πŸ“ Walkthrough

Walkthrough

The pull request self-hosts CJK and Greek fonts, removes Google Fonts references from CSP and HTML, adds aligned deployment security headers with regression tests, updates security documentation, and refreshes README test and i18n metrics.

Changes

Self-hosted font pipeline

Layer / File(s) Summary
Bundle and load regional fonts
package.json, index.css, index.tsx, index.html
Adds regional @fontsource packages and imports, updates font tokens, removes Google Fonts links, and restricts font/style CSP sources to local content.
Enforce font source contracts
tests/unit/fontPipeline.test.ts
Tests reject live Google Fonts references and stale Noto Sans GR usage, validate CSP sources, and require matching @fontsource imports for UI font families.

Deployment security headers

Layer / File(s) Summary
Configure host security headers
vercel.json, public/_headers, nginx.conf
Adds aligned CSP headers and changes Permissions-Policy to allow same-origin microphone access while keeping camera and geolocation disabled.
Validate header consistency
tests/utils/deploymentConfigParsers.ts, tests/unit/csp.test.ts, tests/unit/deploymentHeaders.test.ts
Parses deployment configurations, compares host CSP values, checks header strictness and frame-ancestors, and validates Permissions-Policy consistency.
Document host invariants
docs/DEPLOYMENT.md, docs/SECURITY-THREAT-MODEL.md, docs/adr/0004-csp-connect-src-byok-tradeoff.md
Documents host-specific header enforcement, CSP precedence, GitHub Pages limitations, and coordinated maintenance requirements.

README project metrics

Layer / File(s) Summary
Refresh documented project metrics
README.md
Updates documented counts to 527 unit-test files and 2854 i18n keys while retaining the 5807+ test count.

Estimated code review effort: 3 (Moderate) | ~25 minutes

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately summarizes the main hardening work: Permissions-Policy, self-hosted fonts, and CSP headers.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deployment-surface-csp-fonts

Comment @coderabbitai help to get the list of available commands.

Comment thread tests/unit/fontPipeline.test.ts Fixed
@socket-security

socket-security Bot commented Jul 28, 2026

Copy link
Copy Markdown

@codeant-ai

codeant-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: 22e4d7c1
Scan Time: 2026-07-28 21:16:43 UTC

βœ… Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets βœ… PASSED 0 secrets found
Duplicate Code βœ… PASSED 0.0% duplicated
SAST βœ… PASSED No security issues
Bugs βœ… PASSED Rating S: No bugs
IAC βœ… PASSED Rating S: No issues

View Full Results

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
tests/unit/fontPipeline.test.ts (1)

113-118: 🎯 Functional Correctness | πŸ”΅ Trivial | ⚑ Quick win

Assert the Greek subset import explicitly.

Noto Sans currently passes with any @fontsource/noto-sans/ import, including a Latin-only stylesheet. Require a greek-* import for this family so removal of Greek coverage fails the regression test.

Proposed fix
+const REQUIRED_IMPORT_PREFIXES: Readonly<Record<string, string>> = {
+  'Noto Sans': "'`@fontsource/noto-sans/greek-`",
+};
+
 // e.g. "Noto Sans JP" -> `@fontsource/noto-sans-jp`; "Noto Sans" -> `@fontsource/noto-sans`
 const pkg = `@fontsource/${family.toLowerCase().replace(/\s+/g, '-')}`;
+const importPrefix = REQUIRED_IMPORT_PREFIXES[family] ?? `'${pkg}/`;
 expect(
-  indexTsx.includes(`'${pkg}/`),
+  indexTsx.includes(importPrefix),
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/fontPipeline.test.ts` around lines 113 - 118, Update the font
import assertion in the relevant font pipeline test so the Noto Sans family
specifically requires an `@fontsource/noto-sans/greek-`* import, rather than
accepting any Noto Sans stylesheet. Preserve the existing generic package
assertion for other families and continue allowing configured system fallbacks.
vercel.json (1)

25-29: πŸ”’ Security & Privacy | πŸ”΅ Trivial | ⚑ Quick win

Security Misconfiguration (CWE-16)

Reachability: External

CSP header addition looks correct; consider closing the Permissions-Policy gap on Vercel.

The added CSP string matches public/_headers and nginx.conf byte-for-byte (as asserted by the new tests/unit/csp.test.ts suite). However, this file still has no Permissions-Policy header at all, while Cloudflare Pages and the Docker/nginx image now both restrict camera=()/geolocation=() and allow microphone=(self). docs/DEPLOYMENT.md's new table documents this as "not set" for Vercel, but it means a Vercel deployment doesn't get the same camera/geolocation hardening as the other two hosts that can set headers.

πŸ›‘οΈ Proposed addition for host parity
         { "key": "X-Frame-Options", "value": "DENY" },
         { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
+        { "key": "Permissions-Policy", "value": "camera=(), microphone=(self), geolocation=()" },
         {
           "key": "Content-Security-Policy",
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vercel.json` around lines 25 - 29, Add a Permissions-Policy header to the
Vercel headers configuration, matching the policy used by Cloudflare Pages and
nginx: disable camera and geolocation while allowing microphone for self. Keep
the existing CSP and other security headers unchanged.
tests/unit/deploymentHeaders.test.ts (1)

1-38: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Duplicate config-parsing helpers across the two new deployment-header test suites. Both files independently implement the same "narrow a regex match, then read public/_headers/nginx.conf and regex-extract a header value" pattern β€” the shared root cause is a missing common test utility for parsing these deployment config files.

  • tests/unit/deploymentHeaders.test.ts#L1-L38: group1, headersPolicyValue(), and nginxPolicyValue() duplicate the existing helpers/pattern from tests/unit/csp.test.ts; import a shared group1 plus generic extractHeaderValue(source, headerName) from a new tests/utils/ module instead.
  • tests/unit/csp.test.ts#L25-L28,102-155: keep group1 here (or move it to the shared module) and have vercelCsp()/headersCsp()/nginxHeaderCsp() reuse the same generic extractor so both suites read from one source of truth.
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/deploymentHeaders.test.ts` around lines 1 - 38, Introduce a shared
tests/utils helper containing group1 and generic extractHeaderValue(source,
headerName), then update tests/unit/deploymentHeaders.test.ts (lines 1-38) to
import and use them for both policy readers. Update tests/unit/csp.test.ts
(lines 25-28 and 102-155) to reuse the same extractor in vercelCsp(),
headersCsp(), and nginxHeaderCsp(), keeping group1 shared or locally only if
appropriate.
πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/DEPLOYMENT.md`:
- Around line 116-130: Correct the CSP explanation in the β€œHeader invariants per
host” section: replace the claim that the header wins and makes the meta CSP
misleading with the accurate behavior that HTTP and meta CSP policies are both
enforced, using the most restrictive applicable rules. Preserve or clarify the
existing directive-specific exception that meta CSP cannot enforce
frame-ancestors and sandbox, which require headers.

In `@index.css`:
- Around line 33-39: The multiline QNBS-v3 comment format must be replaced with
the required single-line format: update the adjacent comment in index.css lines
33-39 to one /* QNBS-v3: … */ comment, and update the adjacent block comment in
index.tsx lines 35-51 to one // QNBS-v3: [Grund / Impact / Kreativer Mehrwert]
comment.

In `@nginx.conf`:
- Around line 30-36: Update the nginx child location blocks for static assets
and sw.js to preserve the server-level security headers when add_header
Cache-Control is present. Either duplicate X-Frame-Options,
X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and
Content-Security-Policy in those blocks, or use add_header_inherit merge when
nginx 1.29.3+ is guaranteed.

In `@README.md`:
- Line 501: Update the README test-directory metric near the unit tree entry to
accurately describe the scope counted by scripts/sync-readme-metrics.mjs: either
identify the tests, components, and package test directories included, or
replace the value with a tests/unit-only count.

---

Nitpick comments:
In `@tests/unit/deploymentHeaders.test.ts`:
- Around line 1-38: Introduce a shared tests/utils helper containing group1 and
generic extractHeaderValue(source, headerName), then update
tests/unit/deploymentHeaders.test.ts (lines 1-38) to import and use them for
both policy readers. Update tests/unit/csp.test.ts (lines 25-28 and 102-155) to
reuse the same extractor in vercelCsp(), headersCsp(), and nginxHeaderCsp(),
keeping group1 shared or locally only if appropriate.

In `@tests/unit/fontPipeline.test.ts`:
- Around line 113-118: Update the font import assertion in the relevant font
pipeline test so the Noto Sans family specifically requires an
`@fontsource/noto-sans/greek-`* import, rather than accepting any Noto Sans
stylesheet. Preserve the existing generic package assertion for other families
and continue allowing configured system fallbacks.

In `@vercel.json`:
- Around line 25-29: Add a Permissions-Policy header to the Vercel headers
configuration, matching the policy used by Cloudflare Pages and nginx: disable
camera and geolocation while allowing microphone for self. Keep the existing CSP
and other security headers unchanged.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ad06f798-5f92-41e1-b664-ded24a15d493

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between f24f41d and 345889b.

β›” Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
πŸ“’ Files selected for processing (14)
  • README.md
  • docs/DEPLOYMENT.md
  • docs/SECURITY-THREAT-MODEL.md
  • docs/adr/0004-csp-connect-src-byok-tradeoff.md
  • index.css
  • index.html
  • index.tsx
  • nginx.conf
  • package.json
  • public/_headers
  • tests/unit/csp.test.ts
  • tests/unit/deploymentHeaders.test.ts
  • tests/unit/fontPipeline.test.ts
  • vercel.json

Comment thread docs/DEPLOYMENT.md
Comment thread index.css Outdated
Comment thread nginx.conf
Comment thread README.md Outdated
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.

πŸ“’ Thoughts on this report? Let us know!

- CodeQL (high severity, "incomplete multi-character sanitization"): make
  tests/unit/fontPipeline.test.ts's stripComments() loop until a fixed point
  instead of a single regex pass, so a comment marker nested inside another
  can't survive removal.
- CodeRabbit: vercel.json had no Permissions-Policy header at all β€” Vercel is
  the primary host, so it never got the microphone=(self) fix from the prior
  commit. Added it, matching Cloudflare Pages/nginx, and extended
  deploymentHeaders.test.ts to assert parity across all three hosts.
- CodeRabbit: nginx's asset-caching and /sw.js location blocks each set their
  own Cache-Control via add_header, which (per nginx's inheritance rule) drops
  every server-level add_header for requests matching them β€” X-Frame-Options,
  CSP, and Permissions-Policy were silently absent on those paths. Duplicated
  the security headers into both blocks.
- CodeRabbit: tightened the fontPipeline "Noto Sans" family check to require
  an actual `greek-` subset import, not just any noto-sans stylesheet.
- CodeRabbit: corrected docs/DEPLOYMENT.md's CSP-precedence claim β€” a header
  and meta CSP are enforced simultaneously (strictest per-directive wins),
  not "the header wins and the meta tag is ignored"; frame-ancestors is the
  one directive that only works as a header at all.
- CodeRabbit: corrected README.md's test-file-count line, which attributed
  the repo-wide count (tests/, components/, packages/*/tests/) to the
  tests/unit/ folder specifically.
- CodeRabbit (nitpick): extracted the duplicated header-parsing helpers from
  csp.test.ts and deploymentHeaders.test.ts into tests/utils/deploymentConfigParsers.ts.
- QNBS-v3 convention: condensed three multi-line comments (index.html,
  index.css, index.tsx) introduced by the previous commit into single lines.

Also found and reverted an unrelated pre-existing bug surfaced while
investigating this: scripts/sync-readme-metrics.mjs sums per-file leaf counts
(2854) while scripts/check-i18n-keys.mjs deduplicates via a Set (2844) β€” 10
keys are defined in both `settings.json` and `common.json`/`dashboard.json`
for the same locale, and diverge in translation for several non-EN locales
(the `es` copies are literally in German). Kept README at the canonical 2844;
the duplicate-key cleanup itself is a separate, larger fix and out of scope
here β€” noted for follow-up.
Comment thread tests/unit/fontPipeline.test.ts Fixed
@qnbs

qnbs commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
βœ… Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/deploymentHeaders.test.ts`:
- Around line 37-50: Update the Permissions-Policy assertions in the microphone
and camera/geolocation tests to parse each policy into directives and compare
the exact values for microphone=(self), camera=(), and geolocation=(). Replace
substring-based toContain checks so broader or duplicate directives cannot pass,
while retaining validation across vercelPolicyValue(), headersPolicyValue(), and
nginxPolicyValue().

In `@tests/utils/deploymentConfigParsers.ts`:
- Around line 14-40: Insert a single-line `// QNBS-v3: ...` rationale comment
immediately before the helper block beginning with `extractHeadersFileValue`,
covering all three shared deployment-config parser helpers. Do not alter the
parsing logic or add additional markers.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a3cef1c-77b1-4738-b60c-d9504e3d1db5

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 345889b and f4c9aa8.

πŸ“’ Files selected for processing (11)
  • README.md
  • docs/DEPLOYMENT.md
  • index.css
  • index.html
  • index.tsx
  • nginx.conf
  • tests/unit/csp.test.ts
  • tests/unit/deploymentHeaders.test.ts
  • tests/unit/fontPipeline.test.ts
  • tests/utils/deploymentConfigParsers.ts
  • vercel.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • vercel.json
  • index.html
  • index.tsx
  • tests/unit/csp.test.ts
  • nginx.conf
  • tests/unit/fontPipeline.test.ts

Comment thread tests/unit/deploymentHeaders.test.ts
Comment thread tests/utils/deploymentConfigParsers.ts
- CodeQL (still flagged after the first do-while fix): split
  stripComments() into two independent stripToFixedPoint() calls, one per
  regex, each shaped exactly like CodeQL's own recommended remediation
  example β€” the previous version chained both replace() calls before
  checking the fixed point, which likely obscured the loop invariant from
  its dataflow analysis for one of the two patterns.
- CodeRabbit: deploymentHeaders.test.ts's Permissions-Policy assertions used
  toContain('microphone=(self)'), which would also pass for a broader
  allowlist (`microphone=(self https://evil.example)`) or a duplicated
  directive where a later, looser value is the one actually in effect. Added
  parsePermissionsPolicy() (directive -> value map, last occurrence wins) to
  the shared test utils and switched to exact per-directive assertions.
- CodeRabbit: added the missing QNBS-v3 marker to
  tests/utils/deploymentConfigParsers.ts, and escaped the header-name
  argument before interpolating it into `new RegExp(...)` β€” it's always a
  fixed string literal at every current call site, never untrusted input,
  but escaping costs nothing and satisfies static analysis that can't verify
  that invariant across call sites on its own.
@qnbs

qnbs commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
βœ… Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@qnbs
qnbs enabled auto-merge (squash) July 28, 2026 21:15
@qnbs
qnbs merged commit 3277de2 into main Jul 28, 2026
25 checks passed
@qnbs
qnbs deleted the fix/deployment-surface-csp-fonts branch July 28, 2026 21:16
qnbs added a commit that referenced this pull request Jul 28, 2026
#282)

Merging PR #278 broke main: the push-triggered CI run's "Attest build
provenance" step failed with "Too many subjects specified (>1024)". This
step never runs on pull_request events (only push to main), so the PR's own
checks looked fully green and the regression only surfaced after merge.

Root cause: PR #278 self-hosts CJK fonts (@fontsource noto-sans-jp/kr/sc) to
fix the broken Google Fonts request. Those packages ship one small .woff2
file per Unicode-range subset for efficient lazy-loading β€” 2278 font files
in dist/ alone (2150 Noto-branded), against 195 everything else, well past
actions/attest-build-provenance's 1024-subject limit.

Fonts are vendored static assets, not application code that needs per-file
SLSA provenance β€” excluding woff/woff2 from subject-path still attests every
JS/CSS/HTML/JSON/wasm file that actually makes up the deployed application.
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.

2 participants