@ebowwa/sandbox 0.1.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/dist/compilers/index.d.ts +24 -0
- package/dist/compilers/index.d.ts.map +1 -0
- package/dist/compilers/index.js +42 -0
- package/dist/compilers/index.js.map +1 -0
- package/dist/compilers/javascript.d.ts +117 -0
- package/dist/compilers/javascript.d.ts.map +1 -0
- package/dist/compilers/javascript.js +462 -0
- package/dist/compilers/javascript.js.map +1 -0
- package/dist/compilers/python.d.ts +140 -0
- package/dist/compilers/python.d.ts.map +1 -0
- package/dist/compilers/python.js +650 -0
- package/dist/compilers/python.js.map +1 -0
- package/dist/compilers/typescript.d.ts +99 -0
- package/dist/compilers/typescript.d.ts.map +1 -0
- package/dist/compilers/typescript.js +323 -0
- package/dist/compilers/typescript.js.map +1 -0
- package/dist/core/cell.d.ts +160 -0
- package/dist/core/cell.d.ts.map +1 -0
- package/dist/core/cell.js +319 -0
- package/dist/core/cell.js.map +1 -0
- package/dist/core/compiler.d.ts +126 -0
- package/dist/core/compiler.d.ts.map +1 -0
- package/dist/core/compiler.js +123 -0
- package/dist/core/compiler.js.map +1 -0
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/limits.d.ts +173 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +440 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/permissions.d.ts +103 -0
- package/dist/core/permissions.d.ts.map +1 -0
- package/dist/core/permissions.js +341 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/runtime.d.ts +127 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +325 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/types.d.ts +380 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +67 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -0
- package/dist/multi/index.d.ts +9 -0
- package/dist/multi/index.d.ts.map +1 -0
- package/dist/multi/index.js +7 -0
- package/dist/multi/index.js.map +1 -0
- package/dist/multi/polyglot.d.ts +179 -0
- package/dist/multi/polyglot.d.ts.map +1 -0
- package/dist/multi/polyglot.js +319 -0
- package/dist/multi/polyglot.js.map +1 -0
- package/dist/runtimes/docker.d.ts +97 -0
- package/dist/runtimes/docker.d.ts.map +1 -0
- package/dist/runtimes/docker.js +368 -0
- package/dist/runtimes/docker.js.map +1 -0
- package/dist/runtimes/index.d.ts +11 -0
- package/dist/runtimes/index.d.ts.map +1 -0
- package/dist/runtimes/index.js +9 -0
- package/dist/runtimes/index.js.map +1 -0
- package/dist/runtimes/process.d.ts +47 -0
- package/dist/runtimes/process.d.ts.map +1 -0
- package/dist/runtimes/process.js +230 -0
- package/dist/runtimes/process.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +9 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/kernel.d.ts +199 -0
- package/dist/session/kernel.d.ts.map +1 -0
- package/dist/session/kernel.js +400 -0
- package/dist/session/kernel.js.map +1 -0
- package/dist/session/notebook.d.ts +168 -0
- package/dist/session/notebook.d.ts.map +1 -0
- package/dist/session/notebook.js +499 -0
- package/dist/session/notebook.js.map +1 -0
- package/dist/session/repl.d.ts +159 -0
- package/dist/session/repl.d.ts.map +1 -0
- package/dist/session/repl.js +409 -0
- package/dist/session/repl.js.map +1 -0
- package/package.json +142 -0
- package/src/compilers/index.ts +80 -0
- package/src/compilers/javascript.ts +571 -0
- package/src/compilers/python.ts +785 -0
- package/src/compilers/typescript.ts +442 -0
- package/src/core/cell.ts +439 -0
- package/src/core/compiler.ts +250 -0
- package/src/core/index.ts +123 -0
- package/src/core/limits.ts +508 -0
- package/src/core/permissions.ts +409 -0
- package/src/core/runtime.ts +499 -0
- package/src/core/types.ts +528 -0
- package/src/global.d.ts +59 -0
- package/src/index.ts +515 -0
- package/src/multi/index.ts +22 -0
- package/src/multi/polyglot.ts +461 -0
- package/src/runtimes/docker.ts +501 -0
- package/src/runtimes/index.ts +21 -0
- package/src/runtimes/process.ts +316 -0
- package/src/session/index.ts +41 -0
- package/src/session/kernel.ts +553 -0
- package/src/session/notebook.ts +635 -0
- package/src/session/repl.ts +521 -0
- package/src/wasm2wasm.d.ts +35 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permissions Primitive
|
|
3
|
+
*
|
|
4
|
+
* Composable permission enforcement for sandbox execution.
|
|
5
|
+
* Each permission can be independently granted or denied.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
Permissions,
|
|
10
|
+
PermissionLevel,
|
|
11
|
+
FileSystemPermission,
|
|
12
|
+
NetworkPermission,
|
|
13
|
+
EnvironmentPermission,
|
|
14
|
+
ProcessPermission,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Permission Checker
|
|
19
|
+
*
|
|
20
|
+
* Validates permissions against allowed operations.
|
|
21
|
+
*/
|
|
22
|
+
export class PermissionChecker {
|
|
23
|
+
constructor(private permissions: Permissions) {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if filesystem read is allowed
|
|
27
|
+
*/
|
|
28
|
+
canReadFile(path?: string): boolean {
|
|
29
|
+
const fs = this.permissions.fs;
|
|
30
|
+
if (!fs?.read) return false;
|
|
31
|
+
|
|
32
|
+
if (path) {
|
|
33
|
+
return this.isPathAllowed(path, fs);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if filesystem write is allowed
|
|
40
|
+
*/
|
|
41
|
+
canWriteFile(path?: string): boolean {
|
|
42
|
+
const fs = this.permissions.fs;
|
|
43
|
+
if (!fs?.write) return false;
|
|
44
|
+
|
|
45
|
+
if (path) {
|
|
46
|
+
return this.isPathAllowed(path, fs);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if filesystem delete is allowed
|
|
53
|
+
*/
|
|
54
|
+
canDeleteFile(path?: string): boolean {
|
|
55
|
+
const fs = this.permissions.fs;
|
|
56
|
+
if (!fs?.delete) return false;
|
|
57
|
+
|
|
58
|
+
if (path) {
|
|
59
|
+
return this.isPathAllowed(path, fs);
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if network outbound is allowed
|
|
66
|
+
*/
|
|
67
|
+
canConnect(host?: string): boolean {
|
|
68
|
+
const net = this.permissions.network;
|
|
69
|
+
if (!net?.outbound) return false;
|
|
70
|
+
|
|
71
|
+
if (host) {
|
|
72
|
+
return this.isHostAllowed(host, net);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if network inbound is allowed
|
|
79
|
+
*/
|
|
80
|
+
canListen(port?: number): boolean {
|
|
81
|
+
const net = this.permissions.network;
|
|
82
|
+
if (!net?.inbound) return false;
|
|
83
|
+
|
|
84
|
+
// Could add port-based restrictions here
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if environment read is allowed
|
|
90
|
+
*/
|
|
91
|
+
canReadEnv(key?: string): boolean {
|
|
92
|
+
const env = this.permissions.env;
|
|
93
|
+
if (!env?.read) return false;
|
|
94
|
+
|
|
95
|
+
if (key && env.allowedKeys) {
|
|
96
|
+
return env.allowedKeys.includes(key);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if environment write is allowed
|
|
103
|
+
*/
|
|
104
|
+
canWriteEnv(key?: string): boolean {
|
|
105
|
+
const env = this.permissions.env;
|
|
106
|
+
if (!env?.write) return false;
|
|
107
|
+
|
|
108
|
+
if (key && env.allowedKeys) {
|
|
109
|
+
return env.allowedKeys.includes(key);
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if process spawn is allowed
|
|
116
|
+
*/
|
|
117
|
+
canSpawnProcess(command?: string): boolean {
|
|
118
|
+
const proc = this.permissions.process;
|
|
119
|
+
if (!proc?.spawn) return false;
|
|
120
|
+
|
|
121
|
+
if (command && proc.allowedCommands) {
|
|
122
|
+
return proc.allowedCommands.some(cmd =>
|
|
123
|
+
command.startsWith(cmd) || command === cmd
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get memory limit
|
|
131
|
+
*/
|
|
132
|
+
getMemoryLimit(): number | undefined {
|
|
133
|
+
return this.permissions.memory?.maxBytes;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get CPU limits
|
|
138
|
+
*/
|
|
139
|
+
getCpuLimits(): { maxPercent?: number; maxTimeMs?: number } {
|
|
140
|
+
const cpu = this.permissions.cpu;
|
|
141
|
+
return {
|
|
142
|
+
maxPercent: cpu?.maxPercent,
|
|
143
|
+
maxTimeMs: cpu?.maxTimeMs,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if path is allowed
|
|
149
|
+
*/
|
|
150
|
+
private isPathAllowed(path: string, fs: FileSystemPermission): boolean {
|
|
151
|
+
// Check denied paths first
|
|
152
|
+
if (fs.deniedPaths) {
|
|
153
|
+
for (const denied of fs.deniedPaths) {
|
|
154
|
+
if (path.startsWith(denied)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check allowed paths
|
|
161
|
+
if (fs.allowedPaths) {
|
|
162
|
+
for (const allowed of fs.allowedPaths) {
|
|
163
|
+
if (path.startsWith(allowed)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if host is allowed
|
|
175
|
+
*/
|
|
176
|
+
private isHostAllowed(host: string, net: NetworkPermission): boolean {
|
|
177
|
+
// Check denied hosts first
|
|
178
|
+
if (net.deniedHosts) {
|
|
179
|
+
for (const denied of net.deniedHosts) {
|
|
180
|
+
if (host === denied || host.endsWith(`.${denied}`)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check allowed hosts
|
|
187
|
+
if (net.allowedHosts) {
|
|
188
|
+
for (const allowed of net.allowedHosts) {
|
|
189
|
+
if (host === allowed || host.endsWith(`.${allowed}`)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Permission Builder
|
|
202
|
+
*
|
|
203
|
+
* Fluent API for building permission sets.
|
|
204
|
+
*/
|
|
205
|
+
export class PermissionBuilder {
|
|
206
|
+
private permissions: Permissions = {};
|
|
207
|
+
|
|
208
|
+
filesystem(options: Partial<FileSystemPermission>): this {
|
|
209
|
+
this.permissions.fs = {
|
|
210
|
+
...this.permissions.fs,
|
|
211
|
+
...options,
|
|
212
|
+
};
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
network(options: Partial<NetworkPermission>): this {
|
|
217
|
+
this.permissions.network = {
|
|
218
|
+
...this.permissions.network,
|
|
219
|
+
...options,
|
|
220
|
+
};
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
environment(options: Partial<EnvironmentPermission>): this {
|
|
225
|
+
this.permissions.env = {
|
|
226
|
+
...this.permissions.env,
|
|
227
|
+
...options,
|
|
228
|
+
};
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
process(options: Partial<ProcessPermission>): this {
|
|
233
|
+
this.permissions.process = {
|
|
234
|
+
...this.permissions.process,
|
|
235
|
+
...options,
|
|
236
|
+
};
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
memory(maxBytes: number): this {
|
|
241
|
+
this.permissions.memory = { maxBytes };
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
cpu(options: { maxPercent?: number; maxTimeMs?: number }): this {
|
|
246
|
+
this.permissions.cpu = options;
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
build(): Permissions {
|
|
251
|
+
return this.permissions;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create permission set from level
|
|
257
|
+
*/
|
|
258
|
+
export function createPermissions(level: PermissionLevel): Permissions {
|
|
259
|
+
switch (level) {
|
|
260
|
+
case "isolated":
|
|
261
|
+
return {};
|
|
262
|
+
|
|
263
|
+
case "readonly":
|
|
264
|
+
return {
|
|
265
|
+
fs: { read: true },
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
case "network":
|
|
269
|
+
return {
|
|
270
|
+
fs: { read: true },
|
|
271
|
+
network: { outbound: true },
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
case "filesystem":
|
|
275
|
+
return {
|
|
276
|
+
fs: { read: true, write: true, delete: true },
|
|
277
|
+
network: { outbound: true, inbound: true },
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
case "admin":
|
|
281
|
+
return {
|
|
282
|
+
fs: { read: true, write: true, delete: true },
|
|
283
|
+
network: { outbound: true, inbound: true },
|
|
284
|
+
env: { read: true, write: true },
|
|
285
|
+
process: { spawn: true },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
case "sudo":
|
|
289
|
+
return {
|
|
290
|
+
fs: { read: true, write: true, delete: true },
|
|
291
|
+
network: { outbound: true, inbound: true },
|
|
292
|
+
env: { read: true, write: true },
|
|
293
|
+
process: { spawn: true },
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Merge permissions (intersection - most restrictive)
|
|
300
|
+
*/
|
|
301
|
+
export function mergePermissions(...perms: Permissions[]): Permissions {
|
|
302
|
+
const result: Permissions = {};
|
|
303
|
+
|
|
304
|
+
// Filesystem
|
|
305
|
+
const fsPerms = perms.filter(p => p.fs);
|
|
306
|
+
if (fsPerms.length === perms.length) {
|
|
307
|
+
result.fs = {
|
|
308
|
+
read: fsPerms.every(p => p.fs?.read),
|
|
309
|
+
write: fsPerms.every(p => p.fs?.write),
|
|
310
|
+
delete: fsPerms.every(p => p.fs?.delete),
|
|
311
|
+
allowedPaths: intersectArrays(
|
|
312
|
+
...fsPerms.map(p => p.fs?.allowedPaths).filter(Boolean) as string[][]
|
|
313
|
+
),
|
|
314
|
+
deniedPaths: unionArrays(
|
|
315
|
+
...fsPerms.map(p => p.fs?.deniedPaths).filter(Boolean) as string[][]
|
|
316
|
+
),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Network
|
|
321
|
+
const netPerms = perms.filter(p => p.network);
|
|
322
|
+
if (netPerms.length === perms.length) {
|
|
323
|
+
result.network = {
|
|
324
|
+
outbound: netPerms.every(p => p.network?.outbound),
|
|
325
|
+
inbound: netPerms.every(p => p.network?.inbound),
|
|
326
|
+
allowedHosts: intersectArrays(
|
|
327
|
+
...netPerms.map(p => p.network?.allowedHosts).filter(Boolean) as string[][]
|
|
328
|
+
),
|
|
329
|
+
deniedHosts: unionArrays(
|
|
330
|
+
...netPerms.map(p => p.network?.deniedHosts).filter(Boolean) as string[][]
|
|
331
|
+
),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Memory (take minimum)
|
|
336
|
+
const memoryLimits = perms
|
|
337
|
+
.map(p => p.memory?.maxBytes)
|
|
338
|
+
.filter(Boolean) as number[];
|
|
339
|
+
if (memoryLimits.length > 0) {
|
|
340
|
+
result.memory = { maxBytes: Math.min(...memoryLimits) };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// CPU (take minimums)
|
|
344
|
+
const cpuPerms = perms.filter(p => p.cpu);
|
|
345
|
+
if (cpuPerms.length > 0) {
|
|
346
|
+
result.cpu = {
|
|
347
|
+
maxPercent: Math.min(
|
|
348
|
+
...cpuPerms.map(p => p.cpu?.maxPercent ?? 100)
|
|
349
|
+
),
|
|
350
|
+
maxTimeMs: Math.min(
|
|
351
|
+
...cpuPerms.map(p => p.cpu?.maxTimeMs ?? Infinity)
|
|
352
|
+
),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Check if permission level implies another
|
|
361
|
+
*/
|
|
362
|
+
export function impliesPermission(
|
|
363
|
+
level: PermissionLevel,
|
|
364
|
+
required: PermissionLevel
|
|
365
|
+
): boolean {
|
|
366
|
+
const levels: PermissionLevel[] = [
|
|
367
|
+
"isolated",
|
|
368
|
+
"readonly",
|
|
369
|
+
"network",
|
|
370
|
+
"filesystem",
|
|
371
|
+
"admin",
|
|
372
|
+
"sudo",
|
|
373
|
+
];
|
|
374
|
+
return levels.indexOf(level) >= levels.indexOf(required);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Upgrade permissions to higher level
|
|
379
|
+
*/
|
|
380
|
+
export function upgradePermissions(
|
|
381
|
+
current: Permissions,
|
|
382
|
+
newLevel: PermissionLevel
|
|
383
|
+
): Permissions {
|
|
384
|
+
const newPerms = createPermissions(newLevel);
|
|
385
|
+
return mergePermissions(current, newPerms);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Helper functions
|
|
389
|
+
function intersectArrays<T>(...arrays: T[][]): T[] {
|
|
390
|
+
if (arrays.length === 0) return [];
|
|
391
|
+
if (arrays.length === 1) return arrays[0];
|
|
392
|
+
|
|
393
|
+
return arrays[0].filter(item =>
|
|
394
|
+
arrays.every(arr => arr.includes(item))
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function unionArrays<T>(...arrays: T[][]): T[] {
|
|
399
|
+
const set = new Set<T>();
|
|
400
|
+
for (const arr of arrays) {
|
|
401
|
+
for (const item of arr) {
|
|
402
|
+
set.add(item);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return Array.from(set);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Re-export from types
|
|
409
|
+
export { permissionsFromLevel } from "./types.js";
|