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.
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +44 -1
- package/dist/core/agent.js.map +1 -1
- package/dist/core/arbitrate.d.ts +32 -0
- package/dist/core/arbitrate.d.ts.map +1 -0
- package/dist/core/arbitrate.js +136 -0
- package/dist/core/arbitrate.js.map +1 -0
- package/dist/core/estimate.d.ts +30 -0
- package/dist/core/estimate.d.ts.map +1 -0
- package/dist/core/estimate.js +94 -0
- package/dist/core/estimate.js.map +1 -0
- package/dist/core/evolve.d.ts +43 -0
- package/dist/core/evolve.d.ts.map +1 -0
- package/dist/core/evolve.js +201 -0
- package/dist/core/evolve.js.map +1 -0
- package/dist/core/filter.d.ts +16 -0
- package/dist/core/filter.d.ts.map +1 -0
- package/dist/core/filter.js +91 -0
- package/dist/core/filter.js.map +1 -0
- package/dist/core/index.d.ts +7 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +13 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/longdoc.d.ts +41 -0
- package/dist/core/longdoc.d.ts.map +1 -0
- package/dist/core/longdoc.js +128 -0
- package/dist/core/longdoc.js.map +1 -0
- package/dist/core/sandbox.d.ts +24 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandbox.js +158 -0
- package/dist/core/sandbox.js.map +1 -0
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +3 -3
- package/dist/tools/builtin.js.map +1 -1
- package/package.json +1 -1
- package/src/core/agent.ts +33 -1
- package/src/core/arbitrate.ts +162 -0
- package/src/core/estimate.ts +104 -0
- package/src/core/evolve.ts +191 -0
- package/src/core/filter.ts +103 -0
- package/src/core/index.ts +8 -2
- package/src/core/longdoc.ts +155 -0
- package/src/core/sandbox.ts +142 -0
- 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
|
+
}
|
package/src/tools/builtin.ts
CHANGED
|
@@ -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
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
});
|