skyloom 1.6.0 → 1.8.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 (44) hide show
  1. package/dist/core/agent.d.ts.map +1 -1
  2. package/dist/core/agent.js +44 -1
  3. package/dist/core/agent.js.map +1 -1
  4. package/dist/core/arbitrate.d.ts +32 -0
  5. package/dist/core/arbitrate.d.ts.map +1 -0
  6. package/dist/core/arbitrate.js +136 -0
  7. package/dist/core/arbitrate.js.map +1 -0
  8. package/dist/core/estimate.d.ts +30 -0
  9. package/dist/core/estimate.d.ts.map +1 -0
  10. package/dist/core/estimate.js +94 -0
  11. package/dist/core/estimate.js.map +1 -0
  12. package/dist/core/evolve.d.ts +43 -0
  13. package/dist/core/evolve.d.ts.map +1 -0
  14. package/dist/core/evolve.js +201 -0
  15. package/dist/core/evolve.js.map +1 -0
  16. package/dist/core/filter.d.ts +16 -0
  17. package/dist/core/filter.d.ts.map +1 -0
  18. package/dist/core/filter.js +91 -0
  19. package/dist/core/filter.js.map +1 -0
  20. package/dist/core/index.d.ts +7 -1
  21. package/dist/core/index.d.ts.map +1 -1
  22. package/dist/core/index.js +13 -2
  23. package/dist/core/index.js.map +1 -1
  24. package/dist/core/longdoc.d.ts +41 -0
  25. package/dist/core/longdoc.d.ts.map +1 -0
  26. package/dist/core/longdoc.js +128 -0
  27. package/dist/core/longdoc.js.map +1 -0
  28. package/dist/core/sandbox.d.ts +24 -0
  29. package/dist/core/sandbox.d.ts.map +1 -0
  30. package/dist/core/sandbox.js +158 -0
  31. package/dist/core/sandbox.js.map +1 -0
  32. package/dist/tools/builtin.d.ts.map +1 -1
  33. package/dist/tools/builtin.js +3 -3
  34. package/dist/tools/builtin.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/core/agent.ts +33 -1
  37. package/src/core/arbitrate.ts +162 -0
  38. package/src/core/estimate.ts +104 -0
  39. package/src/core/evolve.ts +191 -0
  40. package/src/core/filter.ts +103 -0
  41. package/src/core/index.ts +8 -2
  42. package/src/core/longdoc.ts +155 -0
  43. package/src/core/sandbox.ts +142 -0
  44. package/src/tools/builtin.ts +4 -6
@@ -0,0 +1,142 @@
1
+ /**
2
+ * 沙箱隔离模块 — Shell execution sandbox with resource limits.
3
+ *
4
+ * All `run_bash` / `shell_exec` commands are wrapped through this module
5
+ * to ensure: temp directory isolation, timeout enforcement, output size
6
+ * limits, and dangerous command detection BEFORE execution.
7
+ */
8
+
9
+ import { execSync, exec } from "child_process";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import * as os from "os";
13
+ import { getLogger } from "./logger";
14
+ import { REDLINE_PATTERNS, REDLINE_COMMANDS } from "./security";
15
+
16
+ const log = getLogger("sandbox");
17
+
18
+ /* ═══════════════════════════════════════
19
+ Configuration
20
+ ═══════════════════════════════════════ */
21
+ const SANDBOX_DIR = path.join(os.homedir(), ".skyloom", "sandbox");
22
+ const DEFAULT_TIMEOUT_MS = 30000; // 30s max
23
+ const MAX_OUTPUT_BYTES = 1024 * 1024; // 1MB max output
24
+ const HARD_TIMEOUT_MS = 120000; // 2min absolute max
25
+ // Whitelist of safe commands that don't need sandbox
26
+ const SAFE_COMMANDS = new Set(["echo", "pwd", "whoami", "date", "hostname", "uname", "ls", "dir", "cat", "head", "tail", "wc", "env", "printenv"]);
27
+
28
+ /* ═══════════════════════════════════════
29
+ Sandbox lifecycle
30
+ ═══════════════════════════════════════ */
31
+ function ensureSandbox(): string {
32
+ if (!fs.existsSync(SANDBOX_DIR)) fs.mkdirSync(SANDBOX_DIR, { recursive: true });
33
+ // Create named temp dir for this execution
34
+ const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
35
+ const dir = path.join(SANDBOX_DIR, `job_${id}`);
36
+ fs.mkdirSync(dir, { recursive: true });
37
+ return dir;
38
+ }
39
+
40
+ function cleanup(dir: string): void {
41
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
42
+ }
43
+
44
+ /* ═══════════════════════════════════════
45
+ Pre-execution check
46
+ ═══════════════════════════════════════ */
47
+ function preflightCheck(command: string): string | null {
48
+ if (!command || !command.trim()) return "Empty command";
49
+
50
+ const lower = command.toLowerCase().trim();
51
+
52
+ // Red-line patterns
53
+ for (const pattern of REDLINE_PATTERNS) {
54
+ if (pattern.test(lower)) return `REDLINE: pattern '${pattern.source.slice(0, 40)}' detected`;
55
+ }
56
+ for (const forbidden of REDLINE_COMMANDS) {
57
+ if (lower.includes(forbidden)) return `REDLINE: forbidden command '${forbidden}'`;
58
+ }
59
+
60
+ // Network exfiltration attempts
61
+ if (/curl.*\|.*nc\s/.test(lower) || /wget.*-O.*>/.test(lower)) return "BLOCKED: potential data exfiltration";
62
+ if (/nc\s+\S+\s+\d+/.test(lower) && /\|/.test(lower)) return "BLOCKED: potential reverse shell";
63
+
64
+ return null;
65
+ }
66
+
67
+ /* ═══════════════════════════════════════
68
+ Execute in sandbox
69
+ ═══════════════════════════════════════ */
70
+ export interface SandboxResult {
71
+ success: boolean;
72
+ stdout: string;
73
+ stderr: string;
74
+ exitCode: number;
75
+ killed: boolean;
76
+ durationMs: number;
77
+ sandboxDir: string;
78
+ checkFailed?: string;
79
+ }
80
+
81
+ export function runInSandbox(command: string, opts?: {
82
+ timeoutMs?: number;
83
+ cwd?: string;
84
+ env?: Record<string, string>;
85
+ }): SandboxResult {
86
+ // Pre-flight
87
+ const check = preflightCheck(command);
88
+ if (check) {
89
+ return { success: false, stdout: "", stderr: check, exitCode: -1, killed: false, durationMs: 0, sandboxDir: "", checkFailed: check };
90
+ }
91
+
92
+ const dir = ensureSandbox();
93
+ const timeout = Math.min(opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS, HARD_TIMEOUT_MS);
94
+ const t0 = Date.now();
95
+
96
+ try {
97
+ // For safe commands, run in-place without sandbox overhead
98
+ const firstWord = command.trim().split(/\s+/)[0].toLowerCase();
99
+ if (SAFE_COMMANDS.has(firstWord)) {
100
+ const result = execSync(command, { encoding: "utf-8", timeout, maxBuffer: MAX_OUTPUT_BYTES, cwd: opts?.cwd || dir, env: { ...process.env, ...(opts?.env || {}) } });
101
+ cleanup(dir);
102
+ return { success: true, stdout: result.slice(0, MAX_OUTPUT_BYTES), stderr: "", exitCode: 0, killed: false, durationMs: Date.now() - t0, sandboxDir: dir };
103
+ }
104
+
105
+ // Dangerous command — run in sandbox with isolation
106
+ const result = execSync(command, {
107
+ encoding: "utf-8",
108
+ timeout,
109
+ maxBuffer: MAX_OUTPUT_BYTES,
110
+ cwd: dir, // isolate to temp dir
111
+ env: { ...process.env, ...(opts?.env || {}), TMPDIR: dir, TEMP: dir },
112
+ windowsHide: true,
113
+ });
114
+
115
+ cleanup(dir);
116
+ return { success: true, stdout: result.slice(0, MAX_OUTPUT_BYTES), stderr: "", exitCode: 0, killed: false, durationMs: Date.now() - t0, sandboxDir: dir };
117
+
118
+ } catch (e: any) {
119
+ const durationMs = Date.now() - t0;
120
+ const killed = e.killed || e.signal !== undefined || durationMs >= timeout;
121
+ const stdout = (e.stdout || "").slice(0, MAX_OUTPUT_BYTES);
122
+ const stderr = (e.stderr || e.message || "").slice(0, MAX_OUTPUT_BYTES);
123
+ cleanup(dir);
124
+ return { success: false, stdout, stderr, exitCode: e.status || -1, killed, durationMs, sandboxDir: dir };
125
+ }
126
+ }
127
+
128
+ /* ═══════════════════════════════════════
129
+ Format result for display
130
+ ═══════════════════════════════════════ */
131
+ export function formatSandboxResult(r: SandboxResult): string {
132
+ if (r.checkFailed) return `[BLOCKED] ${r.checkFailed}`;
133
+
134
+ const parts: string[] = [];
135
+ if (r.stdout) parts.push(r.stdout);
136
+ if (r.stderr) parts.push(`[stderr]\n${r.stderr}`);
137
+ if (r.killed) parts.push(`[killed after ${r.durationMs}ms]`);
138
+ if (r.exitCode !== 0) parts.push(`[exit code: ${r.exitCode}]`);
139
+
140
+ parts.push(`[sandbox: ${r.sandboxDir || "n/a"} · ${r.durationMs}ms]`);
141
+ return parts.join("\n");
142
+ }
@@ -152,15 +152,13 @@ export function registerBuiltinTools(registry: ToolRegistry): void {
152
152
  { name: 'timeout', type: 'number', description: 'Timeout in milliseconds (default: 30000)', required: false },
153
153
  ],
154
154
  handler: async (params) => {
155
- const { execSync } = require('child_process');
156
155
  const cmd = params.command as string;
157
156
  const timeout = (params.timeout as number) || 30000;
158
157
  try {
159
- const result = execSync(cmd, { encoding: 'utf-8', timeout, maxBuffer: 10 * 1024 * 1024 });
160
- return result || '(command produced no output)';
161
- } catch (e: any) {
162
- return `Error: ${e.message || e}`;
163
- }
158
+ const { runInSandbox, formatSandboxResult } = require('../core/sandbox');
159
+ const result = runInSandbox(cmd, { timeoutMs: timeout });
160
+ return formatSandboxResult(result);
161
+ } catch (e: any) { return `Error: ${e.message || e}`; }
164
162
  },
165
163
  dangerous: true,
166
164
  });