@aigne/afs-sandbox 1.11.0-beta.6

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.md ADDED
@@ -0,0 +1,26 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024-2025 ArcBlock, Inc. All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. Unauthorized copying, modification, distribution, or use of
7
+ this Software, via any medium, is strictly prohibited.
8
+
9
+ The Software is provided for internal use only within ArcBlock, Inc. and its
10
+ authorized affiliates.
11
+
12
+ ## No License Granted
13
+
14
+ No license, express or implied, is granted to any party for any purpose.
15
+ All rights are reserved by ArcBlock, Inc.
16
+
17
+ ## Public Artifact Distribution
18
+
19
+ Portions of this Software may be released publicly under separate open-source
20
+ licenses (such as MIT License) through designated public repositories. Such
21
+ public releases are governed by their respective licenses and do not affect
22
+ the proprietary nature of this repository.
23
+
24
+ ## Contact
25
+
26
+ For licensing inquiries, contact: legal@arcblock.io
package/README.md ADDED
@@ -0,0 +1,489 @@
1
+ # @aigne/afs-sandbox
2
+
3
+ AFS Sandbox Provider - A secure JavaScript execution environment for LLM-generated code.
4
+
5
+ ## Overview
6
+
7
+ The AFS Sandbox Provider transforms AFS from a storage abstraction layer into a **semantic-level virtual machine for LLMs**. It enables a powerful development loop:
8
+
9
+ ```
10
+ LLM generates JS → Sandbox executes → Results/errors flow back → LLM self-debugs → Stabilize as tool
11
+ ```
12
+
13
+ This provider uses [QuickJS](https://bellard.org/quickjs/) (via WebAssembly) to execute JavaScript in a completely isolated environment with controlled capabilities.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @aigne/afs-sandbox
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```typescript
24
+ import { AFS } from "@aigne/afs";
25
+ import { AFSSandbox } from "@aigne/afs-sandbox";
26
+
27
+ // Create and mount the sandbox
28
+ const afs = new AFS();
29
+ const sandbox = new AFSSandbox({ name: "sandbox" });
30
+ afs.mount(sandbox);
31
+
32
+ // Write a script
33
+ await afs.write("/modules/sandbox/scripts/greet.js", {
34
+ content: `
35
+ afs.log("info", "Hello from sandbox!");
36
+ return "Hello, " + args.name;
37
+ `
38
+ });
39
+
40
+ // Execute the script
41
+ const result = await afs.exec("/modules/sandbox/@exec/greet", { name: "World" });
42
+ // result.data = { success: true, result: "Hello, World", logs: [...] }
43
+ ```
44
+
45
+ ## Features
46
+
47
+ ### Secure Isolation
48
+
49
+ - **No Node.js globals**: `process`, `require`, `Buffer`, `__dirname` are not available
50
+ - **No network access**: `fetch`, `XMLHttpRequest`, `WebSocket` are blocked
51
+ - **Memory limits**: Configurable memory ceiling (default 128MB)
52
+ - **Timeout protection**: Configurable execution timeout (default 5000ms)
53
+ - **Path traversal protection**: Encoded paths and `..` sequences are blocked
54
+
55
+ ### Capability-Based API
56
+
57
+ Scripts have access to a controlled `afs` object with whitelisted capabilities:
58
+
59
+ ```javascript
60
+ // Logging
61
+ afs.log("info", "Processing data...");
62
+ afs.log("error", "Something went wrong");
63
+
64
+ // Read from AFS (requires mount with AFS root)
65
+ const content = afs.read("/modules/fs/config.json");
66
+
67
+ // Write to AFS (requires write capability enabled)
68
+ afs.write("/modules/data/output.json", JSON.stringify(result));
69
+
70
+ // List directory contents
71
+ const entries = afs.list("/modules/fs/src");
72
+ ```
73
+
74
+ ### Script Storage & Versioning
75
+
76
+ Scripts are stored at `/scripts/{name}.js` with automatic metadata and version tracking:
77
+
78
+ **Metadata:**
79
+ - `createdAt` - Creation timestamp
80
+ - `updatedAt` - Last modification timestamp
81
+ - `lastRun` - Last execution timestamp
82
+ - `runCount` - Total execution count
83
+
84
+ **Version History:**
85
+ - Last 5 versions automatically preserved
86
+ - Access via `/@history/{name}` path
87
+ - Rollback support via `/@actions/rollback`
88
+
89
+ ### Execution Context & History
90
+
91
+ Track executions with context information:
92
+
93
+ ```typescript
94
+ const result = await afs.exec("/modules/sandbox/@exec/script", args, {
95
+ context: {
96
+ userId: "user-123",
97
+ sessionId: "session-456"
98
+ }
99
+ });
100
+ ```
101
+
102
+ Execution history is stored per-script with:
103
+ - Execution ID, timestamp, duration
104
+ - Success/failure status
105
+ - Sanitized arguments (sensitive fields redacted)
106
+
107
+ ### Script Templates
108
+
109
+ Create scripts from built-in templates:
110
+
111
+ ```typescript
112
+ // List available templates
113
+ const templates = await afs.list("/modules/sandbox/@templates");
114
+
115
+ // Create script from template
116
+ await afs.exec("/modules/sandbox/@actions/create-from-template", {
117
+ template: "data-transform",
118
+ name: "my-transformer",
119
+ variables: {
120
+ inputField: "rawData",
121
+ outputField: "processedData"
122
+ }
123
+ });
124
+ ```
125
+
126
+ **Built-in Templates:**
127
+ - `basic` - Simple script template
128
+ - `data-transform` - Transform input data to output format
129
+ - `validation` - Input validation with error collection
130
+ - `aggregation` - Aggregate multiple values
131
+
132
+ ### Script Promotion (Script → Action)
133
+
134
+ Promote tested scripts to formal actions:
135
+
136
+ ```typescript
137
+ // Promote a script to an action
138
+ await afs.exec("/modules/sandbox/@actions/promote", {
139
+ script: "calculator",
140
+ name: "calculate",
141
+ description: "Perform arithmetic calculations",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ a: { type: "number" },
146
+ b: { type: "number" },
147
+ op: { type: "string", enum: ["add", "sub", "mul", "div"] }
148
+ }
149
+ }
150
+ });
151
+
152
+ // Execute the promoted action
153
+ const result = await afs.exec("/modules/sandbox/@actions/calculate", {
154
+ a: 10, b: 5, op: "mul"
155
+ });
156
+ ```
157
+
158
+ ### Audit Logging
159
+
160
+ Track all sandbox operations for security and debugging:
161
+
162
+ ```typescript
163
+ const sandbox = new AFSSandbox({
164
+ audit: {
165
+ enabled: true,
166
+ onEvent: (event) => console.log(event),
167
+ maxEntries: 1000
168
+ }
169
+ });
170
+
171
+ // Read audit log
172
+ const auditLog = await afs.read("/modules/sandbox/@audit");
173
+ ```
174
+
175
+ **Event Types:**
176
+ - `execution` - Script executions
177
+ - `capability` - Capability usage (read/write/list)
178
+ - `security` - Security-related events (denied access)
179
+ - `script_write` - Script creation/modification
180
+ - `script_delete` - Script deletion
181
+ - `action_promote` - Script promotion to action
182
+
183
+ ### Rate Limiting
184
+
185
+ Protect against abuse with configurable rate limits:
186
+
187
+ ```typescript
188
+ const sandbox = new AFSSandbox({
189
+ rateLimit: {
190
+ maxExecutionsPerMinute: 100, // Global limit
191
+ maxExecutionsPerMinutePerUser: 20, // Per-user limit
192
+ maxExecutionsPerMinutePerScript: 30 // Per-script limit
193
+ }
194
+ });
195
+ ```
196
+
197
+ ### Execution Metrics
198
+
199
+ Monitor sandbox performance:
200
+
201
+ ```typescript
202
+ // Global metrics
203
+ const metrics = await afs.read("/modules/sandbox/@metrics");
204
+ // { totalExecutions, successCount, failureCount, timeoutCount, averageDuration }
205
+
206
+ // Per-script metrics
207
+ const scriptMetrics = await afs.read("/modules/sandbox/@metrics/my-script");
208
+ ```
209
+
210
+ ## Configuration
211
+
212
+ ```typescript
213
+ interface AFSSandboxOptions {
214
+ name?: string; // Module name (default: "sandbox")
215
+ description?: string; // Module description
216
+ accessMode?: "readonly" | "readwrite"; // Access mode (default: "readwrite")
217
+
218
+ runtime?: {
219
+ timeout?: number; // Execution timeout in ms (default: 5000)
220
+ memoryLimit?: number; // Memory limit in MB (default: 128)
221
+ };
222
+
223
+ capabilities?: {
224
+ afs?: {
225
+ read?: boolean; // Enable afs.read() (default: true)
226
+ write?: boolean; // Enable afs.write() (default: false)
227
+ allowPaths?: string[]; // Allowed path patterns
228
+ denyPaths?: string[]; // Denied path patterns
229
+ };
230
+ };
231
+
232
+ audit?: {
233
+ enabled: boolean; // Enable audit logging
234
+ onEvent?: (event) => void; // Callback for audit events
235
+ maxEntries?: number; // Max entries to keep (default: 1000)
236
+ };
237
+
238
+ rateLimit?: {
239
+ maxExecutionsPerMinute?: number;
240
+ maxExecutionsPerMinutePerUser?: number;
241
+ maxExecutionsPerMinutePerScript?: number;
242
+ };
243
+ }
244
+ ```
245
+
246
+ ## Path Structure
247
+
248
+ ```
249
+ /modules/sandbox/
250
+ ├── /scripts/ # Script storage
251
+ │ └── {name}.js # Script content
252
+ ├── /@exec/{name} # Execute stored script
253
+ ├── /@history/{name} # Script version history
254
+ ├── /@actions/
255
+ │ ├── run # Run inline code
256
+ │ ├── validate # Syntax validation
257
+ │ ├── rollback # Rollback to previous version
258
+ │ ├── promote # Promote script to action
259
+ │ ├── create-from-template # Create script from template
260
+ │ └── {promoted-action} # User-promoted actions
261
+ ├── /@templates # List available templates
262
+ │ └── {name} # Template details
263
+ ├── /@metrics # Global execution metrics
264
+ │ └── {script} # Per-script metrics
265
+ └── /@audit # Audit log
266
+ ```
267
+
268
+ ## Execution Result
269
+
270
+ All executions return a structured result:
271
+
272
+ ```typescript
273
+ interface ExecutionResult {
274
+ success: boolean;
275
+ result?: unknown; // Return value (if success)
276
+ error?: string; // Error message (if failed)
277
+ stack?: string; // Stack trace (if failed)
278
+ logs: LogEntry[]; // All log entries
279
+ duration: number; // Execution time in ms
280
+ timedOut?: boolean; // True if execution timed out
281
+ }
282
+
283
+ interface LogEntry {
284
+ level: "debug" | "info" | "warn" | "error";
285
+ message: string;
286
+ timestamp: number;
287
+ }
288
+ ```
289
+
290
+ ## Implementation Status
291
+
292
+ ### Phase 1: POC (Complete) ✅
293
+
294
+ Core execution loop validated with 47 tests.
295
+
296
+ | KPI | Status |
297
+ |-----|--------|
298
+ | Basic execution | ✅ `return 2 + 2` → `{ success: true, result: 4 }` |
299
+ | AFS read capability | ✅ `afs.read('/path')` works |
300
+ | Error propagation | ✅ Full error messages with line numbers |
301
+ | Timeout handling | ✅ Infinite loops timeout correctly |
302
+ | Security isolation | ✅ No access to Node.js globals |
303
+
304
+ ### Phase 2: MVP (Complete) ✅
305
+
306
+ Internal team usable with 54 additional tests (101 total).
307
+
308
+ | Feature | Status |
309
+ |---------|--------|
310
+ | afs.write() capability | ✅ Write to allowed paths |
311
+ | Script version history | ✅ Last 5 versions preserved |
312
+ | Execution context | ✅ userId, sessionId tracking |
313
+ | Execution history | ✅ Per-script history with redaction |
314
+ | Rollback support | ✅ Restore previous versions |
315
+
316
+ ### Phase 3: Beta (Complete) ✅
317
+
318
+ Production-ready with 55 additional tests (156 total).
319
+
320
+ | Feature | Status |
321
+ |---------|--------|
322
+ | Script Promote Workflow | ✅ Promote scripts to formal actions |
323
+ | Script Templates | ✅ 4 built-in templates |
324
+ | Audit Logging | ✅ 6 event types tracked |
325
+ | Rate Limiting | ✅ Global, per-user, per-script |
326
+ | Execution Metrics | ✅ Global and per-script tracking |
327
+
328
+ ### Phase 4+: Future Vision
329
+
330
+ - Agent Tool Marketplace
331
+ - Multi-agent orchestration
332
+ - Alternative runtimes (Deno)
333
+ - Full async/await support (see Limitations below)
334
+
335
+ ## Known Limitations
336
+
337
+ ### Async/Await Support (Deferred)
338
+
339
+ **Current State:** Capability calls (`afs.read()`, `afs.write()`, `afs.list()`) work synchronously within scripts. The sandbox does not support full async/await patterns.
340
+
341
+ | Works | Does Not Work |
342
+ |-------|---------------|
343
+ | `const data = afs.read(path);` | `const data = await afs.read(path);` (across multiple statements) |
344
+ | Single-statement operations | Complex Promise chains |
345
+ | Synchronous data processing | `setTimeout`/`setInterval` |
346
+
347
+ **Technical Reason:** QuickJS (WebAssembly) executes JavaScript synchronously. While we set up Promise infrastructure for capability calls, the runtime cannot pause execution to wait for external async operations. Implementing full async/await would require:
348
+
349
+ 1. Custom Promise polling mechanism
350
+ 2. Careful handle management to avoid memory leaks
351
+ 3. Significant changes to the execution model
352
+
353
+ **Attempted Solution:** We tried implementing async support but encountered QuickJS handle leaks (`list_empty(&rt->gc_obj_list)` assertion failure) when Promises weren't properly resolved before context disposal.
354
+
355
+ **Future Path:** Phase 4 may introduce Deno runtime as an alternative backend with native async support.
356
+
357
+ ### Other Limitations
358
+
359
+ - **No ES modules**: `import`/`export` syntax not supported
360
+ - **No top-level await**: Use synchronous patterns
361
+ - **Limited standard library**: Only core JavaScript, no Node.js APIs
362
+ - **Memory ceiling**: Hard limit of configurable MB (default 128MB)
363
+
364
+ ## Design Documents
365
+
366
+ - **Design Philosophy**: [`/intent/designs/afs-as-llm-runtime.md`](../../intent/designs/afs-as-llm-runtime.md)
367
+ - **Implementation Plan**: [`/intent/plans/sandbox-implementation.md`](../../intent/plans/sandbox-implementation.md)
368
+
369
+ ## Technical Details
370
+
371
+ ### Why QuickJS?
372
+
373
+ | Option | Pros | Cons |
374
+ |--------|------|------|
375
+ | `isolated-vm` | V8 compatible, fast | Native bindings, doesn't work with Bun |
376
+ | `quickjs-emscripten` | WebAssembly, works everywhere | Slightly slower, sync-only execution |
377
+ | `vm2` | Easy to use | Security vulnerabilities, deprecated |
378
+
379
+ We chose `quickjs-emscripten` for its universal compatibility (Node.js, Bun, browsers) and strong isolation guarantees.
380
+
381
+ ## Examples
382
+
383
+ ### Simple Calculator
384
+
385
+ ```typescript
386
+ await afs.write("/modules/sandbox/scripts/calc.js", {
387
+ content: `
388
+ const { a, b, op } = args;
389
+ switch(op) {
390
+ case 'add': return a + b;
391
+ case 'sub': return a - b;
392
+ case 'mul': return a * b;
393
+ case 'div': return b !== 0 ? a / b : 'Division by zero';
394
+ default: return 'Unknown operation';
395
+ }
396
+ `
397
+ });
398
+
399
+ const result = await afs.exec("/modules/sandbox/@exec/calc", {
400
+ a: 10, b: 5, op: "mul"
401
+ });
402
+ // result.data.result = 50
403
+ ```
404
+
405
+ ### Data Processing with Logging
406
+
407
+ ```typescript
408
+ await afs.write("/modules/sandbox/scripts/process.js", {
409
+ content: `
410
+ afs.log("info", "Starting processing...");
411
+
412
+ const items = args.items || [];
413
+ const processed = items.map(item => {
414
+ afs.log("debug", "Processing: " + JSON.stringify(item));
415
+ return { ...item, processed: true };
416
+ });
417
+
418
+ afs.log("info", "Completed: " + processed.length + " items");
419
+ return processed;
420
+ `
421
+ });
422
+
423
+ const result = await afs.exec("/modules/sandbox/@exec/process", {
424
+ items: [{ id: 1 }, { id: 2 }]
425
+ });
426
+ ```
427
+
428
+ ### Promote Script to Action
429
+
430
+ ```typescript
431
+ // 1. Develop and test a script
432
+ await afs.write("/modules/sandbox/scripts/greet.js", {
433
+ content: `return "Hello, " + (args.name || "World") + "!";`
434
+ });
435
+
436
+ // Test it
437
+ const test = await afs.exec("/modules/sandbox/@exec/greet", { name: "Alice" });
438
+ // test.data.result = "Hello, Alice!"
439
+
440
+ // 2. Promote to a formal action
441
+ await afs.exec("/modules/sandbox/@actions/promote", {
442
+ script: "greet",
443
+ name: "greet-user",
444
+ description: "Greet a user by name"
445
+ });
446
+
447
+ // 3. Use the promoted action
448
+ const result = await afs.exec("/modules/sandbox/@actions/greet-user", { name: "Bob" });
449
+ // result.data.result = "Hello, Bob!"
450
+ ```
451
+
452
+ ### Error Handling for LLM Debug Loop
453
+
454
+ ```typescript
455
+ // LLM writes code with a bug
456
+ await afs.write("/modules/sandbox/scripts/buggy.js", {
457
+ content: `
458
+ const data = JSON.parse(args.json);
459
+ return data.value.nested.property; // Bug: doesn't handle missing properties
460
+ `
461
+ });
462
+
463
+ // Execute and get error
464
+ const result = await afs.exec("/modules/sandbox/@exec/buggy", {
465
+ json: '{"value": {}}'
466
+ });
467
+ // result.data = {
468
+ // success: false,
469
+ // error: "Cannot read properties of undefined (reading 'property')",
470
+ // }
471
+
472
+ // LLM sees error, fixes the code
473
+ await afs.write("/modules/sandbox/scripts/buggy.js", {
474
+ content: `
475
+ const data = JSON.parse(args.json);
476
+ return data?.value?.nested?.property ?? 'default';
477
+ `
478
+ });
479
+
480
+ // Now it works
481
+ const fixed = await afs.exec("/modules/sandbox/@exec/buggy", {
482
+ json: '{"value": {}}'
483
+ });
484
+ // fixed.data.result = 'default'
485
+ ```
486
+
487
+ ## License
488
+
489
+ UNLICENSED - Internal use only