@theokit/sdk 4.19.4 → 4.21.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/sandbox/bwrap.d.cts +75 -0
- package/dist/sandbox/bwrap.d.ts +75 -0
- package/dist/sandbox/index.cjs +506 -12
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +3 -0
- package/dist/sandbox/index.d.ts +3 -0
- package/dist/sandbox/index.js +487 -14
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/linux-sandbox.d.cts +115 -0
- package/dist/sandbox/linux-sandbox.d.ts +115 -0
- package/dist/sandbox/seccomp.d.cts +10 -0
- package/dist/sandbox/seccomp.d.ts +10 -0
- package/package.json +2 -2
package/dist/sandbox/index.js
CHANGED
|
@@ -1,8 +1,236 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
1
|
+
import { execFileSync, execFile } from 'child_process';
|
|
2
|
+
import { existsSync, mkdtempSync, writeFileSync, rmSync } from 'fs';
|
|
3
|
+
import path, { dirname, join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
2
5
|
import { mkdir, writeFile } from 'fs/promises';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
6
|
|
|
5
|
-
// src/sandbox/
|
|
7
|
+
// src/sandbox/bwrap.ts
|
|
8
|
+
function buildBwrapArgv(mode, opts) {
|
|
9
|
+
if (mode === "danger-full-access") return null;
|
|
10
|
+
const cwd = path.resolve(opts.cwd);
|
|
11
|
+
const gitDir = path.join(cwd, ".git");
|
|
12
|
+
const hasGit = opts.gitDirExists ?? existsSync(gitDir);
|
|
13
|
+
const argv = [
|
|
14
|
+
// core, always (bwrap.rs:318-332; user+pid namespaces explicit so it works as root in containers)
|
|
15
|
+
"--new-session",
|
|
16
|
+
"--die-with-parent",
|
|
17
|
+
"--unshare-user",
|
|
18
|
+
"--unshare-pid",
|
|
19
|
+
// full-read filesystem base (bwrap.rs:446-452)
|
|
20
|
+
"--ro-bind",
|
|
21
|
+
"/",
|
|
22
|
+
"/",
|
|
23
|
+
"--dev",
|
|
24
|
+
"/dev",
|
|
25
|
+
"--proc",
|
|
26
|
+
"/proc"
|
|
27
|
+
];
|
|
28
|
+
if (!opts.network) argv.push("--unshare-net");
|
|
29
|
+
if (opts.env) argv.push("--clearenv");
|
|
30
|
+
const setenv = {
|
|
31
|
+
...opts.env ?? {},
|
|
32
|
+
// the flag signals the child that network is unshared (spawn.rs:20,79)
|
|
33
|
+
...opts.network ? {} : { CODEX_SANDBOX_NETWORK_DISABLED: "1" }
|
|
34
|
+
};
|
|
35
|
+
for (const [k, v] of Object.entries(setenv)) argv.push("--setenv", k, v);
|
|
36
|
+
if (mode === "workspace-write") {
|
|
37
|
+
argv.push("--bind", cwd, cwd, "--bind", "/tmp", "/tmp");
|
|
38
|
+
if (hasGit) argv.push("--ro-bind", gitDir, gitDir);
|
|
39
|
+
}
|
|
40
|
+
argv.push("--chdir", cwd, "--");
|
|
41
|
+
return argv;
|
|
42
|
+
}
|
|
43
|
+
function detectBwrap(probes = realProbes) {
|
|
44
|
+
try {
|
|
45
|
+
const bin = probes.which();
|
|
46
|
+
if (!bin) return { ok: false, reason: "bwrap not found in PATH" };
|
|
47
|
+
const help = probes.helpText(bin);
|
|
48
|
+
if (!help?.includes("--perms")) {
|
|
49
|
+
return { ok: false, reason: `bwrap at ${bin} lacks --perms support (too old)` };
|
|
50
|
+
}
|
|
51
|
+
if (!probes.userns(bin)) {
|
|
52
|
+
return { ok: false, reason: "user namespaces unavailable (container/kernel restriction)" };
|
|
53
|
+
}
|
|
54
|
+
return { ok: true, bin };
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
reason: `bwrap probe failed: ${err instanceof Error ? err.message : String(err)}`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var sondagensReais = 0;
|
|
63
|
+
function realProbeCount() {
|
|
64
|
+
return sondagensReais;
|
|
65
|
+
}
|
|
66
|
+
var realProbes = {
|
|
67
|
+
which: () => {
|
|
68
|
+
sondagensReais++;
|
|
69
|
+
try {
|
|
70
|
+
const out = execFileSync("which", ["bwrap"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
71
|
+
if (!out || out.startsWith(process.cwd() + path.sep)) return null;
|
|
72
|
+
return out;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
helpText: (bin) => {
|
|
78
|
+
try {
|
|
79
|
+
return execFileSync(bin, ["--help"], { encoding: "utf8", timeout: 2e3 });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const e = err;
|
|
82
|
+
return [e.stdout, e.stderr].filter(Boolean).join("\n") || null;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
userns: (bin) => {
|
|
86
|
+
try {
|
|
87
|
+
execFileSync(bin, ["--unshare-user", "--unshare-net", "--ro-bind", "/", "/", "/bin/true"], {
|
|
88
|
+
timeout: 500,
|
|
89
|
+
stdio: "ignore"
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var memo;
|
|
98
|
+
function detectBwrapMemoizado(probes = realProbes) {
|
|
99
|
+
memo ??= detectBwrap(probes);
|
|
100
|
+
if (memo.ok && !existsSync(memo.bin)) {
|
|
101
|
+
memo = { ok: false, reason: `bwrap disappeared from ${memo.bin} after detection` };
|
|
102
|
+
}
|
|
103
|
+
return memo;
|
|
104
|
+
}
|
|
105
|
+
function resetBwrapMemo() {
|
|
106
|
+
memo = void 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/internal/security/redact.ts
|
|
110
|
+
var REDACT_ENABLED = readEnvOnce();
|
|
111
|
+
function readEnvOnce() {
|
|
112
|
+
const raw = process.env.THEOKIT_REDACT_SECRETS;
|
|
113
|
+
if (raw === void 0) return true;
|
|
114
|
+
return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
|
|
115
|
+
}
|
|
116
|
+
var warnedOptOut = false;
|
|
117
|
+
if (!REDACT_ENABLED && !warnedOptOut) {
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
"[theokit-sdk] Secret redaction is DISABLED via THEOKIT_REDACT_SECRETS. Credentials may leak into errors, telemetry, logs, transcripts.\n"
|
|
120
|
+
);
|
|
121
|
+
warnedOptOut = true;
|
|
122
|
+
}
|
|
123
|
+
var BUILTIN_PATTERNS = [
|
|
124
|
+
// T5.4: 30+ vendor prefixes (was 12 pre-T5.4). Order matters — more
|
|
125
|
+
// specific prefixes precede generic ones (e.g., sk-ant-admin01 before
|
|
126
|
+
// sk-ant-, sk-proj- before sk-). PEM block deliberately first so its
|
|
127
|
+
// multi-line span runs before any per-line patterns can fire.
|
|
128
|
+
/-----BEGIN[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\s\S]+?-----END[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g,
|
|
129
|
+
// JWT — exact 3-segment base64url. Dotted; the body floor of 4 chars per
|
|
130
|
+
// segment matches the minimum legal payload while skipping `a.b.c` noise.
|
|
131
|
+
/eyJ[A-Za-z0-9_-]{4,}\.eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g,
|
|
132
|
+
// Azure Storage SAS — match the sig= component (URL-encoded base64).
|
|
133
|
+
/(?<=[?&]sig=)[A-Za-z0-9%+/]{20,}/g,
|
|
134
|
+
// Anthropic
|
|
135
|
+
/sk-ant-admin01-[A-Za-z0-9_-]{10,}/g,
|
|
136
|
+
// Anthropic admin keys (must precede sk-ant-)
|
|
137
|
+
/sk-ant-[A-Za-z0-9_-]{10,}/g,
|
|
138
|
+
// Anthropic regular
|
|
139
|
+
// OpenAI family + clones (sk- generic must come AFTER all sk-foo- variants)
|
|
140
|
+
/sk-proj-[A-Za-z0-9_-]{10,}/g,
|
|
141
|
+
// OpenAI project key (must precede sk- generic)
|
|
142
|
+
/sk-[A-Za-z0-9_-]{10,}/g,
|
|
143
|
+
// OpenAI / OpenRouter / DeepInfra / Together / DeepSeek
|
|
144
|
+
// Provider prefixes (alphabetized for maintainability)
|
|
145
|
+
/AIza[A-Za-z0-9_-]{35}/g,
|
|
146
|
+
// Google API key
|
|
147
|
+
/AKIA[A-Z0-9]{16}/g,
|
|
148
|
+
// AWS access key
|
|
149
|
+
/fw_[A-Za-z0-9]{20,}/g,
|
|
150
|
+
// Fireworks
|
|
151
|
+
/glpat-[A-Za-z0-9_-]{20}/g,
|
|
152
|
+
// GitLab PAT
|
|
153
|
+
/ghp_[A-Za-z0-9]{36}/g,
|
|
154
|
+
// GitHub PAT classic
|
|
155
|
+
/github_pat_[A-Za-z0-9_]{82}/g,
|
|
156
|
+
// GitHub PAT fine-grained
|
|
157
|
+
/gsk_[A-Za-z0-9]{20,}/g,
|
|
158
|
+
// Groq
|
|
159
|
+
/hf_[A-Za-z0-9]{20,}/g,
|
|
160
|
+
// HuggingFace
|
|
161
|
+
/\bpa-[A-Za-z0-9_-]{20,}/g,
|
|
162
|
+
// Voyage AI (word-boundary to skip CSS / kebab IDs)
|
|
163
|
+
/pcsk_[A-Za-z0-9_-]{20,}/g,
|
|
164
|
+
// Pinecone
|
|
165
|
+
/pplx-[A-Za-z0-9_-]{20,}/g,
|
|
166
|
+
// Perplexity
|
|
167
|
+
/r8_[A-Za-z0-9_-]{20,}/g,
|
|
168
|
+
// Replicate
|
|
169
|
+
/rk_live_[A-Za-z0-9]{20,}/g,
|
|
170
|
+
// Stripe restricted
|
|
171
|
+
/sk_live_[A-Za-z0-9]{20,}/g,
|
|
172
|
+
// Stripe secret
|
|
173
|
+
/sntrys_[A-Za-z0-9]{40,}/g,
|
|
174
|
+
// Sentry user auth
|
|
175
|
+
/xai-[A-Za-z0-9_-]{20,}/g,
|
|
176
|
+
// xAI (Grok)
|
|
177
|
+
/xox[bpasr]-[A-Za-z0-9-]{10,}/g,
|
|
178
|
+
//Slack tokens
|
|
179
|
+
// Additional unique-prefix tokens with low false-positive risk
|
|
180
|
+
/npm_[A-Za-z0-9]{36}/g,
|
|
181
|
+
// npm access token
|
|
182
|
+
/SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g,
|
|
183
|
+
// SendGrid
|
|
184
|
+
/\bSK[A-Za-z0-9]{32}\b/g,
|
|
185
|
+
// Twilio API SID (word-boundary to skip CSS class noise)
|
|
186
|
+
/\bkey-[a-f0-9]{32}\b/g,
|
|
187
|
+
// Mailgun (hex-only narrows false positives)
|
|
188
|
+
/MT[A-Za-z0-9_-]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}/g,
|
|
189
|
+
// Discord bot
|
|
190
|
+
/\b(?:sdk|mob)-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b/g
|
|
191
|
+
// LaunchDarkly
|
|
192
|
+
];
|
|
193
|
+
var BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9_\-.+/=]{8,})/g;
|
|
194
|
+
var PARAM_PATTERN = /(\b(?:access_token|api_key|api-key|client_secret|credential|credentials|id_token|jwt|password|private_key|refresh_token|secret|service_account|session_token|token|x-api-key)\b["']?\s*[:=]\s*["']?)([A-Za-z0-9_\-.+/]+)/gi;
|
|
195
|
+
var _extraPatterns = [];
|
|
196
|
+
function maskToken(token) {
|
|
197
|
+
if (token.length < 18) return "***";
|
|
198
|
+
return `${token.slice(0, 6)}...${token.slice(-4)}`;
|
|
199
|
+
}
|
|
200
|
+
var MASK_SHAPE = /^.{6}\.\.\..{4}$/s;
|
|
201
|
+
function coerceToString(value) {
|
|
202
|
+
if (typeof value === "string") return value;
|
|
203
|
+
if (value === null || value === void 0) return null;
|
|
204
|
+
if (typeof value === "object") {
|
|
205
|
+
try {
|
|
206
|
+
const s = JSON.stringify(value);
|
|
207
|
+
return s === void 0 ? null : s;
|
|
208
|
+
} catch {
|
|
209
|
+
return "[unredactable: circular]";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return String(value);
|
|
213
|
+
}
|
|
214
|
+
function redactSecrets(text, opts) {
|
|
215
|
+
const coerced = coerceToString(text);
|
|
216
|
+
if (coerced === null) return "";
|
|
217
|
+
if (!REDACT_ENABLED) return coerced;
|
|
218
|
+
let s = coerced;
|
|
219
|
+
for (const re of BUILTIN_PATTERNS) {
|
|
220
|
+
s = s.replace(re, (m) => maskToken(m));
|
|
221
|
+
}
|
|
222
|
+
for (const re of _extraPatterns) {
|
|
223
|
+
s = s.replace(re, (m) => maskToken(m));
|
|
224
|
+
}
|
|
225
|
+
{
|
|
226
|
+
s = s.replace(BEARER_PATTERN, (_, prefix) => `${prefix}***`);
|
|
227
|
+
s = s.replace(PARAM_PATTERN, (whole, prefix, value) => {
|
|
228
|
+
if (MASK_SHAPE.test(value)) return whole;
|
|
229
|
+
return `${prefix}***`;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return s;
|
|
233
|
+
}
|
|
6
234
|
|
|
7
235
|
// src/internal/runtime/lifecycle/env-policy.ts
|
|
8
236
|
var SECRET_PATTERNS = [
|
|
@@ -89,15 +317,15 @@ var SandboxBackend = class {
|
|
|
89
317
|
env: config.env ?? "inherit-scrubbed"
|
|
90
318
|
};
|
|
91
319
|
}
|
|
92
|
-
async readFile(
|
|
93
|
-
const result = await this.execute(`cat ${this.shellEscape(
|
|
320
|
+
async readFile(path2) {
|
|
321
|
+
const result = await this.execute(`cat ${this.shellEscape(path2)}`);
|
|
94
322
|
if (result.exitCode !== 0) {
|
|
95
323
|
throw new Error(`readFile failed: ${result.stderr}`);
|
|
96
324
|
}
|
|
97
325
|
return result.stdout;
|
|
98
326
|
}
|
|
99
|
-
async writeFile(
|
|
100
|
-
await this.uploadFile(
|
|
327
|
+
async writeFile(path2, content) {
|
|
328
|
+
await this.uploadFile(path2, content);
|
|
101
329
|
}
|
|
102
330
|
async glob(pattern, cwd) {
|
|
103
331
|
const dir = cwd ?? this.config.workDir ?? ".";
|
|
@@ -107,16 +335,16 @@ var SandboxBackend = class {
|
|
|
107
335
|
if (result.exitCode !== 0) return [];
|
|
108
336
|
return result.stdout.trim().split("\n").filter(Boolean);
|
|
109
337
|
}
|
|
110
|
-
async grep(pattern,
|
|
111
|
-
const target =
|
|
338
|
+
async grep(pattern, path2) {
|
|
339
|
+
const target = path2 ?? ".";
|
|
112
340
|
const result = await this.execute(
|
|
113
341
|
`grep -rn ${this.shellEscape(pattern)} ${this.shellEscape(target)} 2>/dev/null`
|
|
114
342
|
);
|
|
115
343
|
if (result.exitCode !== 0) return [];
|
|
116
344
|
return result.stdout.trim().split("\n").filter(Boolean);
|
|
117
345
|
}
|
|
118
|
-
async listDir(
|
|
119
|
-
const result = await this.execute(`ls -1 ${this.shellEscape(
|
|
346
|
+
async listDir(path2) {
|
|
347
|
+
const result = await this.execute(`ls -1 ${this.shellEscape(path2)}`);
|
|
120
348
|
if (result.exitCode !== 0) return [];
|
|
121
349
|
return result.stdout.trim().split("\n").filter(Boolean);
|
|
122
350
|
}
|
|
@@ -174,13 +402,258 @@ var LocalSandbox = class extends SandboxBackend {
|
|
|
174
402
|
timedOut
|
|
175
403
|
};
|
|
176
404
|
}
|
|
177
|
-
async uploadFile(
|
|
178
|
-
const fullPath =
|
|
405
|
+
async uploadFile(path2, content) {
|
|
406
|
+
const fullPath = path2.startsWith("/") ? path2 : `${this.config.workDir}/${path2}`;
|
|
179
407
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
180
408
|
await writeFile(fullPath, content, "utf-8");
|
|
181
409
|
}
|
|
182
410
|
};
|
|
183
411
|
|
|
412
|
+
// src/sandbox/seccomp.ts
|
|
413
|
+
var BPF_LD = 0;
|
|
414
|
+
var BPF_W = 0;
|
|
415
|
+
var BPF_ABS = 32;
|
|
416
|
+
var BPF_JMP = 5;
|
|
417
|
+
var BPF_JEQ = 16;
|
|
418
|
+
var BPF_JGE = 48;
|
|
419
|
+
var BPF_K = 0;
|
|
420
|
+
var BPF_RET = 6;
|
|
421
|
+
var LD_ABS_W = BPF_LD | BPF_W | BPF_ABS;
|
|
422
|
+
var JEQ_K = BPF_JMP | BPF_JEQ | BPF_K;
|
|
423
|
+
var JGE_K = BPF_JMP | BPF_JGE | BPF_K;
|
|
424
|
+
var RET_K = BPF_RET | BPF_K;
|
|
425
|
+
var OFF_NR = 0;
|
|
426
|
+
var OFF_ARCH = 4;
|
|
427
|
+
var OFF_ARG0 = 16;
|
|
428
|
+
var AUDIT_ARCH_X86_64 = 3221225534;
|
|
429
|
+
var X32_BIT = 1073741824;
|
|
430
|
+
var SECCOMP_RET_ALLOW = 2147418112;
|
|
431
|
+
var SECCOMP_RET_ERRNO = 327680;
|
|
432
|
+
var EPERM = 1;
|
|
433
|
+
var RET_EPERM = SECCOMP_RET_ERRNO | EPERM & 65535;
|
|
434
|
+
var SECCOMP_RET_KILL_PROCESS = 2147483648;
|
|
435
|
+
var AF_UNIX = 1;
|
|
436
|
+
var ALWAYS_DENIED = [101, 310, 311, 425, 426, 427];
|
|
437
|
+
var NETWORK_DENIED = [42, 43, 288, 49, 50, 52, 51, 48, 44, 307, 299, 55, 54];
|
|
438
|
+
var SOCKET_SYSCALLS = [41, 53];
|
|
439
|
+
var stmt = (code, k) => ({ code, jt: 0, jf: 0, k });
|
|
440
|
+
var jmp = (code, k, jt, jf) => ({ code, jt, jf, k });
|
|
441
|
+
function buildSeccompFilter(opts) {
|
|
442
|
+
const insns = [];
|
|
443
|
+
const ALLOW = /* @__PURE__ */ Symbol("ALLOW");
|
|
444
|
+
const DENY = /* @__PURE__ */ Symbol("DENY");
|
|
445
|
+
const KILL = /* @__PURE__ */ Symbol("KILL");
|
|
446
|
+
const body = [];
|
|
447
|
+
const push = (code, k, jt = 0, jf = 0) => {
|
|
448
|
+
body.push({ code, k, jt, jf });
|
|
449
|
+
};
|
|
450
|
+
push(LD_ABS_W, OFF_ARCH);
|
|
451
|
+
push(JEQ_K, AUDIT_ARCH_X86_64, 0, KILL);
|
|
452
|
+
push(LD_ABS_W, OFF_NR);
|
|
453
|
+
push(JGE_K, X32_BIT, KILL, 0);
|
|
454
|
+
const denied = opts.networkRestricted ? [...ALWAYS_DENIED, ...NETWORK_DENIED] : [...ALWAYS_DENIED];
|
|
455
|
+
for (const nr of denied) push(JEQ_K, nr, DENY, 0);
|
|
456
|
+
if (opts.networkRestricted) {
|
|
457
|
+
for (const sysno of SOCKET_SYSCALLS) {
|
|
458
|
+
push(JEQ_K, sysno, 0, 2);
|
|
459
|
+
push(LD_ABS_W, OFF_ARG0);
|
|
460
|
+
push(JEQ_K, AF_UNIX, ALLOW, DENY);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
push(RET_K, SECCOMP_RET_ALLOW);
|
|
464
|
+
const allowIdx = body.length - 1;
|
|
465
|
+
push(RET_K, RET_EPERM);
|
|
466
|
+
const denyIdx = body.length - 1;
|
|
467
|
+
push(RET_K, SECCOMP_RET_KILL_PROCESS);
|
|
468
|
+
const killIdx = body.length - 1;
|
|
469
|
+
const resolve = (i, t) => {
|
|
470
|
+
const abs = t === ALLOW ? allowIdx : t === DENY ? denyIdx : t === KILL ? killIdx : i + 1 + t;
|
|
471
|
+
const off = abs - (i + 1);
|
|
472
|
+
if (off < 0 || off > 255) throw new RangeError(`seccomp jump out of range at ${i}: ${off}`);
|
|
473
|
+
return off;
|
|
474
|
+
};
|
|
475
|
+
for (const [i, b] of body.entries()) {
|
|
476
|
+
if (b.code === JEQ_K || b.code === JGE_K) {
|
|
477
|
+
insns.push(jmp(b.code, b.k, resolve(i, b.jt), resolve(i, b.jf)));
|
|
478
|
+
} else {
|
|
479
|
+
insns.push(stmt(b.code, b.k));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const buf = Buffer.alloc(insns.length * 8);
|
|
483
|
+
insns.forEach((ins, i) => {
|
|
484
|
+
buf.writeUInt16LE(ins.code, i * 8);
|
|
485
|
+
buf.writeUInt8(ins.jt, i * 8 + 2);
|
|
486
|
+
buf.writeUInt8(ins.jf, i * 8 + 3);
|
|
487
|
+
buf.writeUInt32LE(ins.k >>> 0, i * 8 + 4);
|
|
488
|
+
});
|
|
489
|
+
return buf;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/sandbox/linux-sandbox.ts
|
|
493
|
+
function shellQuote(s) {
|
|
494
|
+
return `'${s.replaceAll("'", `'\\''`)}'`;
|
|
495
|
+
}
|
|
496
|
+
function wrapCommandForSandbox(mode, opts, command) {
|
|
497
|
+
const argv = buildBwrapArgv(mode, { cwd: opts.cwd, network: opts.network, env: opts.env });
|
|
498
|
+
if (argv === null) return null;
|
|
499
|
+
const bin = opts.bin ?? "bwrap";
|
|
500
|
+
const seccompArgv = opts.seccompPath !== void 0 ? ["--seccomp", "3"] : [];
|
|
501
|
+
const base = `${shellQuote(bin)} ${[...argv.slice(0, -1), ...seccompArgv, "--"].map(shellQuote).join(" ")} /bin/sh -c ${shellQuote(command)}`;
|
|
502
|
+
return opts.seccompPath !== void 0 ? `${base} 3< ${shellQuote(opts.seccompPath)}` : base;
|
|
503
|
+
}
|
|
504
|
+
var ENV_ALLOWLIST = [
|
|
505
|
+
"PATH",
|
|
506
|
+
"HOME",
|
|
507
|
+
"LANG",
|
|
508
|
+
"LC_ALL",
|
|
509
|
+
"LC_CTYPE",
|
|
510
|
+
"TERM",
|
|
511
|
+
"USER",
|
|
512
|
+
"TMPDIR",
|
|
513
|
+
"SHELL"
|
|
514
|
+
];
|
|
515
|
+
function allowlistedEnv(source = process.env) {
|
|
516
|
+
const out = {};
|
|
517
|
+
for (const k of ENV_ALLOWLIST) {
|
|
518
|
+
const v = source[k];
|
|
519
|
+
if (v !== void 0) out[k] = v;
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
var LinuxSandbox = class extends LocalSandbox {
|
|
524
|
+
mode;
|
|
525
|
+
network;
|
|
526
|
+
cwd;
|
|
527
|
+
bin;
|
|
528
|
+
env;
|
|
529
|
+
/** M63 — path to the cBPF seccomp program written host-side; passed to `bwrap --seccomp 3` via a
|
|
530
|
+
* shell redirect. `undefined` when the network is unrestricted OR generation failed (honest fallback). */
|
|
531
|
+
seccompPath;
|
|
532
|
+
constructor(config, opts) {
|
|
533
|
+
super(config);
|
|
534
|
+
this.mode = opts.mode;
|
|
535
|
+
this.network = opts.network ?? false;
|
|
536
|
+
this.cwd = config.workDir ?? process.cwd();
|
|
537
|
+
this.bin = opts.bin ?? "bwrap";
|
|
538
|
+
this.env = opts.env ?? allowlistedEnv();
|
|
539
|
+
this.seccompPath = this.network ? void 0 : restrictedSeccompPath();
|
|
540
|
+
}
|
|
541
|
+
/** Extracted for test visibility — delegates to the pure `wrapCommandForSandbox` (M57, single wrap SoT). */
|
|
542
|
+
wrapCommand(command) {
|
|
543
|
+
return wrapCommandForSandbox(
|
|
544
|
+
this.mode,
|
|
545
|
+
{
|
|
546
|
+
cwd: this.cwd,
|
|
547
|
+
network: this.network,
|
|
548
|
+
env: this.env,
|
|
549
|
+
bin: this.bin,
|
|
550
|
+
seccompPath: this.seccompPath
|
|
551
|
+
},
|
|
552
|
+
command
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
execute(command, opts) {
|
|
556
|
+
const wrapped = this.wrapCommand(command);
|
|
557
|
+
if (wrapped === null) return super.execute(command, opts);
|
|
558
|
+
return super.execute(wrapped, opts);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
var warnedNonX64 = false;
|
|
562
|
+
function seccompPathForArch(arch, warn) {
|
|
563
|
+
if (arch !== "x64") {
|
|
564
|
+
if (!warnedNonX64) {
|
|
565
|
+
warnedNonX64 = true;
|
|
566
|
+
warn(
|
|
567
|
+
`[sandbox] seccomp syscall filter unsupported on ${arch} (x86_64 only in v1) \u2014 running without the filter; bwrap FS/network confinement still applies.`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return void 0;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const dir = mkdtempSync(join(tmpdir(), "ab-seccomp-"));
|
|
574
|
+
const path2 = join(dir, "filter.bpf");
|
|
575
|
+
writeFileSync(path2, buildSeccompFilter({ networkRestricted: true }));
|
|
576
|
+
const cleanup = () => rmSync(dir, { recursive: true, force: true });
|
|
577
|
+
process.once("exit", cleanup);
|
|
578
|
+
process.once("SIGINT", cleanup);
|
|
579
|
+
process.once("SIGTERM", cleanup);
|
|
580
|
+
return path2;
|
|
581
|
+
} catch (err) {
|
|
582
|
+
warn(
|
|
583
|
+
`[sandbox] seccomp filter unavailable (${err instanceof Error ? err.message : String(err)}) \u2014 running without syscall filter (bwrap FS/network confinement still applies).`
|
|
584
|
+
);
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
var seccompFilterPath;
|
|
589
|
+
function restrictedSeccompPath() {
|
|
590
|
+
if (seccompFilterPath !== void 0) return seccompFilterPath ?? void 0;
|
|
591
|
+
const path2 = seccompPathForArch(process.arch, (m) => console.warn(redactSecrets(m)));
|
|
592
|
+
seccompFilterPath = path2 ?? null;
|
|
593
|
+
return path2;
|
|
594
|
+
}
|
|
595
|
+
var warnedUnavailable = false;
|
|
596
|
+
function resetSandboxWarnLatch() {
|
|
597
|
+
warnedUnavailable = false;
|
|
598
|
+
}
|
|
599
|
+
function resolveSandboxPosture(opts) {
|
|
600
|
+
if (opts.mode === "danger-full-access") {
|
|
601
|
+
return { mode: opts.mode, enforced: false, detail: "no confinement (danger-full-access)" };
|
|
602
|
+
}
|
|
603
|
+
const detection = (opts.detect ?? detectBwrapMemoizado)();
|
|
604
|
+
if (!detection.ok) {
|
|
605
|
+
return { mode: opts.mode, enforced: false, detail: `tool-gating only \u2014 ${detection.reason}` };
|
|
606
|
+
}
|
|
607
|
+
return { mode: opts.mode, enforced: true, detail: "kernel (bwrap)" };
|
|
608
|
+
}
|
|
609
|
+
function createSandboxBackend(opts) {
|
|
610
|
+
const config = { workDir: opts.workDir, timeoutMs: opts.timeoutMs };
|
|
611
|
+
if (opts.mode === "danger-full-access") return new LocalSandbox(config);
|
|
612
|
+
const detection = (opts.detect ?? detectBwrapMemoizado)();
|
|
613
|
+
if (!detection.ok) {
|
|
614
|
+
if (!warnedUnavailable) {
|
|
615
|
+
warnedUnavailable = true;
|
|
616
|
+
const warn = opts.warn ?? ((m) => console.warn(redactSecrets(m)));
|
|
617
|
+
warn(
|
|
618
|
+
`[sandbox] OS-level enforcement unavailable (${detection.reason}) \u2014 falling back to tool-level gating only (sandbox_mode=${opts.mode}).`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
return new LocalSandbox(config);
|
|
622
|
+
}
|
|
623
|
+
return new LinuxSandbox(config, { mode: opts.mode, network: opts.network, bin: detection.bin });
|
|
624
|
+
}
|
|
625
|
+
var avisouInterativo = false;
|
|
626
|
+
function resetInteractiveWarnLatch() {
|
|
627
|
+
avisouInterativo = false;
|
|
628
|
+
}
|
|
629
|
+
function interactiveWrapCommand(opts) {
|
|
630
|
+
return (command, cwd) => {
|
|
631
|
+
if (opts.mode === "danger-full-access") return null;
|
|
632
|
+
const detection = (opts.detect ?? detectBwrapMemoizado)();
|
|
633
|
+
if (!detection.ok) {
|
|
634
|
+
if (!avisouInterativo) {
|
|
635
|
+
avisouInterativo = true;
|
|
636
|
+
const warn = opts.warn ?? ((m) => console.warn(redactSecrets(m)));
|
|
637
|
+
warn(
|
|
638
|
+
`[sandbox] OS-level enforcement unavailable (${detection.reason}) \u2014 interactive session runs WITHOUT kernel confinement (sandbox_mode=${opts.mode}).`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
return wrapCommandForSandbox(
|
|
644
|
+
opts.mode,
|
|
645
|
+
{
|
|
646
|
+
cwd,
|
|
647
|
+
network: opts.network ?? false,
|
|
648
|
+
env: allowlistedEnv(),
|
|
649
|
+
bin: detection.bin,
|
|
650
|
+
seccompPath: restrictedSeccompPath()
|
|
651
|
+
},
|
|
652
|
+
command
|
|
653
|
+
);
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
184
657
|
// src/errors.ts
|
|
185
658
|
var TheokitAgentError = class extends Error {
|
|
186
659
|
name = "TheokitAgentError";
|
|
@@ -246,6 +719,6 @@ async function provisionRepo(sandboxOrOpts, maybeOpts) {
|
|
|
246
719
|
return { repoDir };
|
|
247
720
|
}
|
|
248
721
|
|
|
249
|
-
export { LocalSandbox, RepoProvisionError, SandboxBackend, SandboxNotAvailableError, SandboxSecurityError, provisionRepo, resolveSandbox };
|
|
722
|
+
export { LinuxSandbox, LocalSandbox, RepoProvisionError, SandboxBackend, SandboxNotAvailableError, SandboxSecurityError, allowlistedEnv, buildBwrapArgv, buildSeccompFilter, createSandboxBackend, detectBwrap, detectBwrapMemoizado, interactiveWrapCommand, provisionRepo, realProbeCount, realProbes, resetBwrapMemo, resetInteractiveWarnLatch, resetSandboxWarnLatch, resolveSandbox, resolveSandboxPosture, restrictedSeccompPath, seccompPathForArch, wrapCommandForSandbox };
|
|
250
723
|
//# sourceMappingURL=index.js.map
|
|
251
724
|
//# sourceMappingURL=index.js.map
|