You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Provisional plan — recorded 2026-08-16 from the founding sandbox discussion. Adopted as the working app-execution model; converted to a ruling only by the checkpoints at the end. This amends the sketch in NOTES.md — App-frame sandboxing: the UI frame no longer executes an app-supplied HTML/JS bundle — it executes only framework shim code. Related: #5, #6, #7, #13; shape rules for the surface in #15.
The decision
Applications ship no JS, HTML, or CSS that executes or parses as page content anywhere in the system. The component is the entire app:
App logic runs as a wasm component (deltic, framework side), per the existing components bet.
App UI is expressed through a typed, DOM-shaped host surface (WIT): element creation from validated vocabulary, attributes checked per (tag, attribute), URL-typed values only as resource asset handles resolved from the manifest, events delivered as records (stream<event>), styling via parsed-and-laundered stylesheet assets or property-level calls.
The UI frame (sandboxed, dedicated origin, header CSP per NOTES) runs only trusted framework shim code, applying validated operations to real DOM. App-authored strings are never parsed as HTML there; app-authored script never executes there.
JS is userland, not platform: apps wanting JS compile it into their component (componentize-js / StarlingMonkey; QuickJS), optionally against a guest-side DOM shim so DOM-expecting frameworks (React-class) largely work. The sandbox is indifferent — the interpreter sits inside the capability boundary.
Why
Any realm executing app JS keeps a residual-channel long tail. The App-frame sandboxing: a ruling table per sandbox flag and CSP directive; residual channels; anti-spoofing chrome #5 ruling table (WebRTC data channels with webrtc 'block' support caveats, speculation rules, fresh same-origin realms resurrecting censored globals, UA-initiated fetches…) is load-bearing forever if app script runs in the frame, and it degrades on exactly our worst browser (Safari). With no app JS, that table demotes to defense-in-depth backstop; the primary boundary becomes the component import set — enumerable, diffable in CI, fuzzable per-function. There is no realm to escape and no global to censor.
String-HTML never crosses a trust boundary, killing the mutation-XSS / parser-differential class outright rather than sanitizing at it. CSS is treated as code (it is one): parsed with the engine's own parser (constructable stylesheets), URL-bearing properties rewritten to asset handles, @import inlined at install.
No live platform objects are reachable from app code. Handles are opaque component resources; traversal escapes (ownerDocument → window → network) exist only if the surface exports them. Validator TCB shrinks from "HTML/CSS/JS sanitizer" to typed per-op checks.
Sync layout reads become async or shim-cached (getBoundingClientRect-shaped code changes form). Two-hop RPC latency budget per NOTES stands.
Accessibility must be first-class in the surface vocabulary from day one — semantic elements and ARIA in v1, not retrofitted. (This is also why canvas-only rendering was rejected despite its smaller surface.)
Text input, IME, focus, selection are the classically thorny parts of mediated DOM; they need explicit design, not emergence.
Alternatives considered
App HTML/JS bundle in the sandboxed+CSP frame (the prior NOTES sketch): rejected as primary — keeps the ruling table as the real boundary forever, plus sanitizer burden, plus realm problems. Any future "legacy tier" running such bundles is a separate decision with its own threat entry, not a default.
Hardened JS (SES/Compartment) in a worker: viable but adds lockdown/engine-compat risk and keeps JS-realm reasoning; NOTES prior-art already names SES/Endo the road not taken — this extends that choice to the UI layer.
Platform-imposed interpreter-in-wasm: unnecessary given components; apps may do it themselves (see JS-as-userland above).
Canvas-only UI: total mediation, unacceptable a11y/text/i18n.
Performance posture
App logic runs at native wasm speed. The DOM surface is per-call and chatty by design (future-compatible per #15); the shim batches host-side per animation frame, and the postMessage hop — not the component boundary — dominates the UI path. Mozilla's Dodrio experiment (45% of DOM-change application time was glue overhead) both motivates host-side batching now and prices the native-binding upside later.
Conversion checkpoints (provisional → ruling)
Walking-skeleton app: a real (small) app built end-to-end on the surface under deltic — validates the vocabulary, measures two-hop latency and batching against interaction budgets.
Provisional plan — recorded 2026-08-16 from the founding sandbox discussion. Adopted as the working app-execution model; converted to a ruling only by the checkpoints at the end. This amends the sketch in NOTES.md — App-frame sandboxing: the UI frame no longer executes an app-supplied HTML/JS bundle — it executes only framework shim code. Related: #5, #6, #7, #13; shape rules for the surface in #15.
The decision
Applications ship no JS, HTML, or CSS that executes or parses as page content anywhere in the system. The component is the entire app:
resource assethandles resolved from the manifest, events delivered as records (stream<event>), styling via parsed-and-laundered stylesheet assets or property-level calls.Why
webrtc 'block'support caveats, speculation rules, fresh same-origin realms resurrecting censored globals, UA-initiated fetches…) is load-bearing forever if app script runs in the frame, and it degrades on exactly our worst browser (Safari). With no app JS, that table demotes to defense-in-depth backstop; the primary boundary becomes the component import set — enumerable, diffable in CI, fuzzable per-function. There is no realm to escape and no global to censor.@importinlined at install.ownerDocument→ window → network) exist only if the surface exports them. Validator TCB shrinks from "HTML/CSS/JS sanitizer" to typed per-op checks.What it costs (stated plainly — the Sandstorm lesson)
getBoundingClientRect-shaped code changes form). Two-hop RPC latency budget per NOTES stands.Alternatives considered
Performance posture
App logic runs at native wasm speed. The DOM surface is per-call and chatty by design (future-compatible per #15); the shim batches host-side per animation frame, and the postMessage hop — not the component boundary — dominates the UI path. Mozilla's Dodrio experiment (45% of DOM-change application time was glue overhead) both motivates host-side batching now and prices the native-binding upside later.
Conversion checkpoints (provisional → ruling)
Open sub-questions