Summary
RFC 0117 §"Isolation model" already specifies the plugin-isolation requirement as a property — origin + DOM isolation, deny-egress, RPC-only mediation — and explicitly blesses "an equivalent OS/web sandbox" and "WASM-VM vs. browser origin" as conformant. But the enforced advertisement schema (capabilities.uiPlugins.isolation) pins a single browser mechanism as a const "cross-origin-iframe" "the ONLY conformant isolation model." That contradicts RFC 0117's own §2 prose and the corpus's existing categorical isolation vocabulary (capabilities.sandbox.isolationModel: wasm/process/container/vm). This RFC widens isolation from a const to an enum (keeping cross-origin-iframe as a member and the browser default), reframes the frontend-plugin-isolation invariant in property terms, and adds a normative channel binding so the ui-plugin/1 RPC is carriable across a non-browser isolation boundary. The isolation property and all four SECURITY invariants are unchanged; only the mechanism over-specification is removed.
Motivation
The wire contract should constrain the observable security property, not the host's implementation technology. RFC 0117 gets this right in prose and wrong in schema:
- The contradiction is in the merged corpus, not hypothetical.
RFCS/0117-frontend-plugin-packs.md §2line 74: "The spec does not mandate how the host builds the sandbox (iframe origin scheme, CSP nonce strategy, WASM-VM vs. browser origin) — only that the isolation properties above hold." line 69 permits "a cross-origin<iframe>… or an equivalent OS/web sandbox that provides the same origin + DOM isolation." Yetschemas/capabilities.schema.json(uiPlugins.isolation) reads:"const": "cross-origin-iframe", "The ONLY conformant isolation model … Pinned as aconstso the advertisement cannot claim a weaker model." The binding artifact says the opposite of the normative prose it is supposed to encode.
- The corpus already solved this class of problem categorically.
capabilities.sandbox.isolationModel(RFC 0035, backend pack execution) is an enum —["wasm", "process", "container", "vm"]plusx-host-vendor models — letting a host pick the mechanism that fits its substrate while the spec owns the boundary*. RFC 0117 reinvented a narrower, browser-onlyconstfor the same "run untrusted pack bytes in an isolation boundary" problem. The right vocabulary was already in the same schema file.
- The over-specification excludes valid hosts. A host that enforces the identical property via a WASM sandbox, an OS-process boundary, or a microVM — the models RFC 0035 already enumerates — cannot honestly advertise
uiPluginstoday, even though it satisfies every isolation MUST/MUST-NOT RFC 0117 cares about. "The wire dictates a tech stack" is a real defect here, surfaced by a non-steward adopter (MyndHyve) during RFC 0117 implementation review.
Honest non-goal (stated up front so this lands for the right reason). Relaxing the const does not manufacture a new RFC 0117 witness. A genuinely headless durable runtime with no UI surface to render into would remain a vacuous uiPlugins host post-amendment — it has nothing to render, generalized isolation or not. This RFC is a mechanism-neutrality correctness fix (the schema contradicting its own §2), future-proofing the advertisement for a server-rendered / WASM-sandboxed UI-emitting host — not a move to graduate any specific adopter. The isolation property is exactly as strict after this change as before.
Proposal
1. Widen capabilities.uiPlugins.isolation from const to a categorical enum
The isolation field lives only in the host-advertisement (capabilities.uiPlugins), not in the frontend-plugin pack manifest — so pack authors are entirely unaffected; this is a host-advertisement vocabulary widening.
"isolation": {
"type": "string",
- "const": "cross-origin-iframe",
- "description": "The ONLY conformant isolation model. In-process / same-origin / module-federation loading is a protocol-tier MUST NOT (`frontend-plugin-isolation`). Pinned as a `const` so the advertisement cannot claim a weaker model."
+ "enum": ["cross-origin-iframe", "wasm", "process", "container", "vm"],
+ "default": "cross-origin-iframe",
+ "description": "The categorical isolation model the host enforces for plugin bytes. `cross-origin-iframe` = a distinct-origin sandboxed browser frame (the browser default; RFC 0117 §2). `wasm`/`process`/`container`/`vm` mirror `sandbox.isolationModel` (RFC 0035) for non-browser hosts that enforce the SAME isolation property (no host-context execution, RPC-only mediation, deny-egress). Vendor-specific models advertise `^x-host-<host>-<key>$` per `host-extensions.md`. ALL values denote the SAME mandatory property (`frontend-plugin-isolation`); the field names the mechanism, never relaxes the property. A host advertising any value MUST enforce the §2 isolation MUST-NOTs."
}
x-host-* vendor extension values are permitted via the existing host-extensions.md §"Canonical prefixes" convention (matching how sandbox.isolationModel already admits them); the schema gains the ^x-host- pattern allowance alongside the enum.
2. RFC 0117 §"Isolation model" — restate the requirement as the property (it largely already is)
The §2 MUST-NOTs are unchanged. The edit makes the mechanism examples subordinate to the property, so the schema and prose agree:
- MUST execute plugin bytes in a boundary that is origin/execution-isolated from the host application such that the plugin has no access to the host's execution context, host DOM (where one exists), host-origin storage, or host credentials. A cross-origin sandboxed
<iframe>is the canonical browser mechanism; an equivalent WASM / OS-process / container / VM sandbox that provides the same isolation property is equally conformant (capabilities.uiPlugins.isolationnames which). - MUST NOT load plugin bytes into the host's own execution context (no in-process module federation, no host-origin
<script>, no same-origin iframe). (Unchanged — protocol-tier MUST NOT with a public conformance test.) - MUST apply a deny-by-default egress policy to the plugin context (
connect-src 'none'-equivalent unless a capability grants otherwise). (Unchanged; "CSP" becomes "deny-egress policy (CSP where the substrate is a browser; the equivalent sandbox egress control otherwise)".) - MUST mediate all plugin↔host interaction through the §3
ui-plugin/1RPC channel. (Unchanged.)
3. RFC 0117 §"Host-RPC" — add a normative channel binding (the one real design addition)
RFC 0117 §3 describes delivery as window.postMessage. For a non-browser isolation value there is currently no normative way to carry the ui-plugin/1 envelope — so widening the enum without this would advertise a model with no conformant transport. New normative clause:
The host MUST deliver
ui-plugin/1envelopes across the isolation boundary as discrete messages with request/response correlation by the envelopeid. The transport is host-internal: browserwindow.postMessageforcross-origin-iframe, a host-import/export call forwasm, an IPC channel forprocess/container/vm. The envelope shape (theopenwop: "ui-plugin/1"tag,type∈ request/response/event,id,method,result/error) is transport-agnostic and MUST be identical across all isolation models. A host MUST NOT expose any plugin↔host channel other than this mediated RPC.
The envelope fields in ui-plugin-message.schema.json already require nothing browser-specific; only its description (and the manifest/capability descriptions) say "postMessage … across the cross-origin-iframe boundary" — those are de-narrated to "across the isolation boundary."
4. SECURITY invariant frontend-plugin-isolation — generalize the wording
The invariant's intent and MUST-NOT are unchanged; the description generalizes from iframe-specific to property terms:
frontend-plugin-isolation— a host loading a plugin MUST do so in an origin/execution-isolated sandbox that denies the plugin access to the host's execution context. Loading plugin bytes in the host's own execution context — in-process module federation, a host-origin<script>, or a same-origin iframe — is a MUST NOT, regardless of the advertisedisolationmechanism.
Examples
Positive (new, additive): a WASM-sandbox host advertises capabilities.uiPlugins.{supported: true, isolation: "wasm", surfaces: ["artifact-viewer"], hostApi: ["artifact.read","artifact.write"]}, loads vendor.acme.canvas-editor.app-builder opaque bytes inside a WASM sandbox with explicit host imports (no host-context access, deny-egress), carries ui-plugin/1 over its host-call boundary, enforces the artifact.read/artifact.write allowlist and the version-token optimistic concurrency. Valid — same property, non-browser mechanism.
Positive (unchanged): a browser host advertises isolation: "cross-origin-iframe" (the default) and loads the plugin in a distinct-origin sandboxed iframe behind postMessage. Valid exactly as before this RFC.
Negative (unchanged MUST-NOT): any host — browser or otherwise — that loads plugin bytes into its own execution context (same-origin iframe, in-process module federation) FAILS frontend-plugin-isolation, regardless of the isolation value it advertises.
Compatibility
Additive, per COMPATIBILITY.md §4 row: "Looser validation accepting input that previously failed — Yes — additive (clients that sent invalid input were already broken)."
Backward-compatibility clauses:
- No existing advertisement becomes invalid.
cross-origin-iframeremains a valid (and default) member of the enum; every host currently advertising it — including openwop-app, the RFC 0117 witness #1 currently implementing against it — stays conformant with zero change. - The field stays a
string; the shape is unchanged. RFC 0117 isActive(which locks the wire shape, perRFCS/README.md§status).const→enumwidens an allowed value-set without changing the field type or object shape —COMPATIBILITY.md §2.2("optional field type-changed") is not triggered. - No MUST relaxed (
§2.2). The isolation property MUST-NOTs and all four SECURITY invariants (frontend-plugin-isolation/-egress/-rpc-allowlist/-no-byok) are unchanged; every advertisedisolationvalue still guarantees the same property. The schema is brought up to match RFC 0117 §2's existing prose MUST, not loosened below it. - Discovery payload is server-emitted and open (
§2.1). Widening an enum the host advertises is the intended additive mechanism for capability vocabularies; an older consumer that only recognizedcross-origin-iframetreats an unknown value as "a model I don't recognize" → degrade-to-unsupported, the safe direction (it never under-enforces the property, since any advertised value still guarantees it).
No migration tooling required (additive). Pack authors are unaffected (the field is host-advertisement-only).
Conformance
- Existing coverage:
conformance/src/scenarios/frontend-plugin-packs.test.tsvalidates the manifest, theui-plugin/1allowlist (method_not_allowed), and theversion-token concurrency (artifact_conflict/currentVersion) via thehost-sample-test-seams.md §ui-plugin/rpcseam — which already routes one RPC through the host's real dispatcher with no live iframe, i.e. it is already transport-neutral and needs no change. - One assertion generalized: the
frontend-plugin-isolationleg currently asserts the browser-specific "not a same-origin iframe" shape; it generalizes to "not host-execution-context" so a non-browser isolation model can prove the property. - New schema legs (always-on): assert the widened
isolationenum accepts each new member and still rejects a weaker/unknown non-x-host-value; assert theui-plugin/1envelope shape is unchanged. - Capability gating: unchanged — all behavioral legs remain gated on
capabilities.uiPlugins.supported. - Deferred behavioral legs (named gaps). The three §Host-RPC strengthenings (framing/ordering, WASM single-import, backpressure) are transport / host-behavior properties, not envelope-shape properties — no always-on schema leg can cover them. Each becomes a capability-gated behavioral leg (framing-integrity, WASM-single-import-rejection, backpressure-refusal) that soft-skips exactly like the rest of the
uiPluginsbehavioral suite until a host advertises a non-browserisolationmodel. The existing browser witness + the unchanged envelope schema remain sufficient forAccepted; these are additive evidence if a WASM/process host appears. - Ships in the next
@openwop/openwop-conformanceminor (one net-new schema-leg assertion; no net-new scenario file ⇒ patch-or-minor per the independent-bump rule, decided at release).
Alternatives considered
- Do nothing — leave
isolationasconst "cross-origin-iframe". Rejected: the schema permanently contradicts RFC 0117 §2's own normative prose (an internal corpus inconsistency), and the protocol dictates a browser tech stack for a property that is mechanism-independent — excluding valid WASM/process/VM hosts that enforce the identical isolation. Doing nothing also lets the inconsistency harden as more hosts pin theconstbefore 0117 graduates toAccepted. - Drop the
isolationadvertisement entirely (let hosts self-describe in prose). Rejected: the property is load-bearing and must remain machine-checkable; removing the advertised model would weaken the capability handshake and make thefrontend-plugin-isolationgate unverifiable at discovery time. The fix is to generalize the vocabulary, not delete it. - A free-form
isolationstring. Rejected: loses the closed, reviewable vocabulary that lets a client reason about the model; reusing the establishedsandbox.isolationModelenum (+x-host-*for vendors) keeps the corpus consistent and the set auditable.
Unresolved questions
- Channel-binding strictness. — RESOLVED 2026-06-29. The abstract clause is not sufficient for non-browser transports. Per the MyndHyve implementer review (live non-iframe
ui-plugin/1Worker bridge), §Host-RPC now adds three normative clauses: MUST transport-guaranteed message-boundary framing + per-channel ordering (aMessagePortgives this free; a byte-stream transport must add framing); MUST, forisolation:"wasm", bind no host import beyond the singleui-plugin/1entrypoint (mirrors RFC 0008 §C; also a clause in thefrontend-plugin-isolationinvariant note); and SHOULD bound in-flightids with a same-channel refusal (no new error code). Stayed mechanism-neutral — a named per-model WASM import signature is still deferred unless a real WASM host needs it. - Conformance bump. Patch vs. minor for
@openwop/openwop-conformance— one generalized assertion + new always-on schema legs, no net-new scenario file. Decide at release per the independent-minor-bump rule. - **
x-host-*in the egress-policy clause.** For non-browser models, does "deny-egress equivalent" need a per-model normative floor, or is "host enforces no ambient egress" sufficient? Recommendation: sufficient for v1.x.
Implementation notes (non-normative)
- 0117 header update deferred to
Active. PerRFCS/README.md §"Amended by", RFC 0117 gains anAmended byforward pointer to this RFC. That edit lands when 0119 flips toActive(not while it is aDraftproposal), so a reader of 0117 is never told it is amended by something still under discussion. - openwop-app is RFC 0117 witness #1, mid-implementation against
cross-origin-iframe. This change keeps their path valid (they keep advertisingcross-origin-iframe), but they are the concrete browser adopter and must not be surprised — heads-up posted on the implementation channel at draft time. - Comment window: run it, don't instant-waive. Although additive (and the steward waived 0117's own window via lazy consensus), this touches an
ActiveRFC an external witness is actively implementing — so run the 7-day additive window (or an explicit ack-gate from openwop-app) rather than an instant waiver. - Surface edits land at
Active, not atDraft. This PR is the RFC proposal only. Thecapabilities.schema.json/ prose / invariant / conformance edits land in the follow-upActivePR per process.
Acceptance criteria
- [ ] RFC text merged (
Draft); 7-day additive comment window run (or explicit openwop-app ack). - [ ] On
Active:capabilities.schema.jsonuiPlugins.isolationwidened to the enum (+x-host-*); RFC 0117 §2 + §3 prose updated;ui-plugin-message/manifest descriptions de-narrated;frontend-plugin-isolationinvariant wording generalized; RFC 0117 header gains theAmended byrow. - [ ]
frontend-plugin-isolationconformance assertion generalized + new always-on enum/envelope schema legs; ships in the next@openwop/openwop-conformancerelease. - [ ] CHANGELOG entry under the appropriate version, classified
additive. - [ ]
npm run openwop:checkgreen on the merged tree. - [ ] A non-browser isolation witness is not required for
Accepted(the property is mechanism-neutral; the existing browser witness + the always-on schema legs prove the contract). A future WASM/processuiPluginshost, if one appears, becomes additional evidence.
References
RFCS/0117-frontend-plugin-packs.md§"Isolation model" + §"Host-RPC" (the amended surface).schemas/capabilities.schema.json—uiPlugins.isolation(consttoday) andsandbox.isolationModel(the categorical-enum precedent, RFC 0035).COMPATIBILITY.md§2.1 (server-emitted shapes open), §2.2 (never-in-v1.x list), §4 (looser-validation = additive).RFCS/README.md§status (Activelocks wire shape "unless the RFC explicitly says otherwise"), §"Amended by".SECURITY/invariants.yaml—frontend-plugin-isolationand siblings.spec/v1/host-sample-test-seams.md§ui-plugin/rpc (the already-transport-neutral conformance seam).