Repository navigation
feat(embed): package an opaque bootstrap for bundle sources - #54
Merged
Merged
Conversation
MCP hosts can load bundle apps in data frames by omitting bootstrapUrl. Share the restrictive runtime HTML with the Worker and retain source-owned sandbox, session, cancellation, timeout, and presentation behavior. Generate browser SDK identifiers with crypto.getRandomValues when opaque documents omit crypto.randomUUID. Keep randomness cryptographic and fail closed when it is unavailable. Verify opaque-frame queries, cancellation, message isolation, presentation updates, runtime recovery, and UUID fallback through public APIs. bun run check passes all 135 tests plus package and starter checks.
Host bundlers cannot optimize JavaScript embedded inside a string. Minify the classic bootstrap before sharing it with the local data document and Worker to reduce browser payloads. Bootstrap size drops from 24,767 to 12,273 bytes; URL-encoded content drops from 43,273 to 17,251 bytes. bun run check passes all 135 tests, including runtime error attribution, plus package and starter checks.
robinvrd
approved these changes
Oct 8, 2026
The Ubuntu runner toolchain is older than actionlint requires. Automatic toolchain selection attempted to download an unavailable Go release and failed before validation. Pin the fallback go run command to the published Go 1.27.1 toolchain instead of relying on automatic version selection. Existing installed actionlint behavior remains unchanged. Verified the pinned actionlint fallback command, workflow and shell checks, formatting, and diff checks.
albert20260301
approved these changes
Oct 8, 2026
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.
Context
The frontend integration for rendering saved data apps in Claude exposed a restriction in Claude’s MCP proxy: it permits
data:andblob:child frames but blocks HTTPS child frames, even when the runtime domain is declared inframeDomains.The SDK currently requires bundle sources to provide an HTTP(S)
bootstrapUrl. Its source-mode bridge therefore cannot load the opaque bootstrap document that this integration needs.The frontend works around that restriction by importing the SDK Worker, obtaining its runtime HTML, inserting a UUID compatibility script, and encoding the result as a
data:text/htmlURL. It then uses connection mode and separately manages the iframe source, random token, initialization timeout, cleanup, and presentation updates.This duplicates SDK-owned behavior in the host and makes the frontend depend on the structure of the generated bootstrap HTML.
A second issue appears inside opaque data documents:
crypto.randomUUID()can be unavailable whilecrypto.getRandomValues()remains available. Direct UUID calls can consequently prevent the bootstrap from starting or fail later during app requests.Proposed solution
Make
bootstrapUrloptional on the existing bundle source:When
bootstrapUrlis omitted, the SDK generates adata:document containing its packaged bootstrap and the exact host origin. When an HTTP(S) URL is supplied, the existing external-bootstrap behavior remains available.This keeps bootstrap creation within source mode, which already owns iframe loading, sandbox policy, token rotation, initialization timeout, and disposal. It requires no additional public HTML generator or bootstrap lifecycle API.
The Worker’s runtime HTML generation and CSP move into a shared private module. The build compiles the bootstrap once and embeds the same classic script into both the browser SDK and Worker artifacts. The local document needs no runtime script download.
Secure UUID generation
Introduce a private UUID helper that uses native
crypto.randomUUID()when available and otherwise generates a UUID v4 fromcrypto.getRandomValues().Use it throughout the browser SDK wherever opaque execution can reach identifier generation: bootstrap and document identifiers, transport requests, host tokens and sessions, browser operations, SQL requests, React hook scopes, and annotations.
The helper does not modify the global
cryptoobject and never falls back toMath.random(). Identifier generation fails when cryptographic randomness is unavailable. App-owned code that independently calls nativecrypto.randomUUID()remains responsible for its own compatibility.Security and lifecycle
The packaged bootstrap uses the existing authenticated bridge and preserves its boundaries:
sandbox="allow-scripts"creates an opaque frame without same-origin privileges.connect-src 'none'; data requests run through the host’s dispatcher.Existing URL sources and explicit HTTP(S) bootstrap sources retain their behavior. Externally served bootstraps continue to own their response CSP.
Frontend migration
After adopting an SDK release containing this change, the frontend sandbox wrapper can render:
This removes the Worker import, HTML patching, UUID polyfill, data URL construction, manual token creation, startup timer, iframe/bridge refs, and lifecycle effects. The frontend continues to own bundle fetching, loading/error UI, and retry controls.
Verification
bun run checkpassed all 135 tests, including opaque-frame delivery and runtime error attribution, plus package and starter checks. Bootstrap minification reduces script size from 24,767 to 12,273 bytes and URL-encoded content from 43,273 to 17,251 bytes.Workflow validation pins its fallback
go runcommand to Go 1.27.1 because the runner's older Go version selected an unavailable toolchain download. The pinned actionlint fallback and workflow/shell checks pass locally.