Fix the blank desktop window: CSP was refusing the export's inline payload - #10
Merged
Conversation
v0.2.1 fixed the assets and uncovered the next layer. A Next export carries its Flight payload in inline <script> tags, and the window's policy was script-src 'self' with no allowance for them. Before v0.2.1 that was invisible: the chunks 404ed, so React never ran and the raw prerendered HTML showed. Once the chunks loaded, React booted with its payload refused seven times over and rendered nothing — a blank window. The hashes go in the policy rather than 'unsafe-inline', which would admit any injected script, and rather than a nonce, which cannot work for HTML that is a file on disk and not regenerated per load. The policy is now built from the export the app actually serves and carried on the response itself, so the document is governed by it whether or not a webRequest listener is attached. Verified in headless Chromium under the real policy: 7 violations, 0 visible characters and no hydration before; 0 violations and the full dual-pane UI after, with the preload bridge mocked. smoke:desktop now checks that the policy admits every inline script. Assets loading and the page rendering are different facts, and the existing checks only covered the first — which is precisely how this shipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CBc6KV8dL7oLS5okq15B4W
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The v0.2.1 asset fix worked, and uncovered the next layer. The window is now blank.
Why
A Next static export carries its Flight payload in inline
<script>tags (self.__next_f.push(...)). The window's policy wasscript-src 'self'— no allowance for them.Before v0.2.1 this was invisible: the chunks 404'd, React never ran, and you saw the raw prerendered HTML (unstyled, but visible). Once the chunks loaded, React booted with its payload refused seven times over and rendered nothing.
Reproduced deterministically by serving the real export through the real path resolution with the real policy:
Refused to execute inline script)With the preload bridge mocked, the complete dual-pane UI renders — file lists, sync controls, status bar.
The fix
The inline scripts are hashed into
script-src. Not'unsafe-inline', which would admit any injected script; not a nonce, which cannot work for HTML that is a file on disk and never regenerated per load.The policy is built from the export the app actually serves, and is now carried on the served response itself as well as via
onHeadersReceived, so the document is governed by it either way.Why it got through
smoke:desktopverified that assets resolve. Assets loading and the page rendering are different facts, and my earlier verification harness sent no CSP at all — so it could never see this. The smoke test now also checks that the policy admits every inline script in the export, and I confirmed that guard fails when the hashes are removed.Tests
8 new tests in
csp.test.ts(hashing inline scripts, ignoringsrcones, tolerating extra attributes, dedupe, and assertingscript-srcnever gains'unsafe-inline'). Full suite: 266 passing, typecheck clean, all three smoke guards green.🤖 Generated with Claude Code
https://claude.ai/code/session_01CBc6KV8dL7oLS5okq15B4W