@run0/jiki 0.1.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.
Files changed (152) hide show
  1. package/dist/browser-bundle.d.ts +40 -0
  2. package/dist/builtins.d.ts +22 -0
  3. package/dist/code-transform.d.ts +7 -0
  4. package/dist/config/cdn.d.ts +13 -0
  5. package/dist/container.d.ts +101 -0
  6. package/dist/dev-server.d.ts +69 -0
  7. package/dist/errors.d.ts +19 -0
  8. package/dist/frameworks/code-transforms.d.ts +32 -0
  9. package/dist/frameworks/next-api-handler.d.ts +72 -0
  10. package/dist/frameworks/next-dev-server.d.ts +141 -0
  11. package/dist/frameworks/next-html-generator.d.ts +36 -0
  12. package/dist/frameworks/next-route-resolver.d.ts +19 -0
  13. package/dist/frameworks/next-shims.d.ts +78 -0
  14. package/dist/frameworks/remix-dev-server.d.ts +47 -0
  15. package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
  16. package/dist/frameworks/vite-dev-server.d.ts +50 -0
  17. package/dist/fs-errors.d.ts +36 -0
  18. package/dist/index.cjs +14916 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +61 -0
  21. package/dist/index.mjs +14898 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/kernel.d.ts +48 -0
  24. package/dist/memfs.d.ts +144 -0
  25. package/dist/metrics.d.ts +78 -0
  26. package/dist/module-resolver.d.ts +60 -0
  27. package/dist/network-interceptor.d.ts +71 -0
  28. package/dist/npm/cache.d.ts +76 -0
  29. package/dist/npm/index.d.ts +60 -0
  30. package/dist/npm/lockfile-reader.d.ts +32 -0
  31. package/dist/npm/pnpm.d.ts +18 -0
  32. package/dist/npm/registry.d.ts +45 -0
  33. package/dist/npm/resolver.d.ts +39 -0
  34. package/dist/npm/sync-installer.d.ts +18 -0
  35. package/dist/npm/tarball.d.ts +4 -0
  36. package/dist/npm/workspaces.d.ts +46 -0
  37. package/dist/persistence.d.ts +94 -0
  38. package/dist/plugin.d.ts +156 -0
  39. package/dist/polyfills/assert.d.ts +30 -0
  40. package/dist/polyfills/child_process.d.ts +116 -0
  41. package/dist/polyfills/chokidar.d.ts +18 -0
  42. package/dist/polyfills/crypto.d.ts +49 -0
  43. package/dist/polyfills/events.d.ts +28 -0
  44. package/dist/polyfills/fs.d.ts +82 -0
  45. package/dist/polyfills/http.d.ts +147 -0
  46. package/dist/polyfills/module.d.ts +29 -0
  47. package/dist/polyfills/net.d.ts +53 -0
  48. package/dist/polyfills/os.d.ts +91 -0
  49. package/dist/polyfills/path.d.ts +96 -0
  50. package/dist/polyfills/perf_hooks.d.ts +21 -0
  51. package/dist/polyfills/process.d.ts +99 -0
  52. package/dist/polyfills/querystring.d.ts +15 -0
  53. package/dist/polyfills/readdirp.d.ts +18 -0
  54. package/dist/polyfills/readline.d.ts +32 -0
  55. package/dist/polyfills/stream.d.ts +106 -0
  56. package/dist/polyfills/stubs.d.ts +737 -0
  57. package/dist/polyfills/tty.d.ts +25 -0
  58. package/dist/polyfills/url.d.ts +41 -0
  59. package/dist/polyfills/util.d.ts +61 -0
  60. package/dist/polyfills/v8.d.ts +43 -0
  61. package/dist/polyfills/vm.d.ts +76 -0
  62. package/dist/polyfills/worker-threads.d.ts +77 -0
  63. package/dist/polyfills/ws.d.ts +32 -0
  64. package/dist/polyfills/zlib.d.ts +87 -0
  65. package/dist/runtime-helpers.d.ts +4 -0
  66. package/dist/runtime-interface.d.ts +39 -0
  67. package/dist/sandbox.d.ts +69 -0
  68. package/dist/server-bridge.d.ts +55 -0
  69. package/dist/shell-commands.d.ts +2 -0
  70. package/dist/shell.d.ts +101 -0
  71. package/dist/transpiler.d.ts +47 -0
  72. package/dist/type-checker.d.ts +57 -0
  73. package/dist/types/package-json.d.ts +17 -0
  74. package/dist/utils/binary-encoding.d.ts +4 -0
  75. package/dist/utils/hash.d.ts +6 -0
  76. package/dist/utils/safe-path.d.ts +6 -0
  77. package/dist/worker-runtime.d.ts +34 -0
  78. package/package.json +59 -0
  79. package/src/browser-bundle.ts +498 -0
  80. package/src/builtins.ts +222 -0
  81. package/src/code-transform.ts +183 -0
  82. package/src/config/cdn.ts +17 -0
  83. package/src/container.ts +343 -0
  84. package/src/dev-server.ts +322 -0
  85. package/src/errors.ts +604 -0
  86. package/src/frameworks/code-transforms.ts +667 -0
  87. package/src/frameworks/next-api-handler.ts +366 -0
  88. package/src/frameworks/next-dev-server.ts +1252 -0
  89. package/src/frameworks/next-html-generator.ts +585 -0
  90. package/src/frameworks/next-route-resolver.ts +521 -0
  91. package/src/frameworks/next-shims.ts +1084 -0
  92. package/src/frameworks/remix-dev-server.ts +163 -0
  93. package/src/frameworks/sveltekit-dev-server.ts +197 -0
  94. package/src/frameworks/vite-dev-server.ts +370 -0
  95. package/src/fs-errors.ts +118 -0
  96. package/src/index.ts +188 -0
  97. package/src/kernel.ts +381 -0
  98. package/src/memfs.ts +1006 -0
  99. package/src/metrics.ts +140 -0
  100. package/src/module-resolver.ts +511 -0
  101. package/src/network-interceptor.ts +143 -0
  102. package/src/npm/cache.ts +172 -0
  103. package/src/npm/index.ts +377 -0
  104. package/src/npm/lockfile-reader.ts +105 -0
  105. package/src/npm/pnpm.ts +108 -0
  106. package/src/npm/registry.ts +120 -0
  107. package/src/npm/resolver.ts +339 -0
  108. package/src/npm/sync-installer.ts +217 -0
  109. package/src/npm/tarball.ts +136 -0
  110. package/src/npm/workspaces.ts +255 -0
  111. package/src/persistence.ts +235 -0
  112. package/src/plugin.ts +293 -0
  113. package/src/polyfills/assert.ts +164 -0
  114. package/src/polyfills/child_process.ts +535 -0
  115. package/src/polyfills/chokidar.ts +52 -0
  116. package/src/polyfills/crypto.ts +433 -0
  117. package/src/polyfills/events.ts +178 -0
  118. package/src/polyfills/fs.ts +297 -0
  119. package/src/polyfills/http.ts +478 -0
  120. package/src/polyfills/module.ts +97 -0
  121. package/src/polyfills/net.ts +123 -0
  122. package/src/polyfills/os.ts +108 -0
  123. package/src/polyfills/path.ts +169 -0
  124. package/src/polyfills/perf_hooks.ts +30 -0
  125. package/src/polyfills/process.ts +349 -0
  126. package/src/polyfills/querystring.ts +66 -0
  127. package/src/polyfills/readdirp.ts +72 -0
  128. package/src/polyfills/readline.ts +80 -0
  129. package/src/polyfills/stream.ts +610 -0
  130. package/src/polyfills/stubs.ts +600 -0
  131. package/src/polyfills/tty.ts +43 -0
  132. package/src/polyfills/url.ts +97 -0
  133. package/src/polyfills/util.ts +173 -0
  134. package/src/polyfills/v8.ts +62 -0
  135. package/src/polyfills/vm.ts +111 -0
  136. package/src/polyfills/worker-threads.ts +189 -0
  137. package/src/polyfills/ws.ts +73 -0
  138. package/src/polyfills/zlib.ts +244 -0
  139. package/src/runtime-helpers.ts +83 -0
  140. package/src/runtime-interface.ts +46 -0
  141. package/src/sandbox.ts +178 -0
  142. package/src/server-bridge.ts +473 -0
  143. package/src/service-worker.ts +153 -0
  144. package/src/shell-commands.ts +708 -0
  145. package/src/shell.ts +795 -0
  146. package/src/transpiler.ts +282 -0
  147. package/src/type-checker.ts +241 -0
  148. package/src/types/package-json.ts +17 -0
  149. package/src/utils/binary-encoding.ts +38 -0
  150. package/src/utils/hash.ts +24 -0
  151. package/src/utils/safe-path.ts +38 -0
  152. package/src/worker-runtime.ts +42 -0
@@ -0,0 +1,244 @@
1
+ import pako from "pako";
2
+ import { BufferImpl, Transform } from "./stream";
3
+
4
+ export function gzipSync(data: string | Uint8Array): BufferImpl {
5
+ const input =
6
+ typeof data === "string" ? new TextEncoder().encode(data) : data;
7
+ return BufferImpl.from(pako.gzip(input));
8
+ }
9
+
10
+ export function gunzipSync(data: Uint8Array): BufferImpl {
11
+ return BufferImpl.from(pako.ungzip(data));
12
+ }
13
+
14
+ export function deflateSync(data: string | Uint8Array): BufferImpl {
15
+ const input =
16
+ typeof data === "string" ? new TextEncoder().encode(data) : data;
17
+ return BufferImpl.from(pako.deflate(input));
18
+ }
19
+
20
+ export function inflateSync(data: Uint8Array): BufferImpl {
21
+ return BufferImpl.from(pako.inflate(data));
22
+ }
23
+
24
+ export function deflateRawSync(data: string | Uint8Array): BufferImpl {
25
+ const input =
26
+ typeof data === "string" ? new TextEncoder().encode(data) : data;
27
+ return BufferImpl.from(pako.deflateRaw(input));
28
+ }
29
+
30
+ export function inflateRawSync(data: Uint8Array): BufferImpl {
31
+ return BufferImpl.from(pako.inflateRaw(data));
32
+ }
33
+
34
+ export function gzip(
35
+ data: string | Uint8Array,
36
+ cb: (err: Error | null, result?: BufferImpl) => void,
37
+ ): void {
38
+ try {
39
+ cb(null, gzipSync(data));
40
+ } catch (e) {
41
+ cb(e as Error);
42
+ }
43
+ }
44
+
45
+ export function gunzip(
46
+ data: Uint8Array,
47
+ cb: (err: Error | null, result?: BufferImpl) => void,
48
+ ): void {
49
+ try {
50
+ cb(null, gunzipSync(data));
51
+ } catch (e) {
52
+ cb(e as Error);
53
+ }
54
+ }
55
+
56
+ export function deflate(
57
+ data: string | Uint8Array,
58
+ cb: (err: Error | null, result?: BufferImpl) => void,
59
+ ): void {
60
+ try {
61
+ cb(null, deflateSync(data));
62
+ } catch (e) {
63
+ cb(e as Error);
64
+ }
65
+ }
66
+
67
+ export function inflate(
68
+ data: Uint8Array,
69
+ cb: (err: Error | null, result?: BufferImpl) => void,
70
+ ): void {
71
+ try {
72
+ cb(null, inflateSync(data));
73
+ } catch (e) {
74
+ cb(e as Error);
75
+ }
76
+ }
77
+
78
+ export function unzipSync(data: Uint8Array): BufferImpl {
79
+ try {
80
+ return gunzipSync(data);
81
+ } catch {
82
+ return inflateSync(data);
83
+ }
84
+ }
85
+
86
+ export function unzip(
87
+ data: Uint8Array,
88
+ cb: (err: Error | null, result?: BufferImpl) => void,
89
+ ): void {
90
+ try {
91
+ cb(null, unzipSync(data));
92
+ } catch (e) {
93
+ cb(e as Error);
94
+ }
95
+ }
96
+
97
+ class ZlibTransform extends Transform {
98
+ private processor: pako.Deflate | pako.Inflate;
99
+ private _chunks: Uint8Array[] = [];
100
+
101
+ constructor(processor: pako.Deflate | pako.Inflate) {
102
+ super();
103
+ this.processor = processor;
104
+ // pako calls onData for each output chunk during push()
105
+ (this.processor as any).onData = (chunk: Uint8Array) => {
106
+ this._chunks.push(chunk);
107
+ };
108
+ }
109
+
110
+ _transform(
111
+ chunk: unknown,
112
+ _encoding: string,
113
+ callback: (err?: Error | null, data?: unknown) => void,
114
+ ): void {
115
+ try {
116
+ const input =
117
+ chunk instanceof Uint8Array
118
+ ? chunk
119
+ : new TextEncoder().encode(String(chunk));
120
+ this._chunks = [];
121
+ this.processor.push(input, false);
122
+ // Emit any chunks produced by onData callback
123
+ for (const c of this._chunks) {
124
+ this.push(c);
125
+ }
126
+ callback();
127
+ } catch (err) {
128
+ callback(err instanceof Error ? err : new Error(String(err)));
129
+ }
130
+ }
131
+
132
+ _flush(callback: (err?: Error | null, data?: unknown) => void): void {
133
+ try {
134
+ this._chunks = [];
135
+ this.processor.push(new Uint8Array(0), true); // finalize
136
+ // Emit final chunks produced by onData callback
137
+ for (const c of this._chunks) {
138
+ this.push(c);
139
+ }
140
+ callback();
141
+ } catch (err) {
142
+ callback(err instanceof Error ? err : new Error(String(err)));
143
+ }
144
+ }
145
+
146
+ end(
147
+ chunkOrCb?: unknown,
148
+ encodingOrCb?: string | (() => void),
149
+ cb?: () => void,
150
+ ): this {
151
+ if (this.writableEnded) return this;
152
+ // Handle the case where first arg is a callback
153
+ if (typeof chunkOrCb === "function") {
154
+ cb = chunkOrCb as () => void;
155
+ chunkOrCb = undefined;
156
+ }
157
+ this.writableEnded = true;
158
+ // Write any final chunk
159
+ if (chunkOrCb != null) {
160
+ this.write(chunkOrCb as string | Uint8Array, encodingOrCb as string);
161
+ }
162
+ // Call _flush to finalize compression/decompression
163
+ this._flush(err => {
164
+ if (err) {
165
+ this.emit("error", err);
166
+ }
167
+ // Signal end of readable stream
168
+ this.push(null);
169
+ this.writableFinished = true;
170
+ this.emit("finish");
171
+ this.emit("close");
172
+ if (cb) cb();
173
+ if (typeof encodingOrCb === "function") encodingOrCb();
174
+ });
175
+ return this;
176
+ }
177
+ }
178
+
179
+ export function createGzip() {
180
+ return new ZlibTransform(new pako.Deflate({ gzip: true }));
181
+ }
182
+ export function createGunzip() {
183
+ return new ZlibTransform(new pako.Inflate());
184
+ }
185
+ export function createDeflate() {
186
+ return new ZlibTransform(new pako.Deflate());
187
+ }
188
+ export function createInflate() {
189
+ return new ZlibTransform(new pako.Inflate());
190
+ }
191
+
192
+ export function brotliCompressSync(data: string | Uint8Array): BufferImpl {
193
+ const input =
194
+ typeof data === "string" ? new TextEncoder().encode(data) : data;
195
+ // Use pako deflate as brotli substitute (actual brotli needs wasm/native)
196
+ const compressed = pako.deflate(input);
197
+ return BufferImpl.from(compressed);
198
+ }
199
+ export function brotliDecompressSync(data: Uint8Array): BufferImpl {
200
+ const decompressed = pako.inflate(data);
201
+ return BufferImpl.from(decompressed);
202
+ }
203
+
204
+ export const constants = {
205
+ Z_NO_FLUSH: 0,
206
+ Z_PARTIAL_FLUSH: 1,
207
+ Z_SYNC_FLUSH: 2,
208
+ Z_FULL_FLUSH: 3,
209
+ Z_FINISH: 4,
210
+ Z_OK: 0,
211
+ Z_STREAM_END: 1,
212
+ Z_NEED_DICT: 2,
213
+ Z_ERRNO: -1,
214
+ Z_STREAM_ERROR: -2,
215
+ Z_DEFAULT_COMPRESSION: -1,
216
+ Z_BEST_SPEED: 1,
217
+ Z_BEST_COMPRESSION: 9,
218
+ Z_NO_COMPRESSION: 0,
219
+ BROTLI_OPERATION_PROCESS: 0,
220
+ BROTLI_OPERATION_FLUSH: 1,
221
+ BROTLI_OPERATION_FINISH: 2,
222
+ };
223
+
224
+ export default {
225
+ gzip,
226
+ gzipSync,
227
+ gunzip,
228
+ gunzipSync,
229
+ deflate,
230
+ deflateSync,
231
+ inflate,
232
+ inflateSync,
233
+ deflateRawSync,
234
+ inflateRawSync,
235
+ unzip,
236
+ unzipSync,
237
+ brotliCompressSync,
238
+ brotliDecompressSync,
239
+ createGzip,
240
+ createGunzip,
241
+ createDeflate,
242
+ createInflate,
243
+ constants,
244
+ };
@@ -0,0 +1,83 @@
1
+ import type { RequireFunction } from "./kernel";
2
+
3
+ export function wrapDynamicImport(
4
+ localRequire: RequireFunction,
5
+ ): (specifier: string) => Promise<unknown> {
6
+ return async (specifier: string): Promise<unknown> => {
7
+ const exported = localRequire(specifier);
8
+ if (
9
+ exported &&
10
+ typeof exported === "object" &&
11
+ ("default" in (exported as object) ||
12
+ "__esModule" in (exported as object))
13
+ ) {
14
+ return exported;
15
+ }
16
+ return {
17
+ default: exported,
18
+ ...(exported && typeof exported === "object" ? (exported as object) : {}),
19
+ };
20
+ };
21
+ }
22
+
23
+ const CONSOLE_METHODS = [
24
+ "log",
25
+ "error",
26
+ "warn",
27
+ "info",
28
+ "debug",
29
+ "trace",
30
+ "dir",
31
+ "table",
32
+ "time",
33
+ "timeEnd",
34
+ "timeLog",
35
+ "assert",
36
+ "clear",
37
+ "count",
38
+ "countReset",
39
+ "group",
40
+ "groupCollapsed",
41
+ "groupEnd",
42
+ ] as const;
43
+
44
+ export function buildConsoleProxy(
45
+ hook?: (method: string, args: unknown[]) => void,
46
+ ): typeof console | Record<string, (...args: unknown[]) => void> {
47
+ if (!hook) return console;
48
+ const proxy: Record<string, (...args: unknown[]) => void> = {};
49
+ for (const name of CONSOLE_METHODS) {
50
+ proxy[name] = (...args: unknown[]) => {
51
+ hook(name, args);
52
+ (console as any)[name]?.(...args);
53
+ };
54
+ }
55
+ return proxy;
56
+ }
57
+
58
+ export function buildModuleWrapper(
59
+ code: string,
60
+ filename?: string,
61
+ ): (
62
+ exports: unknown,
63
+ require: RequireFunction,
64
+ mod: unknown,
65
+ __filename: string,
66
+ __dirname: string,
67
+ process: unknown,
68
+ console: unknown,
69
+ import_meta: unknown,
70
+ __dynamicImport: unknown,
71
+ ) => void {
72
+ // Append //# sourceURL so browser DevTools display the correct filename.
73
+ const sourceURL = filename ? `\n//# sourceURL=${filename}` : "";
74
+ const body = [
75
+ "(function(exports, require, module, __filename, __dirname, process, console, import_meta, __dynamicImport) {",
76
+ "(function() {",
77
+ code,
78
+ "}).call(exports);",
79
+ "})",
80
+ sourceURL,
81
+ ].join("\n");
82
+ return new Function("return " + body)();
83
+ }
@@ -0,0 +1,46 @@
1
+ import type { MemFS } from "./memfs";
2
+
3
+ export interface IRuntimeOptions {
4
+ cwd?: string;
5
+ env?: Record<string, string>;
6
+ onConsole?: (method: string, args: unknown[]) => void;
7
+ }
8
+
9
+ export interface IModule {
10
+ id: string;
11
+ filename: string;
12
+ exports: unknown;
13
+ loaded: boolean;
14
+ children: IModule[];
15
+ paths: string[];
16
+ }
17
+
18
+ export interface IExecuteResult {
19
+ exports: unknown;
20
+ module: IModule;
21
+ }
22
+
23
+ export interface IRuntime {
24
+ execute(code: string, filename?: string): Promise<IExecuteResult>;
25
+ runFile(filename: string): Promise<IExecuteResult>;
26
+ clearCache(): void;
27
+ getVFS?(): MemFS;
28
+ terminate?(): void;
29
+ }
30
+
31
+ export interface CreateRuntimeOptions extends IRuntimeOptions {
32
+ sandbox?: string;
33
+ dangerouslyAllowSameOrigin?: boolean;
34
+ useWorker?: boolean | "auto";
35
+ }
36
+
37
+ export interface VFSSnapshot {
38
+ files: VFSFileEntry[];
39
+ }
40
+
41
+ export interface VFSFileEntry {
42
+ path: string;
43
+ type: "file" | "directory" | "symlink";
44
+ content?: string;
45
+ target?: string;
46
+ }
package/src/sandbox.ts ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Resource limits and sandboxing for jiki containers.
3
+ *
4
+ * Provides configurable limits for memory, execution time, file count,
5
+ * network access, and filesystem path boundaries. Essential for
6
+ * production deployments where untrusted code runs.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const container = boot({
11
+ * sandbox: {
12
+ * limits: { maxMemoryMB: 256, maxExecutionMs: 30000, maxFileCount: 10000 },
13
+ * network: { allowedHosts: ['registry.npmjs.org'] },
14
+ * fs: { allowedPaths: ['/app', '/node_modules'] },
15
+ * },
16
+ * });
17
+ * ```
18
+ */
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Types
22
+ // ---------------------------------------------------------------------------
23
+
24
+ export interface SandboxLimits {
25
+ /** Maximum total VFS memory in megabytes. Default: unlimited. */
26
+ maxMemoryMB?: number;
27
+ /** Maximum execution time per run/execute call in milliseconds. Default: unlimited. */
28
+ maxExecutionMs?: number;
29
+ /** Maximum number of files in the VFS. Default: unlimited. */
30
+ maxFileCount?: number;
31
+ /** Maximum single file size in megabytes. Default: unlimited. */
32
+ maxFileSizeMB?: number;
33
+ }
34
+
35
+ export interface SandboxNetwork {
36
+ /** List of allowed hostnames for network requests. */
37
+ allowedHosts?: string[];
38
+ /** Block all network requests. */
39
+ blockAll?: boolean;
40
+ }
41
+
42
+ export interface SandboxFs {
43
+ /** List of allowed path prefixes. Writes outside these paths are rejected. */
44
+ allowedPaths?: string[];
45
+ /** Make the entire VFS read-only. */
46
+ readOnly?: boolean;
47
+ }
48
+
49
+ export interface SandboxOptions {
50
+ limits?: SandboxLimits;
51
+ network?: SandboxNetwork;
52
+ fs?: SandboxFs;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // SandboxGuard
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Enforces resource limits and access controls for a container.
61
+ */
62
+ export class SandboxGuard {
63
+ private opts: SandboxOptions;
64
+ private fileCount = 0;
65
+ private totalBytes = 0;
66
+
67
+ constructor(options: SandboxOptions = {}) {
68
+ this.opts = options;
69
+ }
70
+
71
+ /** Check if the sandbox has any restrictions configured. */
72
+ get isActive(): boolean {
73
+ return !!(this.opts.limits || this.opts.network || this.opts.fs);
74
+ }
75
+
76
+ // -- VFS limits -----------------------------------------------------------
77
+
78
+ /** Check if a file write is allowed by size and count limits. */
79
+ checkWrite(path: string, sizeBytes: number): void {
80
+ if (this.opts.fs?.readOnly) {
81
+ throw new Error(
82
+ `[sandbox] VFS is read-only: write to '${path}' rejected`,
83
+ );
84
+ }
85
+
86
+ if (this.opts.fs?.allowedPaths) {
87
+ const allowed = this.opts.fs.allowedPaths.some(
88
+ prefix => path === prefix || path.startsWith(prefix + "/"),
89
+ );
90
+ if (!allowed) {
91
+ throw new Error(
92
+ `[sandbox] Write to '${path}' rejected: outside allowed paths`,
93
+ );
94
+ }
95
+ }
96
+
97
+ const limits = this.opts.limits;
98
+ if (limits?.maxFileSizeMB) {
99
+ const maxBytes = limits.maxFileSizeMB * 1024 * 1024;
100
+ if (sizeBytes > maxBytes) {
101
+ throw new Error(
102
+ `[sandbox] File '${path}' exceeds max file size (${(sizeBytes / 1024 / 1024).toFixed(1)} MB > ${limits.maxFileSizeMB} MB)`,
103
+ );
104
+ }
105
+ }
106
+
107
+ if (limits?.maxMemoryMB) {
108
+ const maxBytes = limits.maxMemoryMB * 1024 * 1024;
109
+ if (this.totalBytes + sizeBytes > maxBytes) {
110
+ throw new Error(
111
+ `[sandbox] VFS memory limit exceeded (${limits.maxMemoryMB} MB)`,
112
+ );
113
+ }
114
+ }
115
+
116
+ if (limits?.maxFileCount && this.fileCount >= limits.maxFileCount) {
117
+ throw new Error(
118
+ `[sandbox] VFS file count limit exceeded (${limits.maxFileCount})`,
119
+ );
120
+ }
121
+ }
122
+
123
+ /** Track a file being added to the VFS. */
124
+ trackWrite(sizeBytes: number): void {
125
+ this.fileCount++;
126
+ this.totalBytes += sizeBytes;
127
+ }
128
+
129
+ /** Track a file being removed from the VFS. */
130
+ trackDelete(sizeBytes: number): void {
131
+ this.fileCount = Math.max(0, this.fileCount - 1);
132
+ this.totalBytes = Math.max(0, this.totalBytes - sizeBytes);
133
+ }
134
+
135
+ // -- Network limits -------------------------------------------------------
136
+
137
+ /** Check if a network request to the given URL is allowed. */
138
+ checkNetwork(url: string): void {
139
+ if (!this.opts.network) return;
140
+
141
+ if (this.opts.network.blockAll) {
142
+ throw new Error(`[sandbox] All network requests are blocked`);
143
+ }
144
+
145
+ if (this.opts.network.allowedHosts) {
146
+ try {
147
+ const hostname = new URL(url).hostname;
148
+ if (!this.opts.network.allowedHosts.includes(hostname)) {
149
+ throw new Error(
150
+ `[sandbox] Network request to '${hostname}' rejected: not in allowed hosts`,
151
+ );
152
+ }
153
+ } catch (e) {
154
+ if (e instanceof Error && e.message.startsWith("[sandbox]")) throw e;
155
+ throw new Error(`[sandbox] Invalid URL: ${url}`);
156
+ }
157
+ }
158
+ }
159
+
160
+ // -- Execution limits -----------------------------------------------------
161
+
162
+ /** Get the execution timeout in milliseconds, or `undefined` if unlimited. */
163
+ get executionTimeout(): number | undefined {
164
+ return this.opts.limits?.maxExecutionMs;
165
+ }
166
+
167
+ // -- Diagnostics ----------------------------------------------------------
168
+
169
+ get currentFileCount(): number {
170
+ return this.fileCount;
171
+ }
172
+ get currentMemoryBytes(): number {
173
+ return this.totalBytes;
174
+ }
175
+ get currentMemoryMB(): number {
176
+ return this.totalBytes / (1024 * 1024);
177
+ }
178
+ }