sandboxedjs 0.1.29 → 0.1.30
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/LICENSE +1 -7
- package/README.md +52 -0
- package/dist/index.cjs +121 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -2
- package/dist/index.d.ts +125 -2
- package/dist/index.js +119 -13
- package/dist/index.js.map +1 -1
- package/dist/service-worker.js +112 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/worker-entry.js +10 -7
- package/dist/worker-entry.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -18,10 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
This package depends on @scelar/nodepod, which is licensed MIT with the
|
|
26
|
-
Commons Clause. Nodepod is not redistributed here; it is installed as a normal
|
|
27
|
-
npm dependency.
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -768,6 +768,58 @@ Two caveats worth knowing:
|
|
|
768
768
|
itself reports status correctly and can be relied on in `&&` chains.
|
|
769
769
|
- **No hardware acceleration and no native codecs** beyond what the WebAssembly build ships.
|
|
770
770
|
|
|
771
|
+
## Reaching a container's servers from the page
|
|
772
|
+
|
|
773
|
+
A server inside the container is not on the network, so there is no `http://localhost:5173`
|
|
774
|
+
your page can navigate to. There are three ways to reach one, and the right choice depends on
|
|
775
|
+
whether you need a whole site or a single response — and on whether you trust the code.
|
|
776
|
+
|
|
777
|
+
**1. One response, into a variable.** `request()` speaks to a server directly and hands back the
|
|
778
|
+
bytes. Nothing is executed, so there is nothing to be careful about.
|
|
779
|
+
|
|
780
|
+
```ts
|
|
781
|
+
const res = await box.request(5173, { path: "/api/items" });
|
|
782
|
+
element.textContent = res.body; // text
|
|
783
|
+
const items = res.json<Item[]>(); // parsed
|
|
784
|
+
const bytes = res.bytes; // exact bytes, for images and source maps
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
**2. One response, rendered — safely.** `renderInto` puts the response in an iframe with
|
|
788
|
+
`sandbox="allow-scripts"` and deliberately *without* `allow-same-origin`. The document lands in an
|
|
789
|
+
opaque origin: its scripts run, and they can reach neither your DOM nor your cookies and storage.
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
import { renderInto } from "sandboxedjs";
|
|
793
|
+
await renderInto(box, document.querySelector("#preview")!, { port: 5173 });
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
This is the option to reach for when the code came from somewhere you do not control. Its limit is
|
|
797
|
+
that only that one response exists — a page asking for `/main.js` gets nothing, because there is no
|
|
798
|
+
origin to serve it from. Good for generated HTML, a chart, a rendered document.
|
|
799
|
+
|
|
800
|
+
**3. A whole site, with real URLs.** `createPreview` registers a service worker that gives the
|
|
801
|
+
container's ports working URLs, so an iframe can load a dev server with all its subresources.
|
|
802
|
+
|
|
803
|
+
```ts
|
|
804
|
+
import { createPreview } from "sandboxedjs";
|
|
805
|
+
const preview = await createPreview(box); // null where service workers are unavailable
|
|
806
|
+
iframe.src = preview!.urlFor(5173);
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Requests are routed by *which client is asking* rather than by path, so a dev server's absolute
|
|
810
|
+
URLs — `/src/main.js`, `/@vite/client` — resolve without rewriting anything.
|
|
811
|
+
|
|
812
|
+
> **This one runs guest code on your origin.** A service worker can only serve URLs under the
|
|
813
|
+
> origin that registered it, so scripts in the preview can reach `window.parent`, your cookies and
|
|
814
|
+
> your `localStorage`. The sandbox contains a program's *filesystem and process table*, not the
|
|
815
|
+
> page it serves. Use it for code you trust; for anything else, serve the preview from a separate
|
|
816
|
+
> origin (a subdomain pointed at the same app) or stay with option 2.
|
|
817
|
+
|
|
818
|
+
Service workers need a secure context, and some embedded browsers disable them entirely — hence
|
|
819
|
+
the `null` return rather than a throw. Responses are served with both
|
|
820
|
+
`Cross-Origin-Resource-Policy` and `Cross-Origin-Embedder-Policy`, so a preview still frames
|
|
821
|
+
correctly inside the cross-origin isolated page that Rolldown requires.
|
|
822
|
+
|
|
771
823
|
## Isolation
|
|
772
824
|
|
|
773
825
|
By default each guest program runs on its own thread, in a Worker. The volume, the kernel, the
|
package/dist/index.cjs
CHANGED
|
@@ -19712,6 +19712,10 @@ var KernelChildProcess = class {
|
|
|
19712
19712
|
started = false;
|
|
19713
19713
|
cancelled = false;
|
|
19714
19714
|
constructor(kernel, cred, config, pid) {
|
|
19715
|
+
if (config.inheritStdio) {
|
|
19716
|
+
this.stdin.isTTY = true;
|
|
19717
|
+
this.stdin.interactive = true;
|
|
19718
|
+
}
|
|
19715
19719
|
this.kernel = kernel;
|
|
19716
19720
|
this.cred = cred;
|
|
19717
19721
|
this.pid = pid;
|
|
@@ -22116,7 +22120,8 @@ var ChildProcess = class extends EventEmitter4__default.default {
|
|
|
22116
22120
|
queueMicrotask(() => this.emit("close", code, null));
|
|
22117
22121
|
}
|
|
22118
22122
|
};
|
|
22119
|
-
function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
|
|
22123
|
+
function createChildProcessModule(spawnChild, defaultCwd, syncSpawn, defaultEnv = () => ({})) {
|
|
22124
|
+
const environmentFor = (options) => options.env ? { ...options.env } : defaultEnv();
|
|
22120
22125
|
const throughShell = (command, options) => {
|
|
22121
22126
|
const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
|
|
22122
22127
|
return { file: shell, args: ["-c", command] };
|
|
@@ -22127,7 +22132,7 @@ function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
|
|
|
22127
22132
|
command: resolved.file,
|
|
22128
22133
|
args: resolved.args,
|
|
22129
22134
|
cwd: options.cwd ?? defaultCwd(),
|
|
22130
|
-
|
|
22135
|
+
env: environmentFor(options)
|
|
22131
22136
|
});
|
|
22132
22137
|
return new ChildProcess(handle, resolved.file, resolved.args);
|
|
22133
22138
|
};
|
|
@@ -22173,11 +22178,11 @@ ${err.join("")}`),
|
|
|
22173
22178
|
exec,
|
|
22174
22179
|
execFile,
|
|
22175
22180
|
fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
|
|
22176
|
-
...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
|
|
22181
|
+
...buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor),
|
|
22177
22182
|
ChildProcess
|
|
22178
22183
|
};
|
|
22179
22184
|
}
|
|
22180
|
-
function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
|
|
22185
|
+
function buildSyncFamily(syncSpawn, throughShell, defaultCwd, environmentFor) {
|
|
22181
22186
|
if (!syncSpawn) {
|
|
22182
22187
|
return {
|
|
22183
22188
|
execSync: unavailable("execSync"),
|
|
@@ -22188,12 +22193,14 @@ function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
|
|
|
22188
22193
|
const run = (file3, args, options) => {
|
|
22189
22194
|
const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
|
|
22190
22195
|
const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
|
|
22196
|
+
const inherit = options.stdio === "inherit" || Array.isArray(options.stdio) && options.stdio[0] === "inherit";
|
|
22191
22197
|
return syncSpawn({
|
|
22192
22198
|
command: resolved.file,
|
|
22193
22199
|
args: resolved.args,
|
|
22194
22200
|
cwd: options.cwd ?? defaultCwd(),
|
|
22195
|
-
|
|
22196
|
-
...input === void 0 ? {} : { input }
|
|
22201
|
+
env: environmentFor(options),
|
|
22202
|
+
...input === void 0 ? {} : { input },
|
|
22203
|
+
...inherit ? { inheritStdio: true } : {}
|
|
22197
22204
|
});
|
|
22198
22205
|
};
|
|
22199
22206
|
const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
|
|
@@ -22873,7 +22880,7 @@ function createCoreModules(options) {
|
|
|
22873
22880
|
};
|
|
22874
22881
|
const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
|
|
22875
22882
|
const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
|
|
22876
|
-
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
|
|
22883
|
+
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn, () => ({ ...processObject.env })) : createUnsupportedModule("child_process");
|
|
22877
22884
|
const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
|
|
22878
22885
|
const dns = createDnsModule();
|
|
22879
22886
|
const builtins = {
|
|
@@ -24118,8 +24125,10 @@ var MirroringVolume = class {
|
|
|
24118
24125
|
* container pays nothing for this.
|
|
24119
24126
|
*/
|
|
24120
24127
|
attach(mirror, root) {
|
|
24128
|
+
const cleanRoot = clean(root);
|
|
24129
|
+
if (this.mirror === mirror && this.root === cleanRoot) return;
|
|
24121
24130
|
this.mirror = mirror;
|
|
24122
|
-
this.root =
|
|
24131
|
+
this.root = cleanRoot;
|
|
24123
24132
|
this.seed();
|
|
24124
24133
|
}
|
|
24125
24134
|
detach() {
|
|
@@ -25254,9 +25263,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25254
25263
|
...this.workerUrl ? { url: this.workerUrl } : {},
|
|
25255
25264
|
timeoutMs: 15e3
|
|
25256
25265
|
});
|
|
25266
|
+
const streams = { target: null };
|
|
25257
25267
|
const server = new SyncChannelServer(buffers, serveSyncSyscalls({
|
|
25258
25268
|
volume: this.volume,
|
|
25259
|
-
spawnChild: (request) => this.runChildToCompletion(request)
|
|
25269
|
+
spawnChild: (request) => this.runChildToCompletion(request, streams.target)
|
|
25260
25270
|
}));
|
|
25261
25271
|
const entry = { worker, server };
|
|
25262
25272
|
this.live.add(entry);
|
|
@@ -25266,6 +25276,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25266
25276
|
this.closeProxies(owner);
|
|
25267
25277
|
void worker.terminate();
|
|
25268
25278
|
});
|
|
25279
|
+
streams.target = process2;
|
|
25269
25280
|
const children = /* @__PURE__ */ new Map();
|
|
25270
25281
|
worker.onMessage((raw) => {
|
|
25271
25282
|
const message = raw;
|
|
@@ -25332,7 +25343,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25332
25343
|
handle.exec();
|
|
25333
25344
|
}
|
|
25334
25345
|
/** Run a child to completion and collect it, for the guest's `spawnSync`. */
|
|
25335
|
-
runChildToCompletion(request) {
|
|
25346
|
+
runChildToCompletion(request, streamTo) {
|
|
25336
25347
|
return new Promise((resolve2) => {
|
|
25337
25348
|
let handle;
|
|
25338
25349
|
try {
|
|
@@ -25340,7 +25351,8 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25340
25351
|
command: request.command,
|
|
25341
25352
|
args: request.args,
|
|
25342
25353
|
cwd: request.cwd,
|
|
25343
|
-
...request.env ? { env: request.env } : {}
|
|
25354
|
+
...request.env ? { env: request.env } : {},
|
|
25355
|
+
...request.inheritStdio ? { inheritStdio: true } : {}
|
|
25344
25356
|
});
|
|
25345
25357
|
} catch (error) {
|
|
25346
25358
|
const failure = error;
|
|
@@ -25349,18 +25361,21 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
|
25349
25361
|
}
|
|
25350
25362
|
let stdout = "";
|
|
25351
25363
|
let stderr = "";
|
|
25364
|
+
const live = request.inheritStdio ? streamTo : null;
|
|
25352
25365
|
handle.on("stdout", (text2) => {
|
|
25353
25366
|
stdout += text2;
|
|
25367
|
+
live?.output(text2);
|
|
25354
25368
|
});
|
|
25355
25369
|
handle.on("stderr", (text2) => {
|
|
25356
25370
|
stderr += text2;
|
|
25371
|
+
live?.error(text2);
|
|
25357
25372
|
});
|
|
25358
25373
|
handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
|
|
25359
25374
|
handle.exec();
|
|
25360
25375
|
if (request.input !== void 0) {
|
|
25361
25376
|
handle.sendStdin?.(request.input);
|
|
25362
25377
|
}
|
|
25363
|
-
handle.endStdin?.();
|
|
25378
|
+
if (!request.inheritStdio) handle.endStdin?.();
|
|
25364
25379
|
});
|
|
25365
25380
|
}
|
|
25366
25381
|
// ── HTTP servers living on another thread ─────────────────────────────────
|
|
@@ -26361,6 +26376,97 @@ init_variables();
|
|
|
26361
26376
|
init_arith();
|
|
26362
26377
|
init_expand();
|
|
26363
26378
|
init_builtins();
|
|
26379
|
+
|
|
26380
|
+
// src/preview/register.ts
|
|
26381
|
+
function serveContainerOn(port, box) {
|
|
26382
|
+
port.onmessage = async (event) => {
|
|
26383
|
+
const request = event.data;
|
|
26384
|
+
try {
|
|
26385
|
+
const response = await box.request(request.port, {
|
|
26386
|
+
method: request.method,
|
|
26387
|
+
path: request.path,
|
|
26388
|
+
headers: request.headers,
|
|
26389
|
+
...request.body ? { body: new Uint8Array(request.body) } : {}
|
|
26390
|
+
});
|
|
26391
|
+
const bytes2 = response.bytes.slice();
|
|
26392
|
+
port.postMessage(
|
|
26393
|
+
{
|
|
26394
|
+
id: request.id,
|
|
26395
|
+
response: {
|
|
26396
|
+
status: response.status,
|
|
26397
|
+
statusText: response.statusText,
|
|
26398
|
+
headers: response.headers,
|
|
26399
|
+
body: bytes2.buffer
|
|
26400
|
+
}
|
|
26401
|
+
},
|
|
26402
|
+
[bytes2.buffer]
|
|
26403
|
+
);
|
|
26404
|
+
} catch (error) {
|
|
26405
|
+
const message = new TextEncoder().encode(error instanceof Error ? error.message : String(error));
|
|
26406
|
+
port.postMessage({
|
|
26407
|
+
id: request.id,
|
|
26408
|
+
response: { status: 502, statusText: "Bad Gateway", headers: {}, body: message.buffer }
|
|
26409
|
+
});
|
|
26410
|
+
}
|
|
26411
|
+
};
|
|
26412
|
+
port.start?.();
|
|
26413
|
+
}
|
|
26414
|
+
async function createPreview(box, options = {}) {
|
|
26415
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return null;
|
|
26416
|
+
const scriptUrl = options.scriptUrl ?? new URL("./service-worker.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
26417
|
+
let registration;
|
|
26418
|
+
try {
|
|
26419
|
+
registration = await navigator.serviceWorker.register(scriptUrl, {
|
|
26420
|
+
type: "module",
|
|
26421
|
+
...options.scope ? { scope: options.scope } : {}
|
|
26422
|
+
});
|
|
26423
|
+
} catch {
|
|
26424
|
+
return null;
|
|
26425
|
+
}
|
|
26426
|
+
const worker = registration.active ?? registration.waiting ?? registration.installing;
|
|
26427
|
+
if (!worker) return null;
|
|
26428
|
+
if (worker.state !== "activated") {
|
|
26429
|
+
const activated = await new Promise((resolve2) => {
|
|
26430
|
+
const check = () => {
|
|
26431
|
+
if (worker.state === "activated") {
|
|
26432
|
+
worker.removeEventListener("statechange", check);
|
|
26433
|
+
resolve2(true);
|
|
26434
|
+
} else if (worker.state === "redundant") {
|
|
26435
|
+
worker.removeEventListener("statechange", check);
|
|
26436
|
+
resolve2(false);
|
|
26437
|
+
}
|
|
26438
|
+
};
|
|
26439
|
+
worker.addEventListener("statechange", check);
|
|
26440
|
+
setTimeout(() => resolve2(worker.state === "activated"), 1e4);
|
|
26441
|
+
check();
|
|
26442
|
+
});
|
|
26443
|
+
if (!activated) return null;
|
|
26444
|
+
}
|
|
26445
|
+
const channel = new MessageChannel();
|
|
26446
|
+
serveContainerOn(channel.port1, box);
|
|
26447
|
+
worker.postMessage({ type: "sandboxedjs:connect" }, [channel.port2]);
|
|
26448
|
+
const base2 = registration.scope.replace(/\/$/, "");
|
|
26449
|
+
return {
|
|
26450
|
+
urlFor: (port) => `${base2}/__sbx__/${port}/`,
|
|
26451
|
+
dispose: async () => {
|
|
26452
|
+
channel.port1.close();
|
|
26453
|
+
await registration.unregister();
|
|
26454
|
+
}
|
|
26455
|
+
};
|
|
26456
|
+
}
|
|
26457
|
+
async function renderInto(box, element, options = { port: 80 }) {
|
|
26458
|
+
const response = await box.request(options.port, { path: options.path ?? "/" });
|
|
26459
|
+
const frame = document.createElement("iframe");
|
|
26460
|
+
frame.setAttribute("sandbox", "allow-scripts");
|
|
26461
|
+
frame.style.width = "100%";
|
|
26462
|
+
frame.style.height = "100%";
|
|
26463
|
+
frame.style.border = "0";
|
|
26464
|
+
frame.srcdoc = response.body;
|
|
26465
|
+
element.replaceChildren(frame);
|
|
26466
|
+
return frame;
|
|
26467
|
+
}
|
|
26468
|
+
|
|
26469
|
+
// src/index.ts
|
|
26364
26470
|
var src_default = createContainer;
|
|
26365
26471
|
|
|
26366
26472
|
exports.BufferSink = BufferSink;
|
|
@@ -26404,6 +26510,7 @@ exports.VirtualHttpServer = VirtualHttpServer;
|
|
|
26404
26510
|
exports.VirtualIncomingMessage = VirtualIncomingMessage;
|
|
26405
26511
|
exports.VirtualServerResponse = VirtualServerResponse;
|
|
26406
26512
|
exports.WASM_ALIASES = WASM_ALIASES;
|
|
26513
|
+
exports.WorkerRuntimePod = WorkerRuntimePod;
|
|
26407
26514
|
exports.allCommands = allCommands;
|
|
26408
26515
|
exports.applyChmod = applyChmod;
|
|
26409
26516
|
exports.braceExpand = braceExpand;
|
|
@@ -26416,6 +26523,7 @@ exports.createChildProcessModule = createChildProcessModule;
|
|
|
26416
26523
|
exports.createContainer = createContainer;
|
|
26417
26524
|
exports.createContext = createContext;
|
|
26418
26525
|
exports.createCoreModules = createCoreModules;
|
|
26526
|
+
exports.createPreview = createPreview;
|
|
26419
26527
|
exports.default = src_default;
|
|
26420
26528
|
exports.defineCommand = defineCommand;
|
|
26421
26529
|
exports.evalArith = evalArith;
|
|
@@ -26442,6 +26550,7 @@ exports.octalMode = octalMode;
|
|
|
26442
26550
|
exports.parseShell = parse;
|
|
26443
26551
|
exports.parseUmask = parseUmask;
|
|
26444
26552
|
exports.posixPath = path_exports;
|
|
26553
|
+
exports.renderInto = renderInto;
|
|
26445
26554
|
exports.resetPidCounter = resetPidCounter;
|
|
26446
26555
|
exports.shellQuote = shellQuote;
|
|
26447
26556
|
exports.startRuntimeWorker = startRuntimeWorker;
|