Skip to content

Fix the blank desktop window: CSP was refusing the export's inline payload - #10

Merged
ralyodio merged 1 commit into
mainfrom
fix/csp-blocks-inline-payload
Aug 30, 2026
Merged

Fix the blank desktop window: CSP was refusing the export's inline payload#10
ralyodio merged 1 commit into
mainfrom
fix/csp-blocks-inline-payload

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

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 was script-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:

v0.2.1 this PR
CSP violations 7 (Refused to execute inline script) 0
visible text 0 chars full UI
DOM nodes in body 8 236
React hydrated no yes

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:desktop verified 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, ignoring src ones, tolerating extra attributes, dedupe, and asserting script-src never gains 'unsafe-inline'). Full suite: 266 passing, typecheck clean, all three smoke guards green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CBc6KV8dL7oLS5okq15B4W

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
@ralyodio
ralyodio merged commit 228a672 into main Aug 30, 2026
4 checks 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.

1 participant