Drop resource-openai-csp to warn: the key was not observed to change enforcement - #102
Conversation
…enforcement 0.21.0 made `resource-openai-csp` an error on the premise that ChatGPT reads the frame's policy from `openai/widgetCSP`. That was inferred from one observation — a frame whose resource carried `ui.csp` alone got no policy — which identified the absence of a policy but not the reason for it. A server emitting both dialects has now been measured (2026-09-18, developer mode, over CDP against the live frame). The component still runs under ChatGPT's own default sandbox CSP: a boilerplate allowlist carried as a meta Content-Security-Policy on the sandbox shell and inherited by the frame the component is injected into, with no per-widget policy applied and no `csp` attribute on that frame. Both readings were the host's default, so neither says which key it reads. profiles.ts states the rule this turns on: `error` is for a break, `warn` for a requirement documented but not known to be enforced. Neither dialect clears that bar, so `resource-openai-csp` joins `resource-csp` at warn. Both keys keep being emitted — OpenAI documents `openai/widgetCSP`, and it is still the only way to declare `redirect_domains`. What changes is that a run no longer fails on it, so servers emitting `ui.csp` alone pass `--target chatgpt` again. Whether a custom policy is honoured at all for an unreviewed app, and whether a bare scheme-source like `data:` survives the vendor's origin-list validation, are both unmeasured. Either would explain the observation; the changelog says so rather than picking one. Patch, not minor: this only relaxes a gate.
QA Review: fix/csp-check-severityScope: 6 files, +35/−16 · reviewed: ef50a13 Critical (must fix)None. The core change ( Fix in-PR (apply mechanically — no adjudication essay expected)
Suggestions (optional)
What Looks Good
Body claims
Next step: the author answers this round with the pr-adjudicate skill. machine record{
"kind": "review", "pr": 102, "round": 1,
"reviewed": "ef50a13", "delta_base": null,
"risk": "LOW", "stop_gate": "n/a",
"verdict": "Core severity change is correct; docs table and premise debris must move with it.",
"findings": [
{"id": "r1f1", "bucket": "fix-in-pr", "file": "web/src/content/docs/docs/api/cli.mdx", "line": 75,
"claim": "Published CLI reference still lists resource-openai-csp as error for chatgpt and restates the refuted premise.",
"evidence": "reproduced", "receipt": "sed -n 70,90p cli.mdx: row shows '— | — | error'; paragraph 'So that check is an error for chatgpt'",
"preconditions": null, "fix": "table cell -> warn; rewrite paragraph to the corrected premise", "scope": "core"},
{"id": "r1f2", "bucket": "fix-in-pr", "file": "src/check/profiles.ts", "line": 107,
"claim": "why-string asserts an unmeasured case (openai/widgetCSP alone).",
"evidence": "reproduced", "receipt": "PR body lists only ui.csp-alone (09-17) and both-dialects (09-18) measurements",
"preconditions": null, "fix": "drop 'alone or'", "scope": "core"},
{"id": "r1f3", "bucket": "fix-in-pr", "file": "src/check/index.ts", "line": 519,
"claim": "Comments/messages in src/check/index.ts, python server.py and python tests still assert ChatGPT reads openai/widgetCSP / omission means no policy.",
"evidence": "reproduced", "receipt": "grep -rn '2026-09-17\\|no policy' → index.ts:519, server.py:407,422, test_synapse_ui.py:243,267; index.ts:369 'which ChatGPT does not read'",
"preconditions": null, "fix": "one sweep restating the surviving rationale", "scope": "peripheral"},
{"id": "r1f4", "bucket": "fix-in-pr", "file": "src/check/index.ts", "line": 366,
"claim": "Body says the camelCase check keeps its own failure mode; it shares the resource-openai-csp id and now warns.",
"evidence": "reproduced", "receipt": "index.ts:366-370 pushes into noOpenAiCsp under resource-openai-csp",
"preconditions": null, "fix": "refresh the body", "scope": "peripheral"},
{"id": "r1s1", "bucket": "suggestion", "file": "CLAUDE.md", "line": 188,
"claim": "'CSP off' badge attribution may exceed what was observed in the both-dialects run.",
"evidence": "reasoned", "receipt": null, "preconditions": null,
"fix": "state only what was observed", "scope": "peripheral"}
]
} |
…ed one was left The CLI reference table and paragraph now match the warn severity, the check's message no longer claims ChatGPT ignores camelCase keys, and the Python comments and docstrings drop the premise that the key was observed to be read.
Adjudication round 1: all Fix-in-PR items applied, no Criticals · fixed at 6e50f23Fix in-PR
VerificationCI run 35292571609 is green: build, lint, typecheck, test on Node 22 and 24, conformance, Python, and the docs site build. The Python package changes are comments and docstrings only, so it needs no release. machine record{
"kind": "adjudication", "pr": 102, "round": 1, "fixed_at": "6e50f23",
"verdicts": [
{"id": "r1f1", "verdict": "valid", "premise": "verified", "resolution": "cli.mdx table cell -> warn, paragraph rewritten, 6e50f23"},
{"id": "r1f2", "verdict": "valid", "premise": "verified", "resolution": "dropped 'alone or', 6e50f23"},
{"id": "r1f3", "verdict": "valid", "premise": "verified", "resolution": "comment/message/docstring sweep incl. check.test.ts:114 and CLAUDE.md:193, 6e50f23"},
{"id": "r1f4", "verdict": "valid", "premise": "verified", "resolution": "body refreshed"},
{"id": "r1s1", "verdict": "valid", "premise": "accepted", "resolution": "declined"}
],
"ci": "green, run 35292571609"
} |
What
resource-openai-cspgoes error → warn for thechatgpttarget, and the prose thatasserted the premise behind it is corrected. Patch release 0.21.1.
Why
0.21.0 made this an error because a frame whose resource carried
ui.cspalone got nopolicy, concluding the key ChatGPT reads is
openai/widgetCSP. That observationestablished that no policy was applied, not why.
A server emitting both dialects has now been measured — 2026-09-18, developer mode,
over CDP against the live frame:
<meta http-equiv="Content-Security-Policy">holding a boilerplateallowlist (jsdelivr, tailwind, esm.sh, unpkg, pypi, threejs.org, Azure blobs,
data:) —identical for every app
cspattribute and no meta CSP of its own, soit inherits that default
_metacarriedopenai/widgetCSPwith snake_case origin lists on bothresources/listand theresources/readcontent_meta, andopenai/widgetDomainwasdemonstrably consumed (the host derives the sandbox subdomain from it)
So the component ran under the host's default policy in both readings. The earlier result
was that default, not evidence about which key is read.
The rule this turns on
profiles.tsalready states it:Neither dialect is known to be enforced, so neither clears the bar for
error.resource-openai-cspjoinsresource-cspatwarn.What does not change
Both keys are still emitted and still checked. OpenAI documents
openai/widgetCSP, and itremains the only way to declare
redirect_domains. The camelCase-spelling check shares the check id, so it keeps its own message andwarns too. The only change is that a
--target chatgptrun no longer fails on thekey — so servers emitting
ui.cspalone pass again, which 0.21.0 broke.Left unmeasured, deliberately
Two explanations fit the observation and neither is asserted:
data:is rejected by the vendor's origin-list validation,discarding the policy
The changelog records both as open rather than picking one.
Also
The CLI reference (
web/.../api/cli.mdx), the check's comments and message, and the Pythoncomments and docstrings no longer state the refuted premise.
Test
npm run ci— 566 passed, lint/typecheck/size clean.python -m pytest— 37 passed.The severity is asserted directly in the test, since
failedis now false either way.