Skip to content

Trust tiers: what Braid's boundary actually enforces ​

Braid composes fragments at two different boundaries. They are not a fast path and a safe path, or a default and an advanced option. They are different bets, and choosing between them is the most consequential decision you will make about a fragment.

This document exists because Braid's other documentation has, in places, described the trusted tier in language that implies more than it delivers. The correction, stated plainly:

The trusted tier is namespace isolation, not a security boundary. It protects one application from another application's runtime accidents. It does not protect one application when another application's runtime has gone bad.


The two tiers ​

Trusted (default)Untrusted
Mount<fragment-slot name="checkout"><fragment-slot trust="untrusted" src="https://…">
Realmhidden same-origin iframevisible cross-origin iframe
Where its DOM livesprojected into the host document, in a shadow rootits own document
IsolationJS globals, module registry, framework identitybrowser-enforced: Same-Origin Policy, sandbox, Permissions-Policy
Cookies & storagethe host'sits own, partitioned
Host globalsits own realm's, plus any the host lists in sharedGlobals or hands it through realmGlobalsits own
Layoutparticipates in host layouta rectangle the host sizes
Accessibilityone treenested documents
Overlayscan escape the fragment's boxclipped by the frame
Printing & selectionnormalframe-bound
SSR piercingyes — fragment HTML in the host's first responseno
Gatewayrequired for compat modenot involved at all
Zero app-code changeyes, via the compat adapterno — the fragment calls connectToBraidHost()

Everything in the trusted column below "where its DOM lives" is a consequence of sharing a document. None of it survives a boundary the browser enforces. The untrusted tier gives up all of it.

Shared globals widen the trusted tier on purpose. A trusted fragment's JavaScript runs in its own realm, so by default its window.dataLayer is a private array that no tag manager reads. initBraid({ sharedGlobals: ['dataLayer', '_satellite'] }) gives every trusted fragment an accessor for each named host global. Through it the fragment can read the host's value and replace it. Braid still installs nothing on the host window. The new thing is a write channel into host globals the host named itself. List only globals you would let any trusted fragment overwrite, and initialize them on the host first: an object a fragment creates outlives the fragment. A fragment's capabilities.sharedGlobals replaces the list for that fragment. It can narrow the list, opt out with [], or add names initBraid never listed. Whoever controls the registry therefore controls this, so review manifest changes to sharedGlobals the way you review host config. The untrusted tier ignores it, because there is nothing same-origin to share.

Realm globals are per-fragment values, not host ones. initBraid({ realmGlobals }) is a hook called once per trusted fragment boot with { fragmentId, signal }; what it returns is defined on that fragment's realm window before its code runs. It is how @braidlabs/fdc3 gives each fragment a window.fdc3 bound to its own app identity. Nothing is shared or written back: the host decides the value per fragment, and the signal says when to release it. Contract and untrusted fragments ignore it, as they do sharedGlobals.

What the untrusted tier is ​

An iframe with a good protocol on it.

That is not a diminishment. The protocol is the part that makes an iframe usable as a composition primitive rather than an escape hatch: a pinned handshake, contract negotiation before any state crosses, liveness that can tell a hung fragment from a hidden tab, and a teardown that lets a fragment flush unsaved work before it is disposed. All of that is shared with the trusted tier — the same weave transport carries both.

What the untrusted tier adds is the only thing the trusted tier cannot: separation that holds when the code on the other side is hostile, compromised, or merely has a compromised dependency.

Choosing ​

Use the trusted tier when every fragment on the page belongs to the same security principal — your own teams, your own code, deployed independently but trusted equally. This is the case Braid is best at, and its advantages are real: seamless layout, one accessibility tree, server-composed first paint, and zero-change adoption of applications you cannot modify.

Use the untrusted tier when that sentence stops being true:

  • a third-party widget, analytics vendor, or embedded product;
  • an acquired system whose code you have not audited;
  • a plugin surface open to authors you do not employ;
  • any fragment where a compromised dependency should not become a compromised session.

Registering a trusted fragment grants it the user's session. Its realm is same-origin, so it shares the host's cookies and storage, and its DOM is in the host's document. If that sentence is uncomfortable for a given fragment, that fragment belongs in the untrusted tier.

Using the untrusted tier ​

Host:

html
<fragment-slot
  name="vendor-analytics"
  trust="untrusted"
  src="https://vendor.example.com/widget"
  allow="clipboard-write"
  sandbox="allow-popups">
  <p slot="fallback">Analytics are unavailable.</p>
</fragment-slot>
ts
initBraid({
  capabilities: {
    'vendor-analytics': {
      // Only these keys enter the fragment's mirror. Every other key is not merely withheld —
      // the fragment is never told it exists or when it changes.
      context: { read: ['locale'] },
      permissions: ['clipboard-write'],
    },
  },
});

Fragment, at its own origin:

ts
import { connectToBraidHost } from '@braidlabs/core';

const session = await connectToBraidHost({
  hostOrigin: 'https://portal.example.com',
  contract: { version: '1.0.0', requires: { host: '>=1.4.0' } },
});

session.context.subscribe('user', renderUser); // the current user now, then changes
session.onClosing(() => flushImpressions());
session.emit('impression', { slot: 'sidebar' });

What is enforced, and by whom ​

ControlEnforced byNotes
DOM, storage, cookie separationthe browser (Same-Origin Policy)the reason this tier exists
sandbox tokensthe browserallow-scripts allow-forms by default; additions are host-declared
allow= featuresthe browser (Permissions-Policy)a deny list: unlisted features are unavailable in the frame
credentiallessthe browser, where supportedephemeral storage, so a fragment cannot correlate the user across sites
Origin pinningBraid, both directionsthe host posts to an exact origin; the guest accepts from one
Contract termsBraidnegotiated before state crosses
Context grantsBraidcapabilities.context.read; an ungranted key is not mirrored and sends no change notification

Two refusals worth knowing about ​

A same-origin src is refused. sandbox="allow-scripts allow-same-origin" on a same-origin frame is a sandbox that is not one: the framed document can reach into the host and remove its own sandbox attribute. The markup looks locked down while it happens, so Braid refuses at the source rather than trusting the token list.

allow-top-navigation is never granted. It is the standard route from "embedded widget" to "phishing redirect". Use allow-top-navigation-by-user-activation if the fragment genuinely needs to navigate the page.

What is still missing ​

Named here rather than left to be discovered:

  • Context write grants. No fragment can publish to the bus today, so capabilities.context.write is reserved and unenforced. read is enforced.
  • No DOM projection, by design. If you need a fragment's content to participate in host layout, escape its box, or appear in the host's server-rendered HTML, it belongs in the trusted tier. This is not a gap to be closed later — it is the trade.
  • Cross-origin authentication is your problem. The trusted tier's smooth session sharing comes from being same-origin. Here, third-party cookie restrictions and separate sessions are real architecture questions, and Braid does not answer them for you.