Skip to content

1.5.1: declare the widget CSP in ChatGPT's own key - #35

Merged
samson-art merged 1 commit into
mainfrom
fix/1.5.1-chatgpt-widget-csp
Sep 19, 2026
Merged

samson-art merged 1 commit into
mainfrom
fix/1.5.1-chatgpt-widget-csp

Conversation

@samson-art

Copy link
Copy Markdown
Owner

Summary

ChatGPT reads the CSP of a widget only from _meta["openai/widgetCSP"]. Our widget resources declared only the standard _meta.ui.csp. This PR adds the ChatGPT key, with the same 15 domains, to all four widget resources.

The problem

In ChatGPT, the transcript widget for an Instagram reel showed the ▶ placeholder instead of the thumbnail. Claude reads ui.csp, and in Claude the same thumbnail loads.

Two investigations read the real host code and reproduced both hosts in headless Chrome:

  • Claude copies ui.csp.resourceDomains into img-src as written, wildcards included. The Instagram thumbnail loads.
  • ChatGPT takes the CSP from its backend (widgetResponse.csp) or from chatgpt_sdk.csp, in the snake_case form of openai/widgetCSP. A declared CSP is "trusted": the sandbox copies each entry, wildcards included, into img-src.
  • If ChatGPT finds no CSP:
    • a published app gets a strict default: img-src 'self' data: plus a fixed OpenAI list. This default blocks every external thumbnail, i.ytimg.com included.
    • a developer-mode app (ONLY_ME) gets no policy at all, unless the account setting "enforce CSP in dev mode" is on.
  • NimbleBrain measured on 2026-09-17 that ChatGPT gives a resource with ui.csp alone no policy (NimbleBrainInc/synapse#100). As a result, the published Transcriptor app most probably shows no thumbnails for any platform today.

Changes

  • One constant, WIDGET_CSP_META, holds both keys. The four resources spread it, so the two lists cannot drift.
  • connect_domains is empty. The widgets fetch nothing themselves. They reach the server through the host.
  • redirect_domains is not set. ChatGPT appends ?redirectUrl= to links on the domains in that list, so the "Open" links to Instagram and YouTube would change.
  • A test makes sure that each resource carries both keys with the same domain list. Without the fix, the test fails.
  • CHANGELOG: a 1.5.1 section. The two track-name fixes from Accept the track names yt-dlp lists, not only short language codes #34 move from [Unreleased] into 1.5.0, because they shipped in 1.5.0. The date of 1.5.0 is now the date of its tag.

What this PR does not settle

The orange "CSP off" badge in the screenshot does not describe the widget. ChatGPT shows it for every developer-mode app when "enforce CSP" is off. In that mode, with no CSP, the reproduction loads the Instagram thumbnail. So in the developer-mode test, something other than the CSP probably blocked the image: for example a browser extension that blocks cdninstagram.com. The console message of the failed image tells which:

  • "violates the following Content Security Policy directive": the CSP.
  • net::ERR_BLOCKED_BY_CLIENT: an extension.
  • A 403 in the Network tab: the URL.

Side effects

  • In developer mode, the widgets move from no policy to an enforced policy: connect-src 'self' plus the OpenAI list, and frame-src 'none'. The four built widgets started with no CSP violations under this policy.
  • ChatGPT caches widget templates by resource URI. After the deploy, refresh the connector in ChatGPT. The ui:// URIs do not change in this PR, because a new URI changes the tool metadata of the published app.
  • The published app can need a new submitted version before the directory picks up the new CSP.

Checks

  • make check-no-smoke passes: 12 suites, 320 tests.
  • Without the fix, the new test fails.

After the merge

  1. Take a snapshot of the production logs.
  2. Make sure that the merge commit contains the last commit of this PR.
  3. Put the v1.5.1 tag on the merge commit, and wait for publish-docker and Watchtower.
  4. Read resources/read inside the container, and make sure that each widget resource has openai/widgetCSP.
  5. In ChatGPT, refresh the connector, and open the reel and a YouTube video.

🤖 Generated with Claude Code

In ChatGPT an Instagram reel's widget showed the ▶ placeholder instead
of its thumbnail, under an orange "CSP off" badge. ChatGPT reads a
widget's policy only from openai/widgetCSP (snake_case connect_domains /
resource_domains); a resource carrying only the standard ui.csp, as ours
did, gets no policy there. Claude reads ui.csp and shows the thumbnail.
NimbleBrain measured the same on 2026-09-17 (NimbleBrainInc/synapse#100).

Every widget resource now carries both keys from one constant, so the
two lists cannot drift. connect_domains is empty: the widgets fetch
nothing and reach the server through the host. redirect_domains is left
out, since ChatGPT appends ?redirectUrl= to links on the domains it lists.

The changelog also moves the two track-name fixes from Unreleased into
1.5.0, where they shipped, and dates 1.5.0 by its tag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@samson-art
samson-art merged commit 86abe3e into main Sep 19, 2026
1 check passed
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