Make the sandbox frame actually run, and survive a reload - #17
Merged
Conversation
Two things, both about HTML relics rendering blank. The sandbox frame is given no allow-same-origin, which puts its document in an opaque origin. Every request that document makes is therefore cross-origin with Origin: null, and sandbox.html linked its handler as a type="module" script. Module scripts are fetched with CORS semantics, the asset carried no Access-Control-Allow-Origin, so the browser refused it. The script never ran, the frame never announced itself, and the parent posted markup to a listener that did not exist. It failed with an empty console, because the failure was in the frame's origin rather than the page's. Verified against the deployed sandbox: the frame never sends relic:sandbox-ready. The bundle is now inlined into the page at build time. Relaxing CORS on the asset would have fixed the symptom by making the sandbox origin serve something cross-origin, which is the property the sandbox exists to remove. An inline script fetches nothing, so there is no request to block and no header anybody can regress. The build refuses to produce a linked version, and the deploy asserts the served page carries its handler and links no script. Second, a reload lost the key. Reading the fragment strips it from the address bar, which is worth keeping, but it meant a refresh landed on a dead page telling the reader to go find the original link. The key is now remembered in local storage on the service origin, keyed by relic, until the relic expires, and dropped as soon as the relic is gone or the key stops working. Storage that is absent or refuses to write degrades to the old behaviour rather than costing somebody the relic they are looking at. That changes who can open a relic: anyone using that browser profile can reopen it without ever being sent the link. The disclosure says so rather than leaving it to be discovered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAZyikoYbH8XdXkGJrxFCv
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.
Two things, both surfacing as a blank frame on an HTML relic.
1. The sandbox script was never running
The frame is given
sandbox="allow-scripts allow-popups allow-forms"with noallow-same-origin, which is the point: its document lands in an opaqueorigin. Every request that document makes is therefore cross-origin with
Origin: null.sandbox.htmllinked its handler as<script type="module" src="/assets/sandbox.js">.Module scripts are fetched with CORS semantics. The asset carried no
Access-Control-Allow-Origin, so the browser refused it.Verified against the deployed sandbox rather than inferred: with a listener
attached in the parent and the frame reloaded,
relic:sandbox-readyneverarrives. The console is empty, because the failure lives in the frame's origin
and not the page's.
Fix: inline the bundle into the page at build time. Relaxing CORS on the
asset would fix the symptom by making the sandbox origin serve something
cross-origin, which is precisely the property the sandbox exists to remove. An
inline script fetches nothing, so there is no request to block and no header
anybody can regress later. The sandbox page's CSP has no
script-src, soinline runs.
Guarded at both ends: the build fails if the expected tag is missing (which
would silently ship the linked version), and the deploy asserts the served page
contains the handler and links no script.
2. A reload lost the key
Reading the fragment strips it from the address bar. That is worth keeping, but
it meant a refresh landed on a dead page telling the reader to go find the
original link.
The key is now remembered in local storage on the service origin, keyed by
relic, until the relic expires. It is written only after a successful mint, so
a key for a relic that does not exist is never stored; dropped when the relic
turns out to be expired, removed, or never published; and dropped when a
remembered key fails to parse, so corrupt storage cannot wedge the page.
Storage that is absent or throws — private browsing, quota, embedded webview,
blocked site data — degrades to doing nothing, which is exactly the behaviour
that existed before. Reads sweep every expired entry, not just the one asked
for, so storage does not accumulate keys to relics that stopped existing.
This changes who can open a relic. For as long as an entry lives, anyone
using that browser profile can reopen it without ever having been sent the
link.
/policynow states that, including that clearing site data removesthem.
Verification
alone, and both degradation paths
remembered key and that a corrupt one is dropped rather than retried
sandbox.htmlconfirmed to contain the handler inline and nosrc=