@secure-exec/browser 0.2.0-rc.1 → 0.3.0-rc.1

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/README.md CHANGED
@@ -1,7 +1,6 @@
1
- # Secure Exec
1
+ # Secure Exec Browser
2
2
 
3
- Secure Node.js execution without a sandbox. V8 isolate-based code execution with full Node.js and npm compatibility.
3
+ Browser driver primitives for secure-exec.
4
4
 
5
- - [Website](https://secureexec.dev)
6
- - [Documentation](https://secureexec.dev/docs)
7
- - [GitHub](https://github.com/rivet-dev/secure-exec)
5
+ - Package: `@secure-exec/browser`
6
+ - Exports: `createBrowserDriver`, `createBrowserRuntimeDriverFactory`, `createOpfsFileSystem`, `BrowserWorkerAdapter`
package/dist/driver.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { createCommandExecutorStub, createFsStub, createNetworkStub } from "@secure-exec/core";
2
- import type { Permissions, VirtualFileSystem } from "@secure-exec/core";
3
- import type { NetworkAdapter, SystemDriver } from "@secure-exec/core";
1
+ import type { NetworkAdapter, Permissions, SystemDriver, VirtualFileSystem } from "./runtime.js";
2
+ import { createCommandExecutorStub, createFsStub, createNetworkStub } from "./runtime.js";
4
3
  export interface BrowserRuntimeSystemOptions {
5
4
  filesystem: "opfs" | "memory";
6
5
  networkEnabled: boolean;
@@ -31,6 +30,9 @@ export declare class OpfsFileSystem implements VirtualFileSystem {
31
30
  stat(path: string): Promise<{
32
31
  mode: number;
33
32
  size: number;
33
+ blocks: number;
34
+ dev: number;
35
+ rdev: number;
34
36
  isDirectory: boolean;
35
37
  isSymbolicLink: boolean;
36
38
  atimeMs: number;
@@ -50,6 +52,9 @@ export declare class OpfsFileSystem implements VirtualFileSystem {
50
52
  lstat(path: string): Promise<{
51
53
  mode: number;
52
54
  size: number;
55
+ blocks: number;
56
+ dev: number;
57
+ rdev: number;
53
58
  isDirectory: boolean;
54
59
  isSymbolicLink: boolean;
55
60
  atimeMs: number;
@@ -68,6 +73,7 @@ export declare class OpfsFileSystem implements VirtualFileSystem {
68
73
  truncate(path: string, length: number): Promise<void>;
69
74
  realpath(path: string): Promise<string>;
70
75
  pread(path: string, offset: number, length: number): Promise<Uint8Array>;
76
+ pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;
71
77
  }
72
78
  export interface BrowserDriverOptions {
73
79
  filesystem?: "opfs" | "memory";
@@ -82,4 +88,4 @@ export declare function createBrowserNetworkAdapter(): NetworkAdapter;
82
88
  export declare function getBrowserSystemDriverOptions(systemDriver: SystemDriver): BrowserRuntimeSystemOptions;
83
89
  /** Assemble a browser-side SystemDriver with permission-wrapped adapters. */
84
90
  export declare function createBrowserDriver(options?: BrowserDriverOptions): Promise<SystemDriver>;
85
- export { createCommandExecutorStub, createFsStub, createNetworkStub, };
91
+ export { createCommandExecutorStub, createFsStub, createNetworkStub };
package/dist/driver.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createCommandExecutorStub, createFsStub, createNetworkStub, wrapFileSystem, wrapNetworkAdapter, createInMemoryFileSystem, createEnosysError, } from "@secure-exec/core";
1
+ import { createCommandExecutorStub, createEnosysError, createFsStub, createInMemoryFileSystem, createNetworkStub, wrapFileSystem, wrapNetworkAdapter, } from "./runtime.js";
2
2
  const S_IFREG = 0o100000;
3
3
  const S_IFDIR = 0o040000;
4
4
  const BROWSER_SYSTEM_DRIVER_OPTIONS = Symbol.for("secure-exec.browserSystemDriverOptions");
@@ -133,6 +133,9 @@ export class OpfsFileSystem {
133
133
  return {
134
134
  mode: S_IFREG | 0o644,
135
135
  size: file.size,
136
+ blocks: file.size === 0 ? 0 : Math.ceil(file.size / 512),
137
+ dev: 1,
138
+ rdev: 0,
136
139
  isDirectory: false,
137
140
  isSymbolicLink: false,
138
141
  atimeMs: file.lastModified,
@@ -153,6 +156,9 @@ export class OpfsFileSystem {
153
156
  return {
154
157
  mode: S_IFDIR | 0o755,
155
158
  size: 4096,
159
+ blocks: 8,
160
+ dev: 1,
161
+ rdev: 0,
156
162
  isDirectory: true,
157
163
  isSymbolicLink: false,
158
164
  atimeMs: now,
@@ -227,10 +233,19 @@ export class OpfsFileSystem {
227
233
  const data = await this.readFile(path);
228
234
  return data.slice(offset, offset + length);
229
235
  }
236
+ async pwrite(path, offset, data) {
237
+ const content = await this.readFile(path);
238
+ const endPos = offset + data.length;
239
+ const newContent = new Uint8Array(Math.max(content.length, endPos));
240
+ newContent.set(content);
241
+ newContent.set(data, offset);
242
+ await this.writeFile(path, newContent);
243
+ }
230
244
  }
231
245
  /** Create an OPFS-backed filesystem, falling back to in-memory if OPFS is unavailable. */
232
246
  export async function createOpfsFileSystem() {
233
- if (!("storage" in navigator) || typeof navigator.storage.getDirectory !== "function") {
247
+ if (!("storage" in navigator) ||
248
+ typeof navigator.storage.getDirectory !== "function") {
234
249
  return createInMemoryFileSystem();
235
250
  }
236
251
  return new OpfsFileSystem();
@@ -332,4 +347,4 @@ export async function createBrowserDriver(options = {}) {
332
347
  };
333
348
  return systemDriver;
334
349
  }
335
- export { createCommandExecutorStub, createFsStub, createNetworkStub, };
350
+ export { createCommandExecutorStub, createFsStub, createNetworkStub };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, } from "./driver.js";
2
1
  export type { BrowserDriverOptions, BrowserRuntimeSystemOptions, } from "./driver.js";
3
- export { createBrowserRuntimeDriverFactory, } from "./runtime-driver.js";
4
- export type { BrowserRuntimeDriverFactoryOptions, } from "./runtime-driver.js";
5
- export { createInMemoryFileSystem } from "@secure-exec/core";
2
+ export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, } from "./driver.js";
6
3
  export { InMemoryFileSystem } from "./os-filesystem.js";
7
- export { BrowserWorkerAdapter } from "./worker-adapter.js";
4
+ export type { ExecOptions, ExecResult, NodeRuntimeDriver, StdioChannel, StdioEvent, TimingMitigation, } from "./runtime.js";
5
+ export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "./runtime.js";
6
+ export type { BrowserRuntimeDriverFactoryOptions } from "./runtime-driver.js";
7
+ export { createBrowserRuntimeDriverFactory } from "./runtime-driver.js";
8
8
  export type { WorkerHandle } from "./worker-adapter.js";
9
+ export { BrowserWorkerAdapter } from "./worker-adapter.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createBrowserDriver, createBrowserNetworkAdapter, createOpfsFileSystem, } from "./driver.js";
2
- export { createBrowserRuntimeDriverFactory, } from "./runtime-driver.js";
3
- export { createInMemoryFileSystem } from "@secure-exec/core";
4
2
  export { InMemoryFileSystem } from "./os-filesystem.js";
3
+ export { allowAll, allowAllChildProcess, allowAllEnv, allowAllFs, allowAllNetwork, createInMemoryFileSystem, } from "./runtime.js";
4
+ export { createBrowserRuntimeDriverFactory } from "./runtime-driver.js";
5
5
  export { BrowserWorkerAdapter } from "./worker-adapter.js";
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * In-memory filesystem for browser environments.
3
3
  *
4
- * Expanded from the original secure-exec InMemoryFileSystem with POSIX
5
- * extensions (symlinks, hard links, chmod, chown, utimes, truncate)
6
- * needed by the kernel VFS interface.
4
+ * In-memory filesystem with POSIX extensions (symlinks, hard links, chmod,
5
+ * chown, utimes, truncate) needed by the kernel VFS interface.
7
6
  */
8
- import type { VirtualFileSystem, VirtualStat, VirtualDirEntry } from "@secure-exec/core";
7
+ import type { VirtualDirEntry, VirtualFileSystem, VirtualStat } from "./runtime.js";
9
8
  export declare class InMemoryFileSystem implements VirtualFileSystem {
10
9
  private entries;
11
10
  constructor();
@@ -33,6 +32,7 @@ export declare class InMemoryFileSystem implements VirtualFileSystem {
33
32
  utimes(path: string, atime: number, mtime: number): Promise<void>;
34
33
  truncate(path: string, length: number): Promise<void>;
35
34
  pread(path: string, offset: number, length: number): Promise<Uint8Array>;
35
+ pwrite(path: string, offset: number, data: Uint8Array): Promise<void>;
36
36
  /**
37
37
  * Resolve symlinks to get the final path. Returns the normalized path
38
38
  * after following all symlinks.
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * In-memory filesystem for browser environments.
3
3
  *
4
- * Expanded from the original secure-exec InMemoryFileSystem with POSIX
5
- * extensions (symlinks, hard links, chmod, chown, utimes, truncate)
6
- * needed by the kernel VFS interface.
4
+ * In-memory filesystem with POSIX extensions (symlinks, hard links, chmod,
5
+ * chown, utimes, truncate) needed by the kernel VFS interface.
7
6
  */
8
7
  const S_IFREG = 0o100000;
9
8
  const S_IFDIR = 0o040000;
@@ -30,13 +29,13 @@ function normalizePath(path) {
30
29
  resolved.push(part);
31
30
  }
32
31
  }
33
- return "/" + resolved.join("/") || "/";
32
+ return `/${resolved.join("/")}` || "/";
34
33
  }
35
34
  function dirname(path) {
36
35
  const parts = normalizePath(path).split("/").filter(Boolean);
37
36
  if (parts.length <= 1)
38
37
  return "/";
39
- return "/" + parts.slice(0, -1).join("/");
38
+ return `/${parts.slice(0, -1).join("/")}`;
40
39
  }
41
40
  let nextIno = 1;
42
41
  export class InMemoryFileSystem {
@@ -67,7 +66,7 @@ export class InMemoryFileSystem {
67
66
  if (!dir || dir.type !== "dir") {
68
67
  throw this.enoent("scandir", path);
69
68
  }
70
- const prefix = resolved === "/" ? "/" : resolved + "/";
69
+ const prefix = resolved === "/" ? "/" : `${resolved}/`;
71
70
  const names = new Map();
72
71
  for (const [entryPath, entry] of this.entries) {
73
72
  if (entryPath.startsWith(prefix)) {
@@ -87,9 +86,7 @@ export class InMemoryFileSystem {
87
86
  const normalized = normalizePath(path);
88
87
  // Ensure parent exists
89
88
  await this.mkdir(dirname(normalized), { recursive: true });
90
- const data = typeof content === "string"
91
- ? new TextEncoder().encode(content)
92
- : content;
89
+ const data = typeof content === "string" ? new TextEncoder().encode(content) : content;
93
90
  const existing = this.entries.get(normalized);
94
91
  if (existing && existing.type === "file") {
95
92
  existing.data = data;
@@ -129,7 +126,7 @@ export class InMemoryFileSystem {
129
126
  const parts = normalized.split("/").filter(Boolean);
130
127
  let current = "";
131
128
  for (const part of parts) {
132
- current += "/" + part;
129
+ current += `/${part}`;
133
130
  if (!this.entries.has(current)) {
134
131
  this.entries.set(current, this.newDir());
135
132
  }
@@ -172,7 +169,7 @@ export class InMemoryFileSystem {
172
169
  throw this.enoent("rmdir", path);
173
170
  }
174
171
  // Check if empty
175
- const prefix = resolved + "/";
172
+ const prefix = `${resolved}/`;
176
173
  for (const key of this.entries.keys()) {
177
174
  if (key.startsWith(prefix)) {
178
175
  throw new Error(`ENOTEMPTY: directory not empty, rmdir '${path}'`);
@@ -199,7 +196,7 @@ export class InMemoryFileSystem {
199
196
  return;
200
197
  }
201
198
  // Move directory and all children
202
- const prefix = oldResolved + "/";
199
+ const prefix = `${oldResolved}/`;
203
200
  const toMove = [];
204
201
  for (const [key, val] of this.entries) {
205
202
  if (key === oldResolved || key.startsWith(prefix)) {
@@ -210,9 +207,7 @@ export class InMemoryFileSystem {
210
207
  this.entries.delete(key);
211
208
  }
212
209
  for (const [key, val] of toMove) {
213
- const newKey = key === oldResolved
214
- ? newNorm
215
- : newNorm + key.slice(oldResolved.length);
210
+ const newKey = key === oldResolved ? newNorm : newNorm + key.slice(oldResolved.length);
216
211
  this.entries.set(newKey, val);
217
212
  }
218
213
  }
@@ -270,8 +265,13 @@ export class InMemoryFileSystem {
270
265
  const entry = this.resolveEntry(path);
271
266
  if (!entry)
272
267
  throw this.enoent("chmod", path);
273
- // Preserve file type bits, update permission bits
274
- entry.mode = (entry.mode & 0o170000) | (mode & 0o7777);
268
+ const callerTypeBits = mode & 0o170000;
269
+ if (callerTypeBits !== 0) {
270
+ entry.mode = mode;
271
+ }
272
+ else {
273
+ entry.mode = (entry.mode & 0o170000) | (mode & 0o7777);
274
+ }
275
275
  entry.ctimeMs = Date.now();
276
276
  }
277
277
  async chown(path, uid, gid) {
@@ -316,6 +316,20 @@ export class InMemoryFileSystem {
316
316
  return new Uint8Array(0);
317
317
  return entry.data.slice(offset, Math.min(offset + length, entry.data.length));
318
318
  }
319
+ async pwrite(path, offset, data) {
320
+ const entry = this.resolveEntry(path);
321
+ if (!entry || entry.type !== "file") {
322
+ throw this.enoent("open", path);
323
+ }
324
+ const endPos = offset + data.length;
325
+ const newContent = new Uint8Array(Math.max(entry.data.length, endPos));
326
+ newContent.set(entry.data);
327
+ newContent.set(data, offset);
328
+ entry.data = newContent;
329
+ const now = Date.now();
330
+ entry.mtimeMs = now;
331
+ entry.ctimeMs = now;
332
+ }
319
333
  // --- Helpers ---
320
334
  /**
321
335
  * Resolve symlinks to get the final path. Returns the normalized path
@@ -332,7 +346,7 @@ export class InMemoryFileSystem {
332
346
  if (entry.type === "symlink") {
333
347
  const target = entry.target.startsWith("/")
334
348
  ? entry.target
335
- : dirname(normalized) + "/" + entry.target;
349
+ : `${dirname(normalized)}/${entry.target}`;
336
350
  return this.resolvePath(target, depth + 1);
337
351
  }
338
352
  return normalized;
@@ -358,9 +372,13 @@ export class InMemoryFileSystem {
358
372
  };
359
373
  }
360
374
  toStat(entry) {
375
+ const size = entry.type === "file" ? entry.data.length : 4096;
361
376
  return {
362
377
  mode: entry.mode,
363
- size: entry.type === "file" ? entry.data.length : 4096,
378
+ size,
379
+ blocks: size === 0 ? 0 : Math.ceil(size / 512),
380
+ dev: 1,
381
+ rdev: 0,
364
382
  isDirectory: entry.type === "dir",
365
383
  isSymbolicLink: entry.type === "symlink",
366
384
  atimeMs: entry.atimeMs,
@@ -1,25 +1,33 @@
1
- import type { NetworkAdapter, NodeRuntimeDriver, NodeRuntimeDriverFactory, RuntimeDriverOptions } from "@secure-exec/core";
2
- import type { ExecOptions, ExecResult, RunResult } from "@secure-exec/core";
1
+ import type { ExecOptions, ExecResult, NetworkAdapter, NodeRuntimeDriver, NodeRuntimeDriverFactory, RunResult, RuntimeDriverOptions } from "./runtime.js";
3
2
  export interface BrowserRuntimeDriverFactoryOptions {
4
3
  workerUrl?: URL | string;
5
4
  }
6
5
  export declare class BrowserRuntimeDriver implements NodeRuntimeDriver {
7
- private readonly options;
8
6
  private readonly worker;
9
7
  private readonly pending;
8
+ private readonly controlToken;
10
9
  private readonly defaultOnStdio?;
10
+ private readonly defaultTimingMitigation;
11
11
  private readonly networkAdapter;
12
+ private readonly syncBridge;
13
+ private readonly syncFilesystem;
12
14
  private readonly ready;
15
+ private readonly encoder;
13
16
  private nextId;
14
17
  private disposed;
15
18
  constructor(options: RuntimeDriverOptions, factoryOptions?: BrowserRuntimeDriverFactoryOptions);
16
19
  get network(): Pick<NetworkAdapter, "fetch" | "dnsLookup" | "httpRequest">;
17
20
  private handleWorkerError;
18
21
  private handleWorkerMessage;
22
+ private handleSyncRequest;
19
23
  private rejectAllPending;
24
+ private clearWorkerHandlers;
25
+ private resetSyncBridgeState;
26
+ private cleanup;
20
27
  private callWorker;
21
28
  run<T = unknown>(code: string, filePath?: string): Promise<RunResult<T>>;
22
29
  exec(code: string, options?: ExecOptions): Promise<ExecResult>;
30
+ dispatchExtensionRequest(namespace: string, payload: Uint8Array): Promise<Uint8Array>;
23
31
  dispose(): void;
24
32
  terminate(): Promise<void>;
25
33
  }