[PR #6963] [MERGED] fix(browser): parse repeated CSP and policy header field lines separately #7476

Closed
opened 2026-11-01 21:48:07 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6963
Author: @Adolanium
Created: 10/24/2026
Status: ✅ Merged
Merged: 10/24/2026
Merged by: @morluto

Base: main ← Head: fix/repeated-csp-header-fields


📝 Commits (1)

  • 464eb3f fix(browser): parse repeated CSP and policy header field lines separately

📊 Changes

3 files changed (+75 additions, -20 deletions)

View changed files

📝 docs/browser-observation.md (+6 -2)
📝 src/browser/CdpSafeMetadata.test.ts (+34 -0)
📝 src/browser/CdpSafeMetadata.ts (+35 -18)

📄 Description

Summary

Fixes #6964.

CDP reports a response header that appears on several field lines as one value joined with LF. The Link projection already parses each line separately, but the CSP and single-valued policy projections did not: a repeated Content-Security-Policy lost the first directive of each later policy, and a repeated Referrer-Policy was reported as null. Header normalization now keeps each field line, so every projection reads the same lines.

Problem and expected behavior

With Content-Security-Policy: default-src 'self' and a second field line script-src https://cdn.example; img-src data:, CDP delivers "default-src 'self'\nscript-src https://cdn.example; img-src data:". parseCsp split only on ;, so the first chunk became one default-src directive whose sources included script-src, and the script-src directive was missing. CSP3 also allows several comma-separated policies on one line; those were merged the same way.

Referrer-Policy: no-referrer followed by Referrer-Policy: strict-origin arrived as "no-referrer\nstrict-origin", which failed the token check and reported no policy.

Expected: every directive of every policy is listed, and repeated single-valued fields combine as one comma-separated value, as RFC 9110 §5.3 specifies, so Referrer-Policy reports strict-origin.

Change and scope

  • src/browser/CdpSafeMetadata.ts:
    • normalizedHeaders keeps the field lines of each header (split on \r?\n, merged across differently cased names) instead of one joined string.
    • parseCsp reads every line and each comma-separated policy before splitting directives on ;.
    • Single-valued fields (Content-Length, Content-Encoding, COOP, COEP, CORP, Referrer-Policy, X-Content-Type-Options, Permissions-Policy) combine their lines with , . Their existing parsers then apply unchanged: a repeated COOP value is still rejected, and Referrer-Policy keeps its last recognized token.
    • parseLinks takes the shared lines; its own LF split is removed.
  • docs/browser-observation.md states the rule next to the existing Link rule, including that csp.directives does not record policy boundaries.

Not included: the response schema is unchanged, so directives from different policies stay in one ordered list.

Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.

Contribution scope: not applicable. This is a fix inside the existing browser response-metadata projection.

Contract and boundary impact

  • Semantic owner and earliest changed stage: normalizedHeaders in CdpSafeMetadata.ts, where CDP response headers enter the projection.
  • Contribution scope fit and linked maintainer decision: not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. csp.directives can now contain a directive name more than once, one per policy.
  • Provider, bridge, target-format, or platform compatibility: CDP header shape as Chrome reports it.
  • Evidence, artifact, provenance, or reconstruction contract: more complete CSP and policy facts; raw headers are still discarded.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/browser/CdpSafeMetadata.test.ts:
    • "parses every policy of a repeated CSP field joined by %j", for \n, \r\n and , : two policies yield default-src, script-src and img-src with their sources.
    • Two new rows in "normalizes declared Referrer-Policy %s": no-referrer\nstrict-origin gives strict-origin, and no-referrer\r\nfuture-policy gives no-referrer.
    • The existing Link, COOP/COEP (including same-origin\nrequire-corp → null) and referrer cases pass unchanged.
  • Base reproduction or other evidence: with src/browser/CdpSafeMetadata.ts from upstream/main d87e73f2d and the new tests, all five new cases fail (the three CSP delimiters and both Referrer-Policy rows). With this change, they pass.
  • User-visible CLI/MCP output (if applicable): csp.directives and policies.referrer_policy in browser response metadata.
  • Remaining proof gaps: no real-browser capture with repeated headers; the tests drive safeResponseMetadata with CDP-shaped values.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

On Linux (WSL Ubuntu, Node 24.22.1) at 464eb3ff8 (on upstream/main d87e73f2d), after npm ci and npm run build:cached:

  • npx tsc -p . --noEmit passed.
  • npx oxfmt --check on the changed files: clean. npx oxlint on the changed source and test files: 0 warnings (1 on the base).
  • npm run docs:check passed.
  • npx vitest run src/browser tests/boundary/browser: 2105 passed, 4 skipped, in two runs.
  • The negative check above failed the five new cases.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Consumers that assumed unique directive names in csp.directives may see repeats.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux only; no real browser.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: no new retained data; raw headers are still discarded.

Author readiness

Human owner: @Adolanium

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/6963 **Author:** [@Adolanium](https://github.com/Adolanium) **Created:** 10/24/2026 **Status:** ✅ Merged **Merged:** 10/24/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/repeated-csp-header-fields` --- ### 📝 Commits (1) - [`464eb3f`](https://github.com/morluto/rea/commit/464eb3ff85c80dafc5bb82cc2a5b7d5c6c20b77a) fix(browser): parse repeated CSP and policy header field lines separately ### 📊 Changes **3 files changed** (+75 additions, -20 deletions) <details> <summary>View changed files</summary> 📝 `docs/browser-observation.md` (+6 -2) 📝 `src/browser/CdpSafeMetadata.test.ts` (+34 -0) 📝 `src/browser/CdpSafeMetadata.ts` (+35 -18) </details> ### 📄 Description ## Summary Fixes #6964. CDP reports a response header that appears on several field lines as one value joined with LF. The Link projection already parses each line separately, but the CSP and single-valued policy projections did not: a repeated `Content-Security-Policy` lost the first directive of each later policy, and a repeated `Referrer-Policy` was reported as `null`. Header normalization now keeps each field line, so every projection reads the same lines. ## Problem and expected behavior With `Content-Security-Policy: default-src 'self'` and a second field line `script-src https://cdn.example; img-src data:`, CDP delivers `"default-src 'self'\nscript-src https://cdn.example; img-src data:"`. `parseCsp` split only on `;`, so the first chunk became one `default-src` directive whose sources included `script-src`, and the `script-src` directive was missing. CSP3 also allows several comma-separated policies on one line; those were merged the same way. `Referrer-Policy: no-referrer` followed by `Referrer-Policy: strict-origin` arrived as `"no-referrer\nstrict-origin"`, which failed the token check and reported no policy. Expected: every directive of every policy is listed, and repeated single-valued fields combine as one comma-separated value, as RFC 9110 §5.3 specifies, so `Referrer-Policy` reports `strict-origin`. ## Change and scope - `src/browser/CdpSafeMetadata.ts`: - `normalizedHeaders` keeps the field lines of each header (split on `\r?\n`, merged across differently cased names) instead of one joined string. - `parseCsp` reads every line and each comma-separated policy before splitting directives on `;`. - Single-valued fields (`Content-Length`, `Content-Encoding`, COOP, COEP, CORP, `Referrer-Policy`, `X-Content-Type-Options`, `Permissions-Policy`) combine their lines with `, `. Their existing parsers then apply unchanged: a repeated COOP value is still rejected, and `Referrer-Policy` keeps its last recognized token. - `parseLinks` takes the shared lines; its own LF split is removed. - `docs/browser-observation.md` states the rule next to the existing Link rule, including that `csp.directives` does not record policy boundaries. Not included: the response schema is unchanged, so directives from different policies stay in one ordered list. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Unresolved scope, authority, side effects, or required verification block integration. Link the required maintainer scope decision for new providers or capabilities involving target execution, injection, mutation, or protection recovery; ordinary fixes within existing boundaries may state "not applicable". Draft PRs may leave the scope decision pending and the readiness and merge checklists unchecked. Maintainers can help resolve open questions. Contribution scope: not applicable. This is a fix inside the existing browser response-metadata projection. ## Contract and boundary impact - Semantic owner and earliest changed stage: `normalizedHeaders` in `CdpSafeMetadata.ts`, where CDP response headers enter the projection. - Contribution scope fit and linked maintainer decision: not applicable. - CLI and MCP/tool-catalog contract: no schema change. `csp.directives` can now contain a directive name more than once, one per policy. - Provider, bridge, target-format, or platform compatibility: CDP header shape as Chrome reports it. - Evidence, artifact, provenance, or reconstruction contract: more complete CSP and policy facts; raw headers are still discarded. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/browser/CdpSafeMetadata.test.ts`: - "parses every policy of a repeated CSP field joined by %j", for `\n`, `\r\n` and `, `: two policies yield `default-src`, `script-src` and `img-src` with their sources. - Two new rows in "normalizes declared Referrer-Policy %s": `no-referrer\nstrict-origin` gives `strict-origin`, and `no-referrer\r\nfuture-policy` gives `no-referrer`. - The existing Link, COOP/COEP (including `same-origin\nrequire-corp` → `null`) and referrer cases pass unchanged. - Base reproduction or other evidence: with `src/browser/CdpSafeMetadata.ts` from upstream/main d87e73f2d and the new tests, all five new cases fail (the three CSP delimiters and both Referrer-Policy rows). With this change, they pass. - User-visible CLI/MCP output (if applicable): `csp.directives` and `policies.referrer_policy` in browser response metadata. - Remaining proof gaps: no real-browser capture with repeated headers; the tests drive `safeResponseMetadata` with CDP-shaped values. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed On Linux (WSL Ubuntu, Node 24.22.1) at 464eb3ff8 (on upstream/main d87e73f2d), after `npm ci` and `npm run build:cached`: - `npx tsc -p . --noEmit` passed. - `npx oxfmt --check` on the changed files: clean. `npx oxlint` on the changed source and test files: 0 warnings (1 on the base). - `npm run docs:check` passed. - `npx vitest run src/browser tests/boundary/browser`: 2105 passed, 4 skipped, in two runs. - The negative check above failed the five new cases. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Consumers that assumed unique directive names in `csp.directives` may see repeats. - Real Hopper/Ghidra, browser, or OS coverage: Linux only; no real browser. - Package or release metadata impact: none. - Security, privacy, process, or containment review: no new retained data; raw headers are still discarded. ## Author readiness <!-- The human owner completes these declarations before requesting review. The PR author is the owner by default. A different owner must explicitly accept ownership on this PR. AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA. Draft PRs may leave them unchecked. These declarations record initial readiness when opening the PR for review; later pushes do not require a new human sign-off. Keep the description and validation evidence current as the work changes. See CONTRIBUTING.md#pull-request-ownership-and-readiness. --> Human owner: @Adolanium - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge <!-- Draft PRs may leave pending items unchecked. Resolve material scope and authority questions before integration. --> - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:48:07 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7476
No description provided.