<iframe sandbox>
with a deliberately restrictive Content-Security-Policy and a strict
schema for any message the surface sends back. This page documents the
flags, the CSP derivation rules, and the postMessage envelope so that
publishers know what their code may and may not do.
Iframe attributes
sandbox="allow-scripts"— and explicitly notallow-same-origin. The iframe runs as an opaque origin, sowindow.parent.documentis unreachable. Cookies, localStorage, IndexedDB, andfetchto the host’s origin are all denied by the browser, even before the CSP fires.referrerpolicy="no-referrer"— outbound requests the surface does make leak no URL identifiers from the host.srcDoc=...— we never load a third-party HTML URL. The host renders the SDUI tree to static HTML inside the worktree and ships it as inline document content. Nosrc=ever points at publisher origins.
srcDoc carries the rendered tree plus a tiny bootstrap that:
- Adds a
clicklistener that walks ancestors looking for a[data-action-id], thenpostMessages a strict envelope to the host. No inlineonclick=handlers ever exist in the document. - Adds a
submitlistener for[data-form]nodes that posts the serialized form values up. - Has zero
eval, zeronew Function, and zero dynamic<script src>.
CSP derivation
The CSP is generated byferal_core.genui.permissions_policy.build_csp_header (Python) and
mirrored at feral-client-v2/src/pages/AppSurface.csp.js (JS). Both
read manifest.permissions.network and produce:
connect-srcdefaults to'none'. Publishers must list every origin they want to talk to. Wildcard*is accepted only under the high-trust flow described in Manifest signing.frame-ancestors 'self'keeps the surface from being embedded in arbitrary attacker pages.default-src 'none'means anything not explicitly granted above is blocked.
wss://... URIs in
permissions.network. The CSP helper coerces bare hostnames into
https://... to keep the policy strict-by-default.
postMessage envelope
The only structured channel between the surface and the host iswindow.postMessage. The host validates every event with
feral_core.genui.app_message_schema.AppMessage (Python) and the
matching validateAppMessage from
feral-client-v2/src/pages/AppSurface.types.ts. Anything that
doesn’t match is dropped silently — no exception, no audit noise.
Schema:
typemust be one of the four enum values. New values require a schema bump on both sides.payloadis a JSON object whose serialized size must be ≤ 64 KB.signed_with_key_idechoes the key id of the manifest being rendered, which lets the host correlate misbehaviour with a specific publisher key.- The Pydantic schema is
extra="forbid"; smuggling extra fields into the envelope is rejected.
type | Behaviour |
|---|---|
submit_form | Routed to sendUiEvent as { action_id, value } — the standard SDUI action path. |
navigate | Calls openSurface(payload.surface_id) if it’s a string the manifest declares. |
request_data | Reserved; currently no-op so that future server-driven prefetch doesn’t break apps. |
close | Reserved; currently no-op. |
What this rules out
- Publishers cannot read or write the host’s cookies or localStorage.
- Publishers cannot reach the host’s
/api/...endpoints unless they list them inpermissions.network(and even then they must talk to them through their own backend; the iframe runs as an opaque origin so it has no session cookie). - Publishers cannot navigate the host frame;
frame-ancestors 'self'- sandbox without
allow-top-navigationblockstop.locationwrites.
- sandbox without
- Publishers cannot invoke
evalor load<script src=>content; CSPscript-src 'unsafe-inline'allows only the inline bootstrap the host generates.
Related modules
feral-core/genui/manifest_signing.py— Ed25519 signing primitives.feral-core/genui/permissions_policy.py—enforce_install_policybuild_csp_header.
feral-core/genui/app_message_schema.py—AppMessagePydantic schema.feral-client-v2/src/pages/AppSurface.jsx— the iframe host.feral-client-v2/src/pages/AppSurface.csp.js— JS mirror of the CSP builder.feral-client-v2/src/pages/AppSurface.types.ts— TS mirror ofAppMessage.
