@theokit/sdk 4.19.4 → 4.20.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.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * M53 — bubblewrap argv + honest detection, faithful to Codex's Linux sandbox
3
+ * (`codex-rs/linux-sandbox/src/bwrap.rs` + `codex-rs/sandboxing/src/bwrap.rs`).
4
+ *
5
+ * HONEST SCOPE: filesystem confinement + network isolation via bwrap, PLUS the second stage —
6
+ * a cBPF seccomp syscall filter (`agents/sandbox/seccomp.ts`), wired in `agents/sandbox/backend.ts`
7
+ * via `restrictedSeccompPath()`. Portado no M63; este bloco afirmava o contrário até o M67 e
8
+ * SUBDECLARAVA a postura de segurança real. Limite honesto que permanece: o filtro é **x86_64**
9
+ * (guarda de arquitetura recusa instalar em outra arch, com WARN, e o confinamento de FS/rede do
10
+ * bwrap segue valendo) **e** só é instalado quando a rede está restrita (`backend.ts:87`, fiel a
11
+ * `landlock.rs:96-117`): com rede ligada não há filtro de syscall, apenas o confinamento de FS do
12
+ * bwrap. `danger-full-access` pula o bwrap por completo, espelhando `bwrap.rs:245-252`.
13
+ * Deltas versus o Codex seguem documentados em docs/CODEX-PARITY.md.
14
+ */
15
+ /**
16
+ * Os tres modos canonicos do Codex. Definidos AQUI porque sao vocabulario do sandbox, nao da
17
+ * configuracao do consumidor: `danger-full-access` significa "nao embrulhe", e essa e uma decisao do
18
+ * subsistema de confinamento.
19
+ */
20
+ export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
21
+ export interface BwrapArgvOptions {
22
+ /** Workspace root — the single RW bind under `workspace-write` (protocol.rs:1189-1200). */
23
+ cwd: string;
24
+ /** `true` removes `--unshare-net` (policy `network_access`, default false). */
25
+ network?: boolean;
26
+ /** Injectable for tests; defaults to a real `existsSync` check on `<cwd>/.git`. */
27
+ gitDirExists?: boolean;
28
+ /**
29
+ * When present, emit `--clearenv` and re-inject ONLY these vars (Codex env_clear model,
30
+ * `exec_env.rs:25-31`). Closes the denylist gap: a secret in an oddly-named var never reaches the
31
+ * sandboxed child. Omitted ⇒ inherit the parent env (backward-compatible; SDK scrub still applies).
32
+ */
33
+ env?: Record<string, string>;
34
+ }
35
+ /**
36
+ * Pure argv builder. Returns the bwrap flags ending in `--` (caller appends `/bin/sh -c <cmd>`),
37
+ * or `null` when the policy skips the sandbox entirely (`danger-full-access`).
38
+ */
39
+ export declare function buildBwrapArgv(mode: SandboxMode, opts: BwrapArgvOptions): string[] | null;
40
+ /** Injectable probes — each mirrors one Codex availability check. */
41
+ export interface BwrapProbes {
42
+ /** `which bwrap` outside the cwd (anti-hijack, sandboxing/src/bwrap.rs:168-191). */
43
+ which: () => string | null;
44
+ /** `bwrap --help` text — must advertise `--perms` (launcher.rs:108-124). */
45
+ helpText: (bin: string) => string | null;
46
+ /** Active user-namespace probe with timeout (sandboxing/src/bwrap.rs:74-136). */
47
+ userns: (bin: string) => boolean;
48
+ }
49
+ export type BwrapDetection = {
50
+ ok: true;
51
+ bin: string;
52
+ } | {
53
+ ok: false;
54
+ reason: string;
55
+ };
56
+ /** Honest detection — fail-closed on every probe; NEVER throws (callers WARN + fall back). */
57
+ export declare function detectBwrap(probes?: BwrapProbes): BwrapDetection;
58
+ /** Quantas sondagens reais rodaram. Seam de TESTE — o gate de performance conta isto. */
59
+ export declare function realProbeCount(): number;
60
+ /** Real probes used in production. */
61
+ export declare const realProbes: BwrapProbes;
62
+ /**
63
+ * `detectBwrap` com memoização — o que a produção deve chamar.
64
+ *
65
+ * Note que `detectBwrap` em si **não** memoiza, de propósito: ele aceita probes injetados, e memoizar
66
+ * ali faria um teste com probes falsos envenenar o cache do processo para todos os outros.
67
+ *
68
+ * A revalidação do positivo NÃO é uma re-sondagem: `detectBwrap` gasta três probes (subprocesso
69
+ * `which` + `--help` + namespace de usuário). Aqui só se confirma que o binário validado continua no
70
+ * lugar. Se sumiu, o memo é rebaixado a negativo com o motivo dito — nunca promovido a positivo, que
71
+ * exigiria a sondagem cara de volta.
72
+ */
73
+ export declare function detectBwrapMemoizado(probes?: BwrapProbes): BwrapDetection;
74
+ /** Seam de TESTE — limpa o memo. Produção nunca chama (ver m71-custo-por-turn#ADR-1). */
75
+ export declare function resetBwrapMemo(): void;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * M53 — bubblewrap argv + honest detection, faithful to Codex's Linux sandbox
3
+ * (`codex-rs/linux-sandbox/src/bwrap.rs` + `codex-rs/sandboxing/src/bwrap.rs`).
4
+ *
5
+ * HONEST SCOPE: filesystem confinement + network isolation via bwrap, PLUS the second stage —
6
+ * a cBPF seccomp syscall filter (`agents/sandbox/seccomp.ts`), wired in `agents/sandbox/backend.ts`
7
+ * via `restrictedSeccompPath()`. Portado no M63; este bloco afirmava o contrário até o M67 e
8
+ * SUBDECLARAVA a postura de segurança real. Limite honesto que permanece: o filtro é **x86_64**
9
+ * (guarda de arquitetura recusa instalar em outra arch, com WARN, e o confinamento de FS/rede do
10
+ * bwrap segue valendo) **e** só é instalado quando a rede está restrita (`backend.ts:87`, fiel a
11
+ * `landlock.rs:96-117`): com rede ligada não há filtro de syscall, apenas o confinamento de FS do
12
+ * bwrap. `danger-full-access` pula o bwrap por completo, espelhando `bwrap.rs:245-252`.
13
+ * Deltas versus o Codex seguem documentados em docs/CODEX-PARITY.md.
14
+ */
15
+ /**
16
+ * Os tres modos canonicos do Codex. Definidos AQUI porque sao vocabulario do sandbox, nao da
17
+ * configuracao do consumidor: `danger-full-access` significa "nao embrulhe", e essa e uma decisao do
18
+ * subsistema de confinamento.
19
+ */
20
+ export type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
21
+ export interface BwrapArgvOptions {
22
+ /** Workspace root — the single RW bind under `workspace-write` (protocol.rs:1189-1200). */
23
+ cwd: string;
24
+ /** `true` removes `--unshare-net` (policy `network_access`, default false). */
25
+ network?: boolean;
26
+ /** Injectable for tests; defaults to a real `existsSync` check on `<cwd>/.git`. */
27
+ gitDirExists?: boolean;
28
+ /**
29
+ * When present, emit `--clearenv` and re-inject ONLY these vars (Codex env_clear model,
30
+ * `exec_env.rs:25-31`). Closes the denylist gap: a secret in an oddly-named var never reaches the
31
+ * sandboxed child. Omitted ⇒ inherit the parent env (backward-compatible; SDK scrub still applies).
32
+ */
33
+ env?: Record<string, string>;
34
+ }
35
+ /**
36
+ * Pure argv builder. Returns the bwrap flags ending in `--` (caller appends `/bin/sh -c <cmd>`),
37
+ * or `null` when the policy skips the sandbox entirely (`danger-full-access`).
38
+ */
39
+ export declare function buildBwrapArgv(mode: SandboxMode, opts: BwrapArgvOptions): string[] | null;
40
+ /** Injectable probes — each mirrors one Codex availability check. */
41
+ export interface BwrapProbes {
42
+ /** `which bwrap` outside the cwd (anti-hijack, sandboxing/src/bwrap.rs:168-191). */
43
+ which: () => string | null;
44
+ /** `bwrap --help` text — must advertise `--perms` (launcher.rs:108-124). */
45
+ helpText: (bin: string) => string | null;
46
+ /** Active user-namespace probe with timeout (sandboxing/src/bwrap.rs:74-136). */
47
+ userns: (bin: string) => boolean;
48
+ }
49
+ export type BwrapDetection = {
50
+ ok: true;
51
+ bin: string;
52
+ } | {
53
+ ok: false;
54
+ reason: string;
55
+ };
56
+ /** Honest detection — fail-closed on every probe; NEVER throws (callers WARN + fall back). */
57
+ export declare function detectBwrap(probes?: BwrapProbes): BwrapDetection;
58
+ /** Quantas sondagens reais rodaram. Seam de TESTE — o gate de performance conta isto. */
59
+ export declare function realProbeCount(): number;
60
+ /** Real probes used in production. */
61
+ export declare const realProbes: BwrapProbes;
62
+ /**
63
+ * `detectBwrap` com memoização — o que a produção deve chamar.
64
+ *
65
+ * Note que `detectBwrap` em si **não** memoiza, de propósito: ele aceita probes injetados, e memoizar
66
+ * ali faria um teste com probes falsos envenenar o cache do processo para todos os outros.
67
+ *
68
+ * A revalidação do positivo NÃO é uma re-sondagem: `detectBwrap` gasta três probes (subprocesso
69
+ * `which` + `--help` + namespace de usuário). Aqui só se confirma que o binário validado continua no
70
+ * lugar. Se sumiu, o memo é rebaixado a negativo com o motivo dito — nunca promovido a positivo, que
71
+ * exigiria a sondagem cara de volta.
72
+ */
73
+ export declare function detectBwrapMemoizado(probes?: BwrapProbes): BwrapDetection;
74
+ /** Seam de TESTE — limpa o memo. Produção nunca chama (ver m71-custo-por-turn#ADR-1). */
75
+ export declare function resetBwrapMemo(): void;
@@ -1,10 +1,242 @@
1
1
  'use strict';
2
2
 
3
3
  var child_process = require('child_process');
4
- var promises = require('fs/promises');
4
+ var fs = require('fs');
5
5
  var path = require('path');
6
+ var os = require('os');
7
+ var promises = require('fs/promises');
6
8
 
7
- // src/sandbox/local-sandbox.ts
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var path__default = /*#__PURE__*/_interopDefault(path);
12
+
13
+ // src/sandbox/bwrap.ts
14
+ function buildBwrapArgv(mode, opts) {
15
+ if (mode === "danger-full-access") return null;
16
+ const cwd = path__default.default.resolve(opts.cwd);
17
+ const gitDir = path__default.default.join(cwd, ".git");
18
+ const hasGit = opts.gitDirExists ?? fs.existsSync(gitDir);
19
+ const argv = [
20
+ // core, always (bwrap.rs:318-332; user+pid namespaces explicit so it works as root in containers)
21
+ "--new-session",
22
+ "--die-with-parent",
23
+ "--unshare-user",
24
+ "--unshare-pid",
25
+ // full-read filesystem base (bwrap.rs:446-452)
26
+ "--ro-bind",
27
+ "/",
28
+ "/",
29
+ "--dev",
30
+ "/dev",
31
+ "--proc",
32
+ "/proc"
33
+ ];
34
+ if (!opts.network) argv.push("--unshare-net");
35
+ if (opts.env) argv.push("--clearenv");
36
+ const setenv = {
37
+ ...opts.env ?? {},
38
+ // the flag signals the child that network is unshared (spawn.rs:20,79)
39
+ ...opts.network ? {} : { CODEX_SANDBOX_NETWORK_DISABLED: "1" }
40
+ };
41
+ for (const [k, v] of Object.entries(setenv)) argv.push("--setenv", k, v);
42
+ if (mode === "workspace-write") {
43
+ argv.push("--bind", cwd, cwd, "--bind", "/tmp", "/tmp");
44
+ if (hasGit) argv.push("--ro-bind", gitDir, gitDir);
45
+ }
46
+ argv.push("--chdir", cwd, "--");
47
+ return argv;
48
+ }
49
+ function detectBwrap(probes = realProbes) {
50
+ try {
51
+ const bin = probes.which();
52
+ if (!bin) return { ok: false, reason: "bwrap not found in PATH" };
53
+ const help = probes.helpText(bin);
54
+ if (!help?.includes("--perms")) {
55
+ return { ok: false, reason: `bwrap at ${bin} lacks --perms support (too old)` };
56
+ }
57
+ if (!probes.userns(bin)) {
58
+ return { ok: false, reason: "user namespaces unavailable (container/kernel restriction)" };
59
+ }
60
+ return { ok: true, bin };
61
+ } catch (err) {
62
+ return {
63
+ ok: false,
64
+ reason: `bwrap probe failed: ${err instanceof Error ? err.message : String(err)}`
65
+ };
66
+ }
67
+ }
68
+ var sondagensReais = 0;
69
+ function realProbeCount() {
70
+ return sondagensReais;
71
+ }
72
+ var realProbes = {
73
+ which: () => {
74
+ sondagensReais++;
75
+ try {
76
+ const out = child_process.execFileSync("which", ["bwrap"], { encoding: "utf8", timeout: 2e3 }).trim();
77
+ if (!out || out.startsWith(process.cwd() + path__default.default.sep)) return null;
78
+ return out;
79
+ } catch {
80
+ return null;
81
+ }
82
+ },
83
+ helpText: (bin) => {
84
+ try {
85
+ return child_process.execFileSync(bin, ["--help"], { encoding: "utf8", timeout: 2e3 });
86
+ } catch (err) {
87
+ const e = err;
88
+ return [e.stdout, e.stderr].filter(Boolean).join("\n") || null;
89
+ }
90
+ },
91
+ userns: (bin) => {
92
+ try {
93
+ child_process.execFileSync(bin, ["--unshare-user", "--unshare-net", "--ro-bind", "/", "/", "/bin/true"], {
94
+ timeout: 500,
95
+ stdio: "ignore"
96
+ });
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ };
103
+ var memo;
104
+ function detectBwrapMemoizado(probes = realProbes) {
105
+ memo ??= detectBwrap(probes);
106
+ if (memo.ok && !fs.existsSync(memo.bin)) {
107
+ memo = { ok: false, reason: `bwrap disappeared from ${memo.bin} after detection` };
108
+ }
109
+ return memo;
110
+ }
111
+ function resetBwrapMemo() {
112
+ memo = void 0;
113
+ }
114
+
115
+ // src/internal/security/redact.ts
116
+ var REDACT_ENABLED = readEnvOnce();
117
+ function readEnvOnce() {
118
+ const raw = process.env.THEOKIT_REDACT_SECRETS;
119
+ if (raw === void 0) return true;
120
+ return ["1", "true", "yes", "on"].includes(raw.toLowerCase());
121
+ }
122
+ var warnedOptOut = false;
123
+ if (!REDACT_ENABLED && !warnedOptOut) {
124
+ process.stderr.write(
125
+ "[theokit-sdk] Secret redaction is DISABLED via THEOKIT_REDACT_SECRETS. Credentials may leak into errors, telemetry, logs, transcripts.\n"
126
+ );
127
+ warnedOptOut = true;
128
+ }
129
+ var BUILTIN_PATTERNS = [
130
+ // T5.4: 30+ vendor prefixes (was 12 pre-T5.4). Order matters — more
131
+ // specific prefixes precede generic ones (e.g., sk-ant-admin01 before
132
+ // sk-ant-, sk-proj- before sk-). PEM block deliberately first so its
133
+ // multi-line span runs before any per-line patterns can fire.
134
+ /-----BEGIN[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\s\S]+?-----END[ ]+(?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g,
135
+ // JWT — exact 3-segment base64url. Dotted; the body floor of 4 chars per
136
+ // segment matches the minimum legal payload while skipping `a.b.c` noise.
137
+ /eyJ[A-Za-z0-9_-]{4,}\.eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g,
138
+ // Azure Storage SAS — match the sig= component (URL-encoded base64).
139
+ /(?<=[?&]sig=)[A-Za-z0-9%+/]{20,}/g,
140
+ // Anthropic
141
+ /sk-ant-admin01-[A-Za-z0-9_-]{10,}/g,
142
+ // Anthropic admin keys (must precede sk-ant-)
143
+ /sk-ant-[A-Za-z0-9_-]{10,}/g,
144
+ // Anthropic regular
145
+ // OpenAI family + clones (sk- generic must come AFTER all sk-foo- variants)
146
+ /sk-proj-[A-Za-z0-9_-]{10,}/g,
147
+ // OpenAI project key (must precede sk- generic)
148
+ /sk-[A-Za-z0-9_-]{10,}/g,
149
+ // OpenAI / OpenRouter / DeepInfra / Together / DeepSeek
150
+ // Provider prefixes (alphabetized for maintainability)
151
+ /AIza[A-Za-z0-9_-]{35}/g,
152
+ // Google API key
153
+ /AKIA[A-Z0-9]{16}/g,
154
+ // AWS access key
155
+ /fw_[A-Za-z0-9]{20,}/g,
156
+ // Fireworks
157
+ /glpat-[A-Za-z0-9_-]{20}/g,
158
+ // GitLab PAT
159
+ /ghp_[A-Za-z0-9]{36}/g,
160
+ // GitHub PAT classic
161
+ /github_pat_[A-Za-z0-9_]{82}/g,
162
+ // GitHub PAT fine-grained
163
+ /gsk_[A-Za-z0-9]{20,}/g,
164
+ // Groq
165
+ /hf_[A-Za-z0-9]{20,}/g,
166
+ // HuggingFace
167
+ /\bpa-[A-Za-z0-9_-]{20,}/g,
168
+ // Voyage AI (word-boundary to skip CSS / kebab IDs)
169
+ /pcsk_[A-Za-z0-9_-]{20,}/g,
170
+ // Pinecone
171
+ /pplx-[A-Za-z0-9_-]{20,}/g,
172
+ // Perplexity
173
+ /r8_[A-Za-z0-9_-]{20,}/g,
174
+ // Replicate
175
+ /rk_live_[A-Za-z0-9]{20,}/g,
176
+ // Stripe restricted
177
+ /sk_live_[A-Za-z0-9]{20,}/g,
178
+ // Stripe secret
179
+ /sntrys_[A-Za-z0-9]{40,}/g,
180
+ // Sentry user auth
181
+ /xai-[A-Za-z0-9_-]{20,}/g,
182
+ // xAI (Grok)
183
+ /xox[bpasr]-[A-Za-z0-9-]{10,}/g,
184
+ //Slack tokens
185
+ // Additional unique-prefix tokens with low false-positive risk
186
+ /npm_[A-Za-z0-9]{36}/g,
187
+ // npm access token
188
+ /SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g,
189
+ // SendGrid
190
+ /\bSK[A-Za-z0-9]{32}\b/g,
191
+ // Twilio API SID (word-boundary to skip CSS class noise)
192
+ /\bkey-[a-f0-9]{32}\b/g,
193
+ // Mailgun (hex-only narrows false positives)
194
+ /MT[A-Za-z0-9_-]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}/g,
195
+ // Discord bot
196
+ /\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
197
+ // LaunchDarkly
198
+ ];
199
+ var BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9_\-.+/=]{8,})/g;
200
+ 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;
201
+ var _extraPatterns = [];
202
+ function maskToken(token) {
203
+ if (token.length < 18) return "***";
204
+ return `${token.slice(0, 6)}...${token.slice(-4)}`;
205
+ }
206
+ var MASK_SHAPE = /^.{6}\.\.\..{4}$/s;
207
+ function coerceToString(value) {
208
+ if (typeof value === "string") return value;
209
+ if (value === null || value === void 0) return null;
210
+ if (typeof value === "object") {
211
+ try {
212
+ const s = JSON.stringify(value);
213
+ return s === void 0 ? null : s;
214
+ } catch {
215
+ return "[unredactable: circular]";
216
+ }
217
+ }
218
+ return String(value);
219
+ }
220
+ function redactSecrets(text, opts) {
221
+ const coerced = coerceToString(text);
222
+ if (coerced === null) return "";
223
+ if (!REDACT_ENABLED) return coerced;
224
+ let s = coerced;
225
+ for (const re of BUILTIN_PATTERNS) {
226
+ s = s.replace(re, (m) => maskToken(m));
227
+ }
228
+ for (const re of _extraPatterns) {
229
+ s = s.replace(re, (m) => maskToken(m));
230
+ }
231
+ {
232
+ s = s.replace(BEARER_PATTERN, (_, prefix) => `${prefix}***`);
233
+ s = s.replace(PARAM_PATTERN, (whole, prefix, value) => {
234
+ if (MASK_SHAPE.test(value)) return whole;
235
+ return `${prefix}***`;
236
+ });
237
+ }
238
+ return s;
239
+ }
8
240
 
9
241
  // src/internal/runtime/lifecycle/env-policy.ts
10
242
  var SECRET_PATTERNS = [
@@ -91,15 +323,15 @@ var SandboxBackend = class {
91
323
  env: config.env ?? "inherit-scrubbed"
92
324
  };
93
325
  }
94
- async readFile(path) {
95
- const result = await this.execute(`cat ${this.shellEscape(path)}`);
326
+ async readFile(path2) {
327
+ const result = await this.execute(`cat ${this.shellEscape(path2)}`);
96
328
  if (result.exitCode !== 0) {
97
329
  throw new Error(`readFile failed: ${result.stderr}`);
98
330
  }
99
331
  return result.stdout;
100
332
  }
101
- async writeFile(path, content) {
102
- await this.uploadFile(path, content);
333
+ async writeFile(path2, content) {
334
+ await this.uploadFile(path2, content);
103
335
  }
104
336
  async glob(pattern, cwd) {
105
337
  const dir = cwd ?? this.config.workDir ?? ".";
@@ -109,16 +341,16 @@ var SandboxBackend = class {
109
341
  if (result.exitCode !== 0) return [];
110
342
  return result.stdout.trim().split("\n").filter(Boolean);
111
343
  }
112
- async grep(pattern, path) {
113
- const target = path ?? ".";
344
+ async grep(pattern, path2) {
345
+ const target = path2 ?? ".";
114
346
  const result = await this.execute(
115
347
  `grep -rn ${this.shellEscape(pattern)} ${this.shellEscape(target)} 2>/dev/null`
116
348
  );
117
349
  if (result.exitCode !== 0) return [];
118
350
  return result.stdout.trim().split("\n").filter(Boolean);
119
351
  }
120
- async listDir(path) {
121
- const result = await this.execute(`ls -1 ${this.shellEscape(path)}`);
352
+ async listDir(path2) {
353
+ const result = await this.execute(`ls -1 ${this.shellEscape(path2)}`);
122
354
  if (result.exitCode !== 0) return [];
123
355
  return result.stdout.trim().split("\n").filter(Boolean);
124
356
  }
@@ -176,13 +408,227 @@ var LocalSandbox = class extends SandboxBackend {
176
408
  timedOut
177
409
  };
178
410
  }
179
- async uploadFile(path$1, content) {
180
- const fullPath = path$1.startsWith("/") ? path$1 : `${this.config.workDir}/${path$1}`;
411
+ async uploadFile(path2, content) {
412
+ const fullPath = path2.startsWith("/") ? path2 : `${this.config.workDir}/${path2}`;
181
413
  await promises.mkdir(path.dirname(fullPath), { recursive: true });
182
414
  await promises.writeFile(fullPath, content, "utf-8");
183
415
  }
184
416
  };
185
417
 
418
+ // src/sandbox/seccomp.ts
419
+ var BPF_LD = 0;
420
+ var BPF_W = 0;
421
+ var BPF_ABS = 32;
422
+ var BPF_JMP = 5;
423
+ var BPF_JEQ = 16;
424
+ var BPF_JGE = 48;
425
+ var BPF_K = 0;
426
+ var BPF_RET = 6;
427
+ var LD_ABS_W = BPF_LD | BPF_W | BPF_ABS;
428
+ var JEQ_K = BPF_JMP | BPF_JEQ | BPF_K;
429
+ var JGE_K = BPF_JMP | BPF_JGE | BPF_K;
430
+ var RET_K = BPF_RET | BPF_K;
431
+ var OFF_NR = 0;
432
+ var OFF_ARCH = 4;
433
+ var OFF_ARG0 = 16;
434
+ var AUDIT_ARCH_X86_64 = 3221225534;
435
+ var X32_BIT = 1073741824;
436
+ var SECCOMP_RET_ALLOW = 2147418112;
437
+ var SECCOMP_RET_ERRNO = 327680;
438
+ var EPERM = 1;
439
+ var RET_EPERM = SECCOMP_RET_ERRNO | EPERM & 65535;
440
+ var SECCOMP_RET_KILL_PROCESS = 2147483648;
441
+ var AF_UNIX = 1;
442
+ var ALWAYS_DENIED = [101, 310, 311, 425, 426, 427];
443
+ var NETWORK_DENIED = [42, 43, 288, 49, 50, 52, 51, 48, 44, 307, 299, 55, 54];
444
+ var SOCKET_SYSCALLS = [41, 53];
445
+ var stmt = (code, k) => ({ code, jt: 0, jf: 0, k });
446
+ var jmp = (code, k, jt, jf) => ({ code, jt, jf, k });
447
+ function buildSeccompFilter(opts) {
448
+ const insns = [];
449
+ const ALLOW = /* @__PURE__ */ Symbol("ALLOW");
450
+ const DENY = /* @__PURE__ */ Symbol("DENY");
451
+ const KILL = /* @__PURE__ */ Symbol("KILL");
452
+ const body = [];
453
+ const push = (code, k, jt = 0, jf = 0) => {
454
+ body.push({ code, k, jt, jf });
455
+ };
456
+ push(LD_ABS_W, OFF_ARCH);
457
+ push(JEQ_K, AUDIT_ARCH_X86_64, 0, KILL);
458
+ push(LD_ABS_W, OFF_NR);
459
+ push(JGE_K, X32_BIT, KILL, 0);
460
+ const denied = opts.networkRestricted ? [...ALWAYS_DENIED, ...NETWORK_DENIED] : [...ALWAYS_DENIED];
461
+ for (const nr of denied) push(JEQ_K, nr, DENY, 0);
462
+ if (opts.networkRestricted) {
463
+ for (const sysno of SOCKET_SYSCALLS) {
464
+ push(JEQ_K, sysno, 0, 2);
465
+ push(LD_ABS_W, OFF_ARG0);
466
+ push(JEQ_K, AF_UNIX, ALLOW, DENY);
467
+ }
468
+ }
469
+ push(RET_K, SECCOMP_RET_ALLOW);
470
+ const allowIdx = body.length - 1;
471
+ push(RET_K, RET_EPERM);
472
+ const denyIdx = body.length - 1;
473
+ push(RET_K, SECCOMP_RET_KILL_PROCESS);
474
+ const killIdx = body.length - 1;
475
+ const resolve = (i, t) => {
476
+ const abs = t === ALLOW ? allowIdx : t === DENY ? denyIdx : t === KILL ? killIdx : i + 1 + t;
477
+ const off = abs - (i + 1);
478
+ if (off < 0 || off > 255) throw new RangeError(`seccomp jump out of range at ${i}: ${off}`);
479
+ return off;
480
+ };
481
+ for (const [i, b] of body.entries()) {
482
+ if (b.code === JEQ_K || b.code === JGE_K) {
483
+ insns.push(jmp(b.code, b.k, resolve(i, b.jt), resolve(i, b.jf)));
484
+ } else {
485
+ insns.push(stmt(b.code, b.k));
486
+ }
487
+ }
488
+ const buf = Buffer.alloc(insns.length * 8);
489
+ insns.forEach((ins, i) => {
490
+ buf.writeUInt16LE(ins.code, i * 8);
491
+ buf.writeUInt8(ins.jt, i * 8 + 2);
492
+ buf.writeUInt8(ins.jf, i * 8 + 3);
493
+ buf.writeUInt32LE(ins.k >>> 0, i * 8 + 4);
494
+ });
495
+ return buf;
496
+ }
497
+
498
+ // src/sandbox/linux-sandbox.ts
499
+ function shellQuote(s) {
500
+ return `'${s.replaceAll("'", `'\\''`)}'`;
501
+ }
502
+ function wrapCommandForSandbox(mode, opts, command) {
503
+ const argv = buildBwrapArgv(mode, { cwd: opts.cwd, network: opts.network, env: opts.env });
504
+ if (argv === null) return null;
505
+ const bin = opts.bin ?? "bwrap";
506
+ const seccompArgv = opts.seccompPath !== void 0 ? ["--seccomp", "3"] : [];
507
+ const base = `${shellQuote(bin)} ${[...argv.slice(0, -1), ...seccompArgv, "--"].map(shellQuote).join(" ")} /bin/sh -c ${shellQuote(command)}`;
508
+ return opts.seccompPath !== void 0 ? `${base} 3< ${shellQuote(opts.seccompPath)}` : base;
509
+ }
510
+ var ENV_ALLOWLIST = [
511
+ "PATH",
512
+ "HOME",
513
+ "LANG",
514
+ "LC_ALL",
515
+ "LC_CTYPE",
516
+ "TERM",
517
+ "USER",
518
+ "TMPDIR",
519
+ "SHELL"
520
+ ];
521
+ function allowlistedEnv(source = process.env) {
522
+ const out = {};
523
+ for (const k of ENV_ALLOWLIST) {
524
+ const v = source[k];
525
+ if (v !== void 0) out[k] = v;
526
+ }
527
+ return out;
528
+ }
529
+ var LinuxSandbox = class extends LocalSandbox {
530
+ mode;
531
+ network;
532
+ cwd;
533
+ bin;
534
+ env;
535
+ /** M63 — path to the cBPF seccomp program written host-side; passed to `bwrap --seccomp 3` via a
536
+ * shell redirect. `undefined` when the network is unrestricted OR generation failed (honest fallback). */
537
+ seccompPath;
538
+ constructor(config, opts) {
539
+ super(config);
540
+ this.mode = opts.mode;
541
+ this.network = opts.network ?? false;
542
+ this.cwd = config.workDir ?? process.cwd();
543
+ this.bin = opts.bin ?? "bwrap";
544
+ this.env = opts.env ?? allowlistedEnv();
545
+ this.seccompPath = this.network ? void 0 : restrictedSeccompPath();
546
+ }
547
+ /** Extracted for test visibility — delegates to the pure `wrapCommandForSandbox` (M57, single wrap SoT). */
548
+ wrapCommand(command) {
549
+ return wrapCommandForSandbox(
550
+ this.mode,
551
+ {
552
+ cwd: this.cwd,
553
+ network: this.network,
554
+ env: this.env,
555
+ bin: this.bin,
556
+ seccompPath: this.seccompPath
557
+ },
558
+ command
559
+ );
560
+ }
561
+ execute(command, opts) {
562
+ const wrapped = this.wrapCommand(command);
563
+ if (wrapped === null) return super.execute(command, opts);
564
+ return super.execute(wrapped, opts);
565
+ }
566
+ };
567
+ var warnedNonX64 = false;
568
+ function seccompPathForArch(arch, warn) {
569
+ if (arch !== "x64") {
570
+ if (!warnedNonX64) {
571
+ warnedNonX64 = true;
572
+ warn(
573
+ `[sandbox] seccomp syscall filter unsupported on ${arch} (x86_64 only in v1) \u2014 running without the filter; bwrap FS/network confinement still applies.`
574
+ );
575
+ }
576
+ return void 0;
577
+ }
578
+ try {
579
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ab-seccomp-"));
580
+ const path2 = path.join(dir, "filter.bpf");
581
+ fs.writeFileSync(path2, buildSeccompFilter({ networkRestricted: true }));
582
+ const cleanup = () => fs.rmSync(dir, { recursive: true, force: true });
583
+ process.once("exit", cleanup);
584
+ process.once("SIGINT", cleanup);
585
+ process.once("SIGTERM", cleanup);
586
+ return path2;
587
+ } catch (err) {
588
+ warn(
589
+ `[sandbox] seccomp filter unavailable (${err instanceof Error ? err.message : String(err)}) \u2014 running without syscall filter (bwrap FS/network confinement still applies).`
590
+ );
591
+ return void 0;
592
+ }
593
+ }
594
+ var seccompFilterPath;
595
+ function restrictedSeccompPath() {
596
+ if (seccompFilterPath !== void 0) return seccompFilterPath ?? void 0;
597
+ const path2 = seccompPathForArch(process.arch, (m) => console.warn(redactSecrets(m)));
598
+ seccompFilterPath = path2 ?? null;
599
+ return path2;
600
+ }
601
+ var warnedUnavailable = false;
602
+ function resetSandboxWarnLatch() {
603
+ warnedUnavailable = false;
604
+ }
605
+ function resolveSandboxPosture(opts) {
606
+ if (opts.mode === "danger-full-access") {
607
+ return { mode: opts.mode, enforced: false, detail: "no confinement (danger-full-access)" };
608
+ }
609
+ const detection = (opts.detect ?? detectBwrapMemoizado)();
610
+ if (!detection.ok) {
611
+ return { mode: opts.mode, enforced: false, detail: `tool-gating only \u2014 ${detection.reason}` };
612
+ }
613
+ return { mode: opts.mode, enforced: true, detail: "kernel (bwrap)" };
614
+ }
615
+ function createSandboxBackend(opts) {
616
+ const config = { workDir: opts.workDir, timeoutMs: opts.timeoutMs };
617
+ if (opts.mode === "danger-full-access") return new LocalSandbox(config);
618
+ const detection = (opts.detect ?? detectBwrapMemoizado)();
619
+ if (!detection.ok) {
620
+ if (!warnedUnavailable) {
621
+ warnedUnavailable = true;
622
+ const warn = opts.warn ?? ((m) => console.warn(redactSecrets(m)));
623
+ warn(
624
+ `[sandbox] OS-level enforcement unavailable (${detection.reason}) \u2014 falling back to tool-level gating only (sandbox_mode=${opts.mode}).`
625
+ );
626
+ }
627
+ return new LocalSandbox(config);
628
+ }
629
+ return new LinuxSandbox(config, { mode: opts.mode, network: opts.network, bin: detection.bin });
630
+ }
631
+
186
632
  // src/errors.ts
187
633
  var TheokitAgentError = class extends Error {
188
634
  name = "TheokitAgentError";
@@ -248,12 +694,27 @@ async function provisionRepo(sandboxOrOpts, maybeOpts) {
248
694
  return { repoDir };
249
695
  }
250
696
 
697
+ exports.LinuxSandbox = LinuxSandbox;
251
698
  exports.LocalSandbox = LocalSandbox;
252
699
  exports.RepoProvisionError = RepoProvisionError;
253
700
  exports.SandboxBackend = SandboxBackend;
254
701
  exports.SandboxNotAvailableError = SandboxNotAvailableError;
255
702
  exports.SandboxSecurityError = SandboxSecurityError;
703
+ exports.allowlistedEnv = allowlistedEnv;
704
+ exports.buildBwrapArgv = buildBwrapArgv;
705
+ exports.buildSeccompFilter = buildSeccompFilter;
706
+ exports.createSandboxBackend = createSandboxBackend;
707
+ exports.detectBwrap = detectBwrap;
708
+ exports.detectBwrapMemoizado = detectBwrapMemoizado;
256
709
  exports.provisionRepo = provisionRepo;
710
+ exports.realProbeCount = realProbeCount;
711
+ exports.realProbes = realProbes;
712
+ exports.resetBwrapMemo = resetBwrapMemo;
713
+ exports.resetSandboxWarnLatch = resetSandboxWarnLatch;
257
714
  exports.resolveSandbox = resolveSandbox;
715
+ exports.resolveSandboxPosture = resolveSandboxPosture;
716
+ exports.restrictedSeccompPath = restrictedSeccompPath;
717
+ exports.seccompPathForArch = seccompPathForArch;
718
+ exports.wrapCommandForSandbox = wrapCommandForSandbox;
258
719
  //# sourceMappingURL=index.cjs.map
259
720
  //# sourceMappingURL=index.cjs.map