@braidlabs/core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/adapters/compat-adapter.js +51 -4
- package/src/adapters/compat-adapter.js.map +1 -1
- package/src/compat/database-partition.d.ts +43 -0
- package/src/compat/database-partition.js +165 -0
- package/src/compat/database-partition.js.map +1 -0
- package/src/compat/fragment-boundary.js +133 -25
- package/src/compat/fragment-boundary.js.map +1 -1
- package/src/compat/html-policy.js +10 -5
- package/src/compat/html-policy.js.map +1 -1
- package/src/compat/script-execution.js +5 -4
- package/src/compat/script-execution.js.map +1 -1
- package/src/compat/shared-globals.d.ts +1 -1
- package/src/compat/shared-globals.js +2 -2
- package/src/compat/shared-globals.js.map +1 -1
- package/src/config.d.ts +13 -2
- package/src/config.js.map +1 -1
- package/src/elements/fragment-slot.d.ts +41 -15
- package/src/elements/fragment-slot.js +195 -9
- package/src/elements/fragment-slot.js.map +1 -1
- package/src/elements/leave-guard.d.ts +49 -0
- package/src/elements/leave-guard.js +122 -0
- package/src/elements/leave-guard.js.map +1 -0
- package/src/elements/overlay-layer.d.ts +45 -0
- package/src/elements/overlay-layer.js +112 -0
- package/src/elements/overlay-layer.js.map +1 -0
- package/src/env/create-env.js +2 -1
- package/src/env/create-env.js.map +1 -1
- package/src/env/fragment-env.d.ts +6 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +14 -0
- package/src/index.js.map +1 -1
- package/src/realm/realm-manager.js +2 -1
- package/src/realm/realm-manager.js.map +1 -1
- package/src/realm/sandbox-realm.d.ts +5 -7
- package/src/realm/sandbox-realm.js +104 -39
- package/src/realm/sandbox-realm.js.map +1 -1
- package/src/trusted-types.d.ts +28 -0
- package/src/trusted-types.js +58 -0
- package/src/trusted-types.js.map +1 -0
- package/src/weave/capabilities.d.ts +10 -3
- package/src/weave/capabilities.js +19 -3
- package/src/weave/capabilities.js.map +1 -1
- package/src/weave/context-bridge.d.ts +2 -0
- package/src/weave/context-bridge.js +1 -1
- package/src/weave/context-bridge.js.map +1 -1
- package/src/weave/guest.d.ts +2 -0
- package/src/weave/guest.js +10 -5
- package/src/weave/guest.js.map +1 -1
- package/src/weave/messages.d.ts +24 -0
- package/src/weave/messages.js +22 -0
- package/src/weave/messages.js.map +1 -1
|
@@ -16,7 +16,7 @@ import { BraidError } from '../errors.js';
|
|
|
16
16
|
* | --- | --- | --- |
|
|
17
17
|
* | isolation | namespace, not security | browser-enforced |
|
|
18
18
|
* | DOM | projected into the host document | stays in its own document |
|
|
19
|
-
* | cookies/storage | the host's |
|
|
19
|
+
* | cookies/storage | the host's | none: an opaque origin has no cookies, Web Storage or IndexedDB |
|
|
20
20
|
* | composition | gateway namespaces, piercing, compat facade | none of it applies |
|
|
21
21
|
*
|
|
22
22
|
* **Why this is not a `RealmHandle`.** That interface exposes `window`, `document`, `evaluate()`
|
|
@@ -45,7 +45,51 @@ export const DEFAULT_SANDBOX_TOKENS = ['allow-scripts', 'allow-forms'];
|
|
|
45
45
|
* grant if you need it, and it must be granted deliberately rather than inherited from a broad
|
|
46
46
|
* "let this fragment navigate" instinct.
|
|
47
47
|
*/
|
|
48
|
-
const
|
|
48
|
+
const TOP_NAVIGATION_HINT = 'use "allow-top-navigation-by-user-activation" if the fragment genuinely needs to navigate the page';
|
|
49
|
+
const REFUSED_SANDBOX_TOKENS = new Map([
|
|
50
|
+
['allow-top-navigation', TOP_NAVIGATION_HINT],
|
|
51
|
+
['allow-top-navigation-to-custom-protocols', TOP_NAVIGATION_HINT],
|
|
52
|
+
/**
|
|
53
|
+
* The same navigation by another door: a popup that escapes the sandbox can set
|
|
54
|
+
* `window.opener.top.location` and swap the host page for a lookalike.
|
|
55
|
+
*/
|
|
56
|
+
[
|
|
57
|
+
'allow-popups-to-escape-sandbox',
|
|
58
|
+
'"allow-popups" alone lets the fragment open windows; they stay sandboxed, which is the point',
|
|
59
|
+
],
|
|
60
|
+
/**
|
|
61
|
+
* The token that turns the sandbox off. With it the frame keeps a real origin: a `javascript:`
|
|
62
|
+
* src, or a navigation to a URL on the host's origin, then runs with the host's own authority.
|
|
63
|
+
* The handshake works without it, so nothing on this tier needs it.
|
|
64
|
+
*/
|
|
65
|
+
[
|
|
66
|
+
'allow-same-origin',
|
|
67
|
+
'leave it out: the untrusted tier connects from an opaque origin, which is what keeps it isolated',
|
|
68
|
+
],
|
|
69
|
+
]);
|
|
70
|
+
/**
|
|
71
|
+
* Every token the HTML standard defines. Anything else is refused rather than passed through: the
|
|
72
|
+
* browser ignores a token it does not know, so a typo would silently grant less than intended — or,
|
|
73
|
+
* as browsers add tokens, silently grant something nobody reviewed.
|
|
74
|
+
*/
|
|
75
|
+
const KNOWN_SANDBOX_TOKENS = new Set([
|
|
76
|
+
'allow-downloads',
|
|
77
|
+
'allow-forms',
|
|
78
|
+
'allow-modals',
|
|
79
|
+
'allow-orientation-lock',
|
|
80
|
+
'allow-pointer-lock',
|
|
81
|
+
'allow-popups',
|
|
82
|
+
'allow-popups-to-escape-sandbox',
|
|
83
|
+
'allow-presentation',
|
|
84
|
+
'allow-same-origin',
|
|
85
|
+
'allow-scripts',
|
|
86
|
+
'allow-storage-access-by-user-activation',
|
|
87
|
+
'allow-top-navigation',
|
|
88
|
+
'allow-top-navigation-by-user-activation',
|
|
89
|
+
'allow-top-navigation-to-custom-protocols',
|
|
90
|
+
]);
|
|
91
|
+
/** Loopback hosts, where plain `http:` is how local development serves a second origin. */
|
|
92
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
49
93
|
const LOAD_TIMEOUT_MS = 15_000;
|
|
50
94
|
/**
|
|
51
95
|
* Refuses a same-origin URL for the untrusted tier.
|
|
@@ -69,6 +113,20 @@ export function assertCrossOrigin(src, fragmentId) {
|
|
|
69
113
|
fixHint: 'untrusted fragments are addressed by absolute url, e.g. src="https://vendor.example.com/widget"',
|
|
70
114
|
});
|
|
71
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Only a network scheme can name another origin. `javascript:`, `data:` and `blob:` have an
|
|
118
|
+
* opaque origin, which never equals the host's, so the comparison below would wave them through
|
|
119
|
+
* — and a `javascript:` src in a frame granted `allow-same-origin` runs in the host's origin.
|
|
120
|
+
*/
|
|
121
|
+
const secure = url.protocol === 'https:';
|
|
122
|
+
const loopback = url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname);
|
|
123
|
+
if (!secure && !loopback) {
|
|
124
|
+
throw new BraidError(`the untrusted tier loads fragments over https, and "${src}" is not an https url`, {
|
|
125
|
+
fragmentId,
|
|
126
|
+
stage: 'slot-config',
|
|
127
|
+
fixHint: 'serve the fragment from its own https origin; plain http is accepted only on localhost',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
72
130
|
if (url.origin === location.origin) {
|
|
73
131
|
throw new BraidError(`the untrusted tier requires a cross-origin url, and "${url.origin}" is this host's origin`, {
|
|
74
132
|
fragmentId,
|
|
@@ -82,12 +140,23 @@ export function assertCrossOrigin(src, fragmentId) {
|
|
|
82
140
|
/** Resolves the sandbox token list, refusing the ones that would undo the sandbox. */
|
|
83
141
|
export function resolveSandboxTokens(requested = [], fragmentId) {
|
|
84
142
|
const tokens = new Set(DEFAULT_SANDBOX_TOKENS);
|
|
85
|
-
|
|
86
|
-
|
|
143
|
+
// The browser splits the attribute on whitespace and matches tokens case-insensitively, so this
|
|
144
|
+
// must too — or "ALLOW-TOP-NAVIGATION" and "allow-popups allow-top-navigation" slip past.
|
|
145
|
+
const normalized = requested.flatMap((entry) => String(entry).toLowerCase().split(/\s+/).filter(Boolean));
|
|
146
|
+
for (const token of normalized) {
|
|
147
|
+
if (!KNOWN_SANDBOX_TOKENS.has(token)) {
|
|
148
|
+
throw new BraidError(`"${token}" is not a sandbox token`, {
|
|
149
|
+
fragmentId,
|
|
150
|
+
stage: 'slot-config',
|
|
151
|
+
fixHint: 'sandbox tokens are the allow-* keywords the HTML standard defines, e.g. "allow-popups"',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const refusal = REFUSED_SANDBOX_TOKENS.get(token);
|
|
155
|
+
if (refusal !== undefined) {
|
|
87
156
|
throw new BraidError(`the sandbox token "${token}" is never granted to an untrusted fragment`, {
|
|
88
157
|
fragmentId,
|
|
89
158
|
stage: 'slot-config',
|
|
90
|
-
fixHint:
|
|
159
|
+
fixHint: refusal,
|
|
91
160
|
});
|
|
92
161
|
}
|
|
93
162
|
tokens.add(token);
|
|
@@ -97,11 +166,9 @@ export function resolveSandboxTokens(requested = [], fragmentId) {
|
|
|
97
166
|
/**
|
|
98
167
|
* Creates the frame, waits for it to load, and establishes the message channel.
|
|
99
168
|
*
|
|
100
|
-
* The
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* whether or not it has already been posted to. Either order converges; neither has to be the fast
|
|
104
|
-
* one.
|
|
169
|
+
* The guest announces itself and the host answers that window with the port. The host listens
|
|
170
|
+
* before the frame is attached, so there is no ordering to race: however fast the guest's script
|
|
171
|
+
* runs, its announcement finds a listener.
|
|
105
172
|
*/
|
|
106
173
|
export async function createSandboxRealm(init) {
|
|
107
174
|
const { fragmentId, src, container, signal } = init;
|
|
@@ -109,7 +176,9 @@ export async function createSandboxRealm(init) {
|
|
|
109
176
|
const tokens = resolveSandboxTokens(init.sandboxTokens, fragmentId);
|
|
110
177
|
const instance = `${fragmentId}:${Date.now().toString(36)}`;
|
|
111
178
|
const frame = document.createElement('iframe');
|
|
112
|
-
|
|
179
|
+
// The absolute URL that was checked, not the string: `src` would resolve against
|
|
180
|
+
// `document.baseURI`, which a `<base>` element can point somewhere `location.href` does not.
|
|
181
|
+
frame.src = new URL(src, location.href).href;
|
|
113
182
|
frame.setAttribute('sandbox', tokens.join(' '));
|
|
114
183
|
frame.setAttribute('part', 'frame');
|
|
115
184
|
frame.style.cssText = 'display:block;border:0;width:100%;height:100%';
|
|
@@ -125,53 +194,50 @@ export async function createSandboxRealm(init) {
|
|
|
125
194
|
if ('credentialless' in HTMLIFrameElement.prototype) {
|
|
126
195
|
frame.setAttribute('credentialless', '');
|
|
127
196
|
}
|
|
128
|
-
container.append(frame);
|
|
129
197
|
const channel = new MessageChannel();
|
|
130
198
|
const dispose = () => {
|
|
131
199
|
channel.port1.close();
|
|
132
200
|
frame.remove();
|
|
133
201
|
};
|
|
134
202
|
signal.addEventListener('abort', dispose, { once: true });
|
|
135
|
-
const post = () => {
|
|
136
|
-
const message = { braid: SANDBOX_CONNECT, v: 1, fragmentId, instance };
|
|
137
|
-
/**
|
|
138
|
-
* The target origin is the fragment's exact origin, never `'*'`.
|
|
139
|
-
*
|
|
140
|
-
* With `'*'` the port would be delivered to whatever document happens to occupy the frame — and
|
|
141
|
-
* an untrusted fragment that navigated itself elsewhere between load and this call would hand a
|
|
142
|
-
* live channel into the host to a document nobody vetted.
|
|
143
|
-
*/
|
|
144
|
-
frame.contentWindow?.postMessage(message, origin, [channel.port2]);
|
|
145
|
-
};
|
|
146
203
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
147
204
|
let connected = false;
|
|
205
|
+
/**
|
|
206
|
+
* The guest speaks first, and the host answers that window and no other.
|
|
207
|
+
*
|
|
208
|
+
* A sandboxed frame without `allow-same-origin` has an opaque origin. A message targeted at the
|
|
209
|
+
* fragment's origin is never delivered to it, and its own messages arrive with origin `"null"`.
|
|
210
|
+
* The origin cannot identify it, so the window does: `event.source` is set by the browser, and
|
|
211
|
+
* only this frame's window can be `frame.contentWindow`. The reply goes to that window with `'*'`,
|
|
212
|
+
* the only target an opaque origin accepts. It reaches whichever document the frame holds when it
|
|
213
|
+
* arrives, which is the one that asked unless the frame navigated in between. Only the frame
|
|
214
|
+
* itself can navigate it, so any document it lands on is the vendor's choice and as trusted as the
|
|
215
|
+
* vendor.
|
|
216
|
+
*/
|
|
148
217
|
const onWindowMessage = (event) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (event.origin !==
|
|
218
|
+
if (event.source === null || event.source !== frame.contentWindow)
|
|
219
|
+
return;
|
|
220
|
+
if (event.origin !== 'null')
|
|
152
221
|
return;
|
|
153
222
|
if (event.data?.braid !== SANDBOX_READY)
|
|
154
223
|
return;
|
|
155
224
|
if (connected)
|
|
156
225
|
return;
|
|
157
226
|
connected = true;
|
|
158
|
-
|
|
227
|
+
const message = { braid: SANDBOX_CONNECT, v: 1, fragmentId, instance };
|
|
228
|
+
event.source.postMessage(message, '*', [channel.port2]);
|
|
159
229
|
resolve();
|
|
160
230
|
};
|
|
231
|
+
// Listening before the frame exists, so a guest that announces itself at once is not missed.
|
|
161
232
|
window.addEventListener('message', onWindowMessage);
|
|
162
|
-
|
|
163
|
-
if (connected)
|
|
164
|
-
return;
|
|
165
|
-
connected = true;
|
|
166
|
-
post();
|
|
167
|
-
resolve();
|
|
168
|
-
};
|
|
169
|
-
frame.addEventListener('load', onLoad, { once: true });
|
|
233
|
+
container.append(frame);
|
|
170
234
|
const timeout = setTimeout(() => {
|
|
171
|
-
reject(new BraidError(`the untrusted fragment at "${src}" did not
|
|
235
|
+
reject(new BraidError(`the untrusted fragment at "${src}" did not connect within ${init.loadTimeoutMs ?? LOAD_TIMEOUT_MS}ms`, {
|
|
172
236
|
fragmentId,
|
|
173
|
-
stage: '
|
|
174
|
-
fixHint:
|
|
237
|
+
stage: 'handshake',
|
|
238
|
+
fixHint: `the fragment must call connectToBraidHost({ hostOrigin: '${location.origin}' }). Its frame runs in an opaque ` +
|
|
239
|
+
'origin, so its module scripts need Access-Control-Allow-Origin. Also check that its origin does not refuse ' +
|
|
240
|
+
'framing via X-Frame-Options or CSP frame-ancestors',
|
|
175
241
|
}));
|
|
176
242
|
}, init.loadTimeoutMs ?? LOAD_TIMEOUT_MS);
|
|
177
243
|
try {
|
|
@@ -180,7 +246,6 @@ export async function createSandboxRealm(init) {
|
|
|
180
246
|
finally {
|
|
181
247
|
clearTimeout(timeout);
|
|
182
248
|
window.removeEventListener('message', onWindowMessage);
|
|
183
|
-
frame.removeEventListener('load', onLoad);
|
|
184
249
|
}
|
|
185
250
|
return {
|
|
186
251
|
kind: 'sandbox',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-realm.js","sourceRoot":"","sources":["../../../../../libs/core/src/realm/sandbox-realm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,CAAC;AAC/C,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAc3C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,eAAe,EAAE,aAAa,CAAU,CAAC;AAEhF;;;;;;;GAOG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"sandbox-realm.js","sourceRoot":"","sources":["../../../../../libs/core/src/realm/sandbox-realm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Braid's own writes into the sinks Trusted Types guards, for a host page that enforces them
|
|
3
|
+
* (`require-trusted-types-for 'script'`).
|
|
4
|
+
*
|
|
5
|
+
* The runtime writes to a handful of sinks itself: it parses the markup the gateway prepared, it
|
|
6
|
+
* marks a script as already started by giving it a line of text, and the opt-in HTML policy
|
|
7
|
+
* parses a fragment's write into an inert document before cleaning it. Under enforcement each of
|
|
8
|
+
* those needs a trusted value, so they go through one policy, named `braid`, which a host lists in
|
|
9
|
+
* its `trusted-types` directive.
|
|
10
|
+
*
|
|
11
|
+
* The policy passes values through unchanged, which is why it is never exported: only these call
|
|
12
|
+
* sites can reach it, and each one either writes something Braid authored or parses into a
|
|
13
|
+
* document where nothing runs. Everything a fragment writes itself still meets the host's own
|
|
14
|
+
* enforcement — which is the point of turning it on.
|
|
15
|
+
*
|
|
16
|
+
* Created on first use, in whichever realm the write happens: a realm document inherits the host's
|
|
17
|
+
* policy, and a trusted value is checked against the realm that writes it.
|
|
18
|
+
*/
|
|
19
|
+
/** Markup Braid parses: the gateway's prepared document, or a write it is about to clean. */
|
|
20
|
+
export declare function trustedHTML(markup: string, realm?: object): string;
|
|
21
|
+
/** Script text Braid writes itself. */
|
|
22
|
+
export declare function trustedScript(source: string, realm?: object): string;
|
|
23
|
+
/**
|
|
24
|
+
* Creates the host's policy now, if the page has Trusted Types. Called by `initBraid`, before any
|
|
25
|
+
* fragment code runs: what the host's directive grants is the name `braid`, so it must be Braid's
|
|
26
|
+
* before anything else on the page can create it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function claimBraidPolicy(): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Braid's own writes into the sinks Trusted Types guards, for a host page that enforces them
|
|
3
|
+
* (`require-trusted-types-for 'script'`).
|
|
4
|
+
*
|
|
5
|
+
* The runtime writes to a handful of sinks itself: it parses the markup the gateway prepared, it
|
|
6
|
+
* marks a script as already started by giving it a line of text, and the opt-in HTML policy
|
|
7
|
+
* parses a fragment's write into an inert document before cleaning it. Under enforcement each of
|
|
8
|
+
* those needs a trusted value, so they go through one policy, named `braid`, which a host lists in
|
|
9
|
+
* its `trusted-types` directive.
|
|
10
|
+
*
|
|
11
|
+
* The policy passes values through unchanged, which is why it is never exported: only these call
|
|
12
|
+
* sites can reach it, and each one either writes something Braid authored or parses into a
|
|
13
|
+
* document where nothing runs. Everything a fragment writes itself still meets the host's own
|
|
14
|
+
* enforcement — which is the point of turning it on.
|
|
15
|
+
*
|
|
16
|
+
* Created on first use, in whichever realm the write happens: a realm document inherits the host's
|
|
17
|
+
* policy, and a trusted value is checked against the realm that writes it.
|
|
18
|
+
*/
|
|
19
|
+
const POLICY_NAME = 'braid';
|
|
20
|
+
const policies = new WeakMap();
|
|
21
|
+
function policyFor(realm) {
|
|
22
|
+
if (policies.has(realm))
|
|
23
|
+
return policies.get(realm) ?? null;
|
|
24
|
+
const factory = realm.trustedTypes;
|
|
25
|
+
let policy = null;
|
|
26
|
+
if (factory) {
|
|
27
|
+
try {
|
|
28
|
+
const same = (input) => input;
|
|
29
|
+
policy = factory.createPolicy(POLICY_NAME, { createHTML: same, createScript: same });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
// Not listed in the host's `trusted-types` directive, or the name already taken — by a second
|
|
33
|
+
// copy of the runtime, or by script that ran first. Writes go as plain strings and enforcement
|
|
34
|
+
// reports them; the warning says which.
|
|
35
|
+
console.warn(`[braid] could not create the "${POLICY_NAME}" Trusted Types policy: ${error?.message ?? error}`);
|
|
36
|
+
policy = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
policies.set(realm, policy);
|
|
40
|
+
return policy;
|
|
41
|
+
}
|
|
42
|
+
/** Markup Braid parses: the gateway's prepared document, or a write it is about to clean. */
|
|
43
|
+
export function trustedHTML(markup, realm = globalThis) {
|
|
44
|
+
return policyFor(realm)?.createHTML(markup) ?? markup;
|
|
45
|
+
}
|
|
46
|
+
/** Script text Braid writes itself. */
|
|
47
|
+
export function trustedScript(source, realm = globalThis) {
|
|
48
|
+
return policyFor(realm)?.createScript(source) ?? source;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates the host's policy now, if the page has Trusted Types. Called by `initBraid`, before any
|
|
52
|
+
* fragment code runs: what the host's directive grants is the name `braid`, so it must be Braid's
|
|
53
|
+
* before anything else on the page can create it.
|
|
54
|
+
*/
|
|
55
|
+
export function claimBraidPolicy() {
|
|
56
|
+
policyFor(globalThis);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=trusted-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-types.js","sourceRoot":"","sources":["../../../../libs/core/src/trusted-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAWH,MAAM,WAAW,GAAG,OAAO,CAAC;AAC5B,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAoC,CAAC;AAEjE,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAC5D,MAAM,OAAO,GAAI,KAA4B,CAAC,YAAY,CAAC;IAC3D,IAAI,MAAM,GAA6B,IAAI,CAAC;IAC5C,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC;YACtC,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,8FAA8F;YAC9F,+FAA+F;YAC/F,wCAAwC;YACxC,OAAO,CAAC,IAAI,CAAC,iCAAiC,WAAW,2BAA4B,KAAe,EAAE,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC;YAC1H,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,QAAgB,UAAU;IACpE,OAAQ,SAAS,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,MAAM,CAAwB,IAAI,MAAM,CAAC;AAChF,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,QAAgB,UAAU;IACtE,OAAQ,SAAS,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,CAAwB,IAAI,MAAM,CAAC;AAClF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,SAAS,CAAC,UAAU,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -48,7 +48,9 @@ export interface FragmentCapabilities {
|
|
|
48
48
|
* Whose Web Storage the fragment sees. `'shared'` (the default) is the host origin's, as for any
|
|
49
49
|
* same-origin frame. `'partitioned'` gives it a store of its own, keyed by fragment id;
|
|
50
50
|
* `{ partition }` names a store several fragments share. See `compat/storage-partition.ts`.
|
|
51
|
-
*
|
|
51
|
+
* `{ databases: true }` (with or without `partition`) also gives the partition its own
|
|
52
|
+
* IndexedDB and Cache Storage — a Storage Bucket where the browser has them, prefixed names
|
|
53
|
+
* elsewhere. See `compat/database-partition.ts`. Compat fragments only; ignored elsewhere.
|
|
52
54
|
*/
|
|
53
55
|
storage?: StorageGrant;
|
|
54
56
|
/**
|
|
@@ -75,7 +77,9 @@ export interface FragmentCapabilities {
|
|
|
75
77
|
* whoever is wondering why `user` is undefined, so the *host's* console names the missing grant —
|
|
76
78
|
* host-side, because the host is where the grant is written.
|
|
77
79
|
*/
|
|
78
|
-
export declare function createContextGate(capabilities: FragmentCapabilities | undefined, fragmentId: string
|
|
80
|
+
export declare function createContextGate(capabilities: FragmentCapabilities | undefined, fragmentId: string, options?: {
|
|
81
|
+
untrusted?: boolean;
|
|
82
|
+
}): (key: string) => boolean;
|
|
79
83
|
/**
|
|
80
84
|
* Resolves capabilities from the three places a host can write them, most authoritative first:
|
|
81
85
|
*
|
|
@@ -100,7 +104,8 @@ export declare function resolveCapabilities(sources: Array<FragmentCapabilities
|
|
|
100
104
|
export declare function resolveSharedGlobals(capabilities: FragmentCapabilities | undefined): readonly string[];
|
|
101
105
|
/** A fragment's Web Storage grant. */
|
|
102
106
|
export type StorageGrant = 'shared' | 'partitioned' | {
|
|
103
|
-
partition
|
|
107
|
+
partition?: string;
|
|
108
|
+
databases?: boolean;
|
|
104
109
|
};
|
|
105
110
|
/**
|
|
106
111
|
* The storage partition a fragment writes into, or `null` for the shared, host-origin store.
|
|
@@ -109,5 +114,7 @@ export type StorageGrant = 'shared' | 'partitioned' | {
|
|
|
109
114
|
* two partitioned forms is the shared default — a typo must not quietly move a fragment's data.
|
|
110
115
|
*/
|
|
111
116
|
export declare function resolveStoragePartition(capabilities: FragmentCapabilities | undefined, fragmentId: string): string | null;
|
|
117
|
+
/** The partition whose IndexedDB and Cache Storage a fragment gets, or `null` for the origin's. */
|
|
118
|
+
export declare function resolveDatabasePartition(capabilities: FragmentCapabilities | undefined, fragmentId: string): string | null;
|
|
112
119
|
/** Reads a capabilities block off a realm stub's adapter options, or off `initBraid` config. */
|
|
113
120
|
export declare function readCapabilities(source: unknown): FragmentCapabilities | undefined;
|
|
@@ -11,8 +11,14 @@ import { getBraidConfig, isDevMode } from '../config.js';
|
|
|
11
11
|
* whoever is wondering why `user` is undefined, so the *host's* console names the missing grant —
|
|
12
12
|
* host-side, because the host is where the grant is written.
|
|
13
13
|
*/
|
|
14
|
-
export function createContextGate(capabilities, fragmentId) {
|
|
15
|
-
|
|
14
|
+
export function createContextGate(capabilities, fragmentId, options = {}) {
|
|
15
|
+
/**
|
|
16
|
+
* No grant means everything for a trusted fragment, which already holds the user's session, so
|
|
17
|
+
* that adding capabilities to one fragment does not starve the rest. An untrusted fragment is the
|
|
18
|
+
* opposite case: forgetting its grant must not hand a third party the user, the tenant and every
|
|
19
|
+
* token the host publishes, so it gets nothing until a grant names what it may read.
|
|
20
|
+
*/
|
|
21
|
+
const read = capabilities?.context?.read ?? (options.untrusted ? [] : undefined);
|
|
16
22
|
if (!read)
|
|
17
23
|
return () => true;
|
|
18
24
|
const granted = new Set(read);
|
|
@@ -75,12 +81,22 @@ export function resolveStoragePartition(capabilities, fragmentId) {
|
|
|
75
81
|
if (grant === 'partitioned')
|
|
76
82
|
return fragmentId;
|
|
77
83
|
if (grant && typeof grant === 'object') {
|
|
78
|
-
const partition = grant
|
|
84
|
+
const { partition, databases } = grant;
|
|
79
85
|
if (typeof partition === 'string' && partition !== '')
|
|
80
86
|
return partition;
|
|
87
|
+
// databases of its own imply a partition of its own, named for the fragment as 'partitioned' is
|
|
88
|
+
if (databases === true && partition === undefined)
|
|
89
|
+
return fragmentId;
|
|
81
90
|
}
|
|
82
91
|
return null;
|
|
83
92
|
}
|
|
93
|
+
/** The partition whose IndexedDB and Cache Storage a fragment gets, or `null` for the origin's. */
|
|
94
|
+
export function resolveDatabasePartition(capabilities, fragmentId) {
|
|
95
|
+
const grant = capabilities?.storage;
|
|
96
|
+
if (!grant || typeof grant !== 'object' || grant.databases !== true)
|
|
97
|
+
return null;
|
|
98
|
+
return resolveStoragePartition(capabilities, fragmentId);
|
|
99
|
+
}
|
|
84
100
|
/** Reads a capabilities block off a realm stub's adapter options, or off `initBraid` config. */
|
|
85
101
|
export function readCapabilities(source) {
|
|
86
102
|
if (!source || typeof source !== 'object')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/capabilities.ts"],"names":[],"mappings":"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"}
|
|
@@ -59,6 +59,8 @@ export interface AttachContextRouterOptions {
|
|
|
59
59
|
* subscription that never fires.
|
|
60
60
|
*/
|
|
61
61
|
capabilities?: FragmentCapabilities;
|
|
62
|
+
/** An untrusted fragment with no `context.read` grant reads nothing, not everything. */
|
|
63
|
+
untrusted?: boolean;
|
|
62
64
|
signal: AbortSignal;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
@@ -31,7 +31,7 @@ export const CTX_CHANGED = 'ctx/changed';
|
|
|
31
31
|
*/
|
|
32
32
|
export function attachContextRouter(options) {
|
|
33
33
|
const { bus, channel, fragmentId, contextVersions, signal } = options;
|
|
34
|
-
const mayRead = createContextGate(options.capabilities, fragmentId);
|
|
34
|
+
const mayRead = createContextGate(options.capabilities, fragmentId, { untrusted: options.untrusted === true });
|
|
35
35
|
const readFor = (key) => {
|
|
36
36
|
const as = contextVersions[key];
|
|
37
37
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-bridge.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/context-bridge.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"context-bridge.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/context-bridge.ts"],"names":[],"mappings":"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"}
|
package/src/weave/guest.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface GuestSession {
|
|
|
32
32
|
onPropsChanged(listener: (props: Readonly<Record<string, unknown>>) => void): () => void;
|
|
33
33
|
/** Fragment → host. Surfaced on the slot element as `braid:event`. */
|
|
34
34
|
emit(type: string, detail?: unknown): void;
|
|
35
|
+
/** Reports the fragment's first content as settled; see `FragmentEnv.settled`. */
|
|
36
|
+
settled(): void;
|
|
35
37
|
onClosing: ClosingCoordinator['onClosing'];
|
|
36
38
|
setDirty: ClosingCoordinator['setDirty'];
|
|
37
39
|
/** Aborts when the host closes the session. */
|
package/src/weave/guest.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createContextMirror } from './context-bridge.js';
|
|
|
6
6
|
import { createClosingCoordinator } from './closing.js';
|
|
7
7
|
import { answerHandshake } from './handshake.js';
|
|
8
8
|
import { startFragmentBeats } from './liveness.js';
|
|
9
|
-
import { FRAGMENT_EVENT, PROPS_CHANGED } from './messages.js';
|
|
9
|
+
import { FRAGMENT_EVENT, PROPS_CHANGED, SETTLED } from './messages.js';
|
|
10
10
|
const CONNECT_TIMEOUT_MS = 15_000;
|
|
11
11
|
/** Waits for a host to offer a port, then completes the Weave handshake over it. */
|
|
12
12
|
export async function connectToBraidHost(options) {
|
|
@@ -70,6 +70,7 @@ export async function connectToBraidHost(options) {
|
|
|
70
70
|
return () => void propsListeners.delete(listener);
|
|
71
71
|
},
|
|
72
72
|
emit: (type, detail) => channel.send(FRAGMENT_EVENT, { type, detail }),
|
|
73
|
+
settled: () => channel.send(SETTLED),
|
|
73
74
|
onClosing: closing.onClosing,
|
|
74
75
|
setDirty: closing.setDirty,
|
|
75
76
|
signal: controller.signal,
|
|
@@ -84,6 +85,10 @@ function acceptConnection(hostOrigin, timeoutMs) {
|
|
|
84
85
|
// the sender, which is what makes it worth checking and `event.data` worth distrusting.
|
|
85
86
|
if (event.origin !== hostOrigin)
|
|
86
87
|
return;
|
|
88
|
+
// And from the page that framed this one: another window on the host's origin, such as a
|
|
89
|
+
// trusted fragment's realm or a popup, must not be able to hand this fragment a channel.
|
|
90
|
+
if (event.source !== window.parent)
|
|
91
|
+
return;
|
|
87
92
|
const data = event.data;
|
|
88
93
|
if (data?.braid !== SANDBOX_CONNECT)
|
|
89
94
|
return;
|
|
@@ -107,11 +112,11 @@ function acceptConnection(hostOrigin, timeoutMs) {
|
|
|
107
112
|
}
|
|
108
113
|
window.addEventListener('message', onMessage);
|
|
109
114
|
/**
|
|
110
|
-
* Announced *after* the listener is attached,
|
|
115
|
+
* Announced *after* the listener is attached, because the host answers at once.
|
|
111
116
|
*
|
|
112
|
-
* The
|
|
113
|
-
*
|
|
114
|
-
*
|
|
117
|
+
* The announcement is what connects: this frame runs in an opaque origin, which the host cannot
|
|
118
|
+
* address by name, so it replies to the window that announced itself. The target origin still
|
|
119
|
+
* pins the host — a page that framed this one from any other origin never receives it.
|
|
115
120
|
*/
|
|
116
121
|
const ready = { braid: SANDBOX_READY, v: 1 };
|
|
117
122
|
window.parent?.postMessage(ready, hostOrigin);
|
package/src/weave/guest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guest.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/guest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,eAAe,EACf,aAAa,GAGd,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAmB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAsB,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAe,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAuB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"guest.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/guest.ts"],"names":[],"mappings":"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"}
|
package/src/weave/messages.d.ts
CHANGED
|
@@ -25,3 +25,27 @@ export interface FragmentEventPayload {
|
|
|
25
25
|
type: string;
|
|
26
26
|
detail?: unknown;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Fragment → host. The fragment's first content has settled: its own loading is done, not just its
|
|
30
|
+
* mount. Surfaced on the slot as the `settled` attribute and a `braid:settled` event.
|
|
31
|
+
*
|
|
32
|
+
* Separate from liveness on purpose. `healthy` proves the realm's event loop is turning, which is
|
|
33
|
+
* true from the first beat — while the application inside may still be showing its own spinner. Only
|
|
34
|
+
* the fragment knows when it has something worth looking at, so this is the fragment's to say, once.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SETTLED = "frag/settled";
|
|
37
|
+
/**
|
|
38
|
+
* Where a compat fragment finds its reporter: on its realm's global, under a registered symbol.
|
|
39
|
+
*
|
|
40
|
+
* A compat fragment is an unmodified app that never receives an env, so the one thing it may want
|
|
41
|
+
* to say has to be reachable from a global. A registered symbol rather than a name: it collides with
|
|
42
|
+
* nothing the app defines, and every copy of `@braidlabs/core` — the host's, and any bundled into
|
|
43
|
+
* the fragment — resolves the same one.
|
|
44
|
+
*/
|
|
45
|
+
export declare const BRAID_FRAGMENT_REPORTER: unique symbol;
|
|
46
|
+
export interface BraidFragmentReporter {
|
|
47
|
+
/** Reports the fragment's first content as settled. Later calls do nothing. */
|
|
48
|
+
settled(): void;
|
|
49
|
+
}
|
|
50
|
+
/** The reporter the realm this code runs in was given, or undefined outside a Braid realm. */
|
|
51
|
+
export declare function braidFragmentReporter(): BraidFragmentReporter | undefined;
|
package/src/weave/messages.js
CHANGED
|
@@ -10,4 +10,26 @@
|
|
|
10
10
|
export const PROPS_CHANGED = 'props/changed';
|
|
11
11
|
/** Fragment → host. Surfaced on the slot element as `braid:event`. */
|
|
12
12
|
export const FRAGMENT_EVENT = 'frag/event';
|
|
13
|
+
/**
|
|
14
|
+
* Fragment → host. The fragment's first content has settled: its own loading is done, not just its
|
|
15
|
+
* mount. Surfaced on the slot as the `settled` attribute and a `braid:settled` event.
|
|
16
|
+
*
|
|
17
|
+
* Separate from liveness on purpose. `healthy` proves the realm's event loop is turning, which is
|
|
18
|
+
* true from the first beat — while the application inside may still be showing its own spinner. Only
|
|
19
|
+
* the fragment knows when it has something worth looking at, so this is the fragment's to say, once.
|
|
20
|
+
*/
|
|
21
|
+
export const SETTLED = 'frag/settled';
|
|
22
|
+
/**
|
|
23
|
+
* Where a compat fragment finds its reporter: on its realm's global, under a registered symbol.
|
|
24
|
+
*
|
|
25
|
+
* A compat fragment is an unmodified app that never receives an env, so the one thing it may want
|
|
26
|
+
* to say has to be reachable from a global. A registered symbol rather than a name: it collides with
|
|
27
|
+
* nothing the app defines, and every copy of `@braidlabs/core` — the host's, and any bundled into
|
|
28
|
+
* the fragment — resolves the same one.
|
|
29
|
+
*/
|
|
30
|
+
export const BRAID_FRAGMENT_REPORTER = Symbol.for('braid.fragment');
|
|
31
|
+
/** The reporter the realm this code runs in was given, or undefined outside a Braid realm. */
|
|
32
|
+
export function braidFragmentReporter() {
|
|
33
|
+
return globalThis[BRAID_FRAGMENT_REPORTER];
|
|
34
|
+
}
|
|
13
35
|
//# sourceMappingURL=messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../../../libs/core/src/weave/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,sEAAsE;AACtE,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC;AAmB3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAOpE,8FAA8F;AAC9F,MAAM,UAAU,qBAAqB;IACnC,OAAQ,UAAgE,CAAC,uBAAuB,CAAC,CAAC;AACpG,CAAC"}
|