Skip to content

Drop resource-openai-csp to warn: the key was not observed to change enforcement - #102

Merged
mgoldsborough merged 2 commits into
mainfrom
fix/csp-check-severity
Sep 18, 2026
Merged

mgoldsborough merged 2 commits into
mainfrom
fix/csp-check-severity

Conversation

@mgoldsborough

@mgoldsborough mgoldsborough commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What

resource-openai-csp goes error → warn for the chatgpt target, and the prose that
asserted 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.csp alone got no
policy, concluding the key ChatGPT reads is openai/widgetCSP. That observation
established 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:

  • the widget loads into a nested frame inside a generic sandbox shell
  • that shell carries a <meta http-equiv="Content-Security-Policy"> holding a boilerplate
    allowlist (jsdelivr, tailwind, esm.sh, unpkg, pypi, threejs.org, Azure blobs, data:) —
    identical for every app
  • the frame holding the component has no csp attribute and no meta CSP of its own, so
    it inherits that default
  • resource _meta carried openai/widgetCSP with snake_case origin lists on both
    resources/list and the resources/read content _meta, and openai/widgetDomain was
    demonstrably 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.ts already states it:

error fails the run. warn is reported and does not: it marks a requirement the host is
documented to have but is not known to enforce, so a failure is a risk rather than a
certain break.

Neither dialect is known to be enforced, so neither clears the bar for error.
resource-openai-csp joins resource-csp at warn.

What does not change

Both keys are still emitted and still checked. OpenAI documents openai/widgetCSP, and it
remains the only way to declare redirect_domains. The camelCase-spelling check shares the check id, so it keeps its own message and
warns too. The only change is that a --target chatgpt run no longer fails on the
key — so servers emitting ui.csp alone pass again, which 0.21.0 broke.

Left unmeasured, deliberately

Two explanations fit the observation and neither is asserted:

  1. a custom policy is honoured only for reviewed/published apps, not developer mode
  2. a bare scheme-source such as 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 Python
comments 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 failed is now false either way.

…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.
@mgoldsborough

Copy link
Copy Markdown
Contributor Author

QA Review: fix/csp-check-severity

Scope: 6 files, +35/−16 · reviewed: ef50a13
Risk: LOW
Why this exists: 0.21.0 made resource-openai-csp an error for --target chatgpt, which fails every server on nimblebrain-synapse 0.7.0. The measurement it rested on has since been shown to be the host's default policy, so this drops the check to warn.

Critical (must fix)

None. The core change (profiles.ts severity, plus a test that asserts severity directly because failed is false either way) is correct and minimal. npm run ci passes: 566 tests, and lint, typecheck and size are clean.

Fix in-PR (apply mechanically — no adjudication essay expected)

  • r1f1 [web/src/content/docs/docs/api/cli.mdx:75,84-90] The published CLI reference still lists resource-openai-csp as error for chatgpt. The paragraph under the table still says "the key it read is the sibling openai/widgetCSP. So that check is an error for chatgpt". This is the doc for the one row this PR changes, and it now contradicts the tool's output. Change the table cell to warn and rewrite the paragraph to match the corrected CHANGELOG entry. The file is outside the diff but has to move with the severity change.

  • r1f2 [src/check/profiles.ts:107] The why string says a resource carrying openai/widgetCSP "alone or beside ui.csp" produced no enforced policy. The body reports only two measurements: ui.csp alone (09-17) and both dialects (09-18). The openai/widgetCSP-alone case was never measured, and this string is printed to users on every warn. Drop "alone or".

  • r1f3 Debris sweep. Several places still state the refuted premise that ChatGPT reads openai/widgetCSP, or that leaving it out means "no policy":

    • src/check/index.ts:515-519: "a target's profile names the one its host was observed to read"
    • src/check/index.ts:369: the detail message "which ChatGPT does not read"
    • python/nimblebrain_synapse/server.py:406-411,422: "the policy it was observed to read is openai/widgetCSP" and "observed default without openai/widgetCSP is no policy"
    • python/tests/test_synapse_ui.py:243-245,265-267: the same premise, in docstrings

    All of these are comments or message strings, so no Python release is needed. Find them with grep -rn "observed to read\|does not read\|no policy" src python/nimblebrain_synapse python/tests. Note that python/nimblebrain_synapse/server.py's _chatgpt_resource_aliases gives the refuted premise as its reason for always emitting the alias. Replace it with the reason that survives: the key is documented, and it is the only home for redirect_domains.

  • r1f4 Refresh the body. It says "The camelCase-spelling check keeps its own failure mode." The camelCase check is the same check id (resource-openai-csp, src/check/index.ts:366-370), so it now warns and no longer fails a chatgpt run. It keeps its own detail message, not its own severity. That is consistent with the rationale, so say it.

Suggestions (optional)

  • r1s1 [CLAUDE.md:188] "which is what the CSP off badge reports" ties the badge to the default sandbox policy. The earlier note in server.py saw the badge in the ui.csp-alone case. If the badge was not also seen in the both-dialects run, soften this to what was actually observed.

What Looks Good

  • The rule in profiles.ts ("warn = documented, not known to enforce") applies consistently, and resource-openai-csp now sits beside resource-csp.
  • The test comment explains why failed alone cannot tell error from warn here. Checking severity directly is the right assertion.
  • The changelog leaves both open explanations unasserted instead of picking one.
  • The version bump is paired correctly: package.json and __client_version__ are both 0.21.1.

Body claims

  • ✅ npm run ci — 566 passed, lint/typecheck/size clean (reproduced at ef50a13).
  • ✅ Servers emitting ui.csp alone pass --target chatgpt again: the only error-level check on the key is gone.
  • ❌ "The camelCase-spelling check keeps its own failure mode": it shares the id, so it drops to warn too (r1f4).
  • ⚠️ The Python run (37 passed) was not re-run here. The Python source is unchanged apart from __client_version__.

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.
@mgoldsborough

Copy link
Copy Markdown
Contributor Author

Adjudication round 1: all Fix-in-PR items applied, no Criticals · fixed at 6e50f23

Fix in-PR

  • r1f1–r1f3 applied in 6e50f23. The r1f3 sweep also covered the same premise at src/__tests__/check/check.test.ts:114 and CLAUDE.md:193 ("a camelCase alias is ignored exactly as a missing key is").
  • r1f4: the body is refreshed.
  • r1s1: declined.

Verification

CI 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"
}

@mgoldsborough mgoldsborough added the qa-reviewed QA review completed with no critical issues label Sep 18, 2026
@mgoldsborough
mgoldsborough merged commit 77d1163 into main Sep 18, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa-reviewed QA review completed with no critical issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant