Shipping
Runtime Environment
On this page
Introduction
Your applet runs inside a sandboxed iframe, under a Content Security Policy the Rack serves with your entry document. Together they decide what your code can reach: anything that matters goes through Rack.*, and everything your applet needs at runtime ships inside the zip.
This page describes that environment as your applet experiences it, what it means you cannot do, and how to handle input you should not trust.
The Sandbox
A Rack renders your applet in an iframe with these attributes:
<iframe sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; clipboard-write"
src="…signed, version-scoped package path…"></iframe>allow-same-origin is absent, so your applet's document has an opaque origin. That means:
- No cookies, no
localStorage, nosessionStorage, noIndexedDB. - No credentialed requests, and no useful requests to the Rack at all.
- No access to the parent DOM.
- Nested frames inherit the flags, so an embedded player is equally contained.
Communication with the Rack is postMessage only, which the SDK handles for you. Your applet never holds a credential: the Rack decides who is asking from the launch session, never from your payload, so nothing you put in a message can reach another participant, another attempt or another applet's data. File URLs handed out by storage@1 are short-lived and signed.
This is why anything that matters must go through Rack.*. Storage inside the frame fails, so there is nowhere for your applet to keep state except the Rack.
Extending the Frame's Permissions
The allow list above is the base. A Rack may grant further browser features to the applets it serves, such as the microphone for an applet that records audio. Which features, if any, is the Rack's decision, and the browser still prompts the participant before granting one. Your applet should therefore treat any such feature as one that may be denied.
The Content Security Policy
The Rack serves your applet's entry document with this Content Security Policy:
| Directive | Value |
|---|---|
default-src |
'self' + the Rack origin |
script-src |
'self' + the Rack origin, 'unsafe-inline' 'unsafe-eval' blob: |
style-src |
'self' + the Rack origin, 'unsafe-inline' + the Rack's font hosts |
font-src |
'self' + the Rack origin, data: + the Rack's font hosts |
img-src |
'self' + the Rack origin, data: blob: + the Rack's allowlist |
media-src |
'self' + the Rack origin, blob: + the Rack's allowlist |
connect-src |
'self' + the Rack origin |
frame-src |
the allowlisted media providers' player hosts, else 'none' |
frame-ancestors, base-uri, form-action |
'self' |
The Rack origin is the exact origin the Rack is served from (scheme, host and any non-default port), for example https://rack.example.com.
Loading Your Assets
In practice, this means your applet must bundle every dependency, ship its own images and media inside the zip, and make no requests to external APIs or analytics services:
- Scripts and styles load from your package. Inline scripts and styles,
evalandblob:scripts are allowed, and stylesheets may also come from the Rack's font hosts. - Fonts load from your package, from
data:URIs, or from the Rack's font hosts. If you need a particular font, ship it in the zip. - Images, audio and video load from your package, from
data:(images) orblob:URLs, or from hosts the Rack has allowlisted. Reference your own files relatively. - Embedded video reaches the frame only through declared media.
The font hosts and the allowlist belong to the Rack, not to your package, and a bare scheme such as https: is never on it. Anything not in the zip and not covered above will be blocked.
Migrations
Data migrations do not run in your applet's frame. They run isolated, in the browser of whoever promotes the version, with no network access, so a migration must be self-contained and work only with the records it is handed.
Untrusted Input
The sandbox stops your applet from reaching things; it does not decide what your applet does with what participants give it. A few rules follow:
- Never ask participants for credentials, not even with a form that looks like the Rack's.
- Don't treat roles as permissions. An applet's roles arrive in the launch payload and are read by your code in the participant's browser, so a role is a reason to show a control, not a permission. Keep anything a role must protect behind a scope, a
permissionsrule or a facilitator event, which the Rack enforces. - Treat participant text passed to a model as untrusted. See Untrusted Variables.
- Expect limits to be enforced on the server. Quotas and AI budgets are hard caps, whatever your applet believes, so handle
quota_exceededandbudget_exceeded.