@node9/proxy 1.67.1 → 1.67.3

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.
@@ -184,6 +184,125 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
184
184
  }
185
185
  return null;
186
186
  }
187
+ function validateRegex(pattern) {
188
+ if (!pattern) return "Pattern is required";
189
+ if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
190
+ try {
191
+ new RegExp(pattern);
192
+ } catch (e) {
193
+ return `Invalid regex syntax: ${e.message}`;
194
+ }
195
+ if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
196
+ if (!safeRegex2(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
197
+ return null;
198
+ }
199
+ function getCompiledRegex(pattern, flags = "") {
200
+ if (flags && !/^[gimsuy]+$/.test(flags)) return null;
201
+ const key = `${pattern}\0${flags}`;
202
+ if (regexCache.has(key)) {
203
+ const cached = regexCache.get(key);
204
+ regexCache.delete(key);
205
+ regexCache.set(key, cached);
206
+ return cached;
207
+ }
208
+ if (validateRegex(pattern) !== null) return null;
209
+ try {
210
+ const re = new RegExp(pattern, flags);
211
+ if (regexCache.size >= REGEX_CACHE_MAX) {
212
+ const oldest = regexCache.keys().next().value;
213
+ if (oldest) regexCache.delete(oldest);
214
+ }
215
+ regexCache.set(key, re);
216
+ return re;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function matchesPattern(text, patterns) {
222
+ const p = Array.isArray(patterns) ? patterns : [patterns];
223
+ if (p.length === 0) return false;
224
+ const isMatch = pm(p, { nocase: true, dot: true });
225
+ const target = text.toLowerCase();
226
+ const directMatch = isMatch(target);
227
+ if (directMatch) return true;
228
+ const withoutDotSlash = text.replace(/^\.\//, "");
229
+ return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
230
+ }
231
+ function getNestedValue(obj, path11) {
232
+ if (!obj || typeof obj !== "object") return null;
233
+ const segments = path11.split(".");
234
+ for (const seg of segments) {
235
+ if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
236
+ }
237
+ return segments.reduce((prev, curr) => prev?.[curr], obj);
238
+ }
239
+ function evaluateSmartConditions(args, rule) {
240
+ if (!rule.conditions || rule.conditions.length === 0) return true;
241
+ const mode = rule.conditionMode ?? "all";
242
+ const fieldCache = /* @__PURE__ */ new Map();
243
+ const resolveField = (field) => {
244
+ if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
245
+ const rawVal = getNestedValue(args, field);
246
+ const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
247
+ const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
248
+ const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
249
+ fieldCache.set(field, val);
250
+ return val;
251
+ };
252
+ const readingsCache = /* @__PURE__ */ new Map();
253
+ const resolveFieldReadings = (field) => {
254
+ const cached = readingsCache.get(field);
255
+ if (cached) return cached;
256
+ const primary = resolveField(field);
257
+ if (primary === null) {
258
+ readingsCache.set(field, []);
259
+ return [];
260
+ }
261
+ let out = [primary];
262
+ if (field === "command") {
263
+ const raw = getNestedValue(args, field);
264
+ if (typeof raw === "string") {
265
+ const collapsed = commandReadings(raw).map((r) => r.replace(/\s+/g, " ").trim());
266
+ out = [.../* @__PURE__ */ new Set([primary, ...collapsed])];
267
+ }
268
+ }
269
+ readingsCache.set(field, out);
270
+ return out;
271
+ };
272
+ const results = rule.conditions.map((cond) => {
273
+ const val = resolveField(cond.field);
274
+ switch (cond.op) {
275
+ case "exists":
276
+ return val !== null && val !== "";
277
+ case "notExists":
278
+ return val === null || val === "";
279
+ case "contains":
280
+ return val !== null && cond.value ? val.includes(cond.value) : false;
281
+ case "notContains":
282
+ return val !== null && cond.value ? !val.includes(cond.value) : true;
283
+ case "matches": {
284
+ if (val === null || !cond.value) return false;
285
+ const reM = getCompiledRegex(cond.value, cond.flags ?? "");
286
+ if (!reM) return false;
287
+ return resolveFieldReadings(cond.field).some((v) => reM.test(v));
288
+ }
289
+ case "notMatches": {
290
+ if (!cond.value) return false;
291
+ if (val === null) return true;
292
+ const reN = getCompiledRegex(cond.value, cond.flags ?? "");
293
+ if (!reN) return false;
294
+ return !resolveFieldReadings(cond.field).some((v) => reN.test(v));
295
+ }
296
+ case "matchesGlob":
297
+ return val !== null && cond.value ? pm.isMatch(val, cond.value) : false;
298
+ case "notMatchesGlob":
299
+ return val !== null && cond.value ? !pm.isMatch(val, cond.value) : false;
300
+ default:
301
+ return false;
302
+ }
303
+ });
304
+ return mode === "any" ? results.some((r) => r) : results.every((r) => r);
305
+ }
187
306
  function isCatHeredocOrLit(part) {
188
307
  if (!part) return false;
189
308
  const t = syntax.NodeType(part);
@@ -237,14 +356,22 @@ function cachedNormalize(command, compute) {
237
356
  return result;
238
357
  }
239
358
  function normalizeCommandForPolicy(command) {
359
+ return commandReadingsImpl(command).posix;
360
+ }
361
+ function commandReadings(command) {
362
+ const r = commandReadingsImpl(command);
363
+ return r.separator === r.posix ? [r.posix] : [r.posix, r.separator];
364
+ }
365
+ function commandReadingsImpl(command) {
240
366
  return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
241
367
  }
242
368
  function normalizeCommandForPolicyImpl(command) {
243
369
  const f = parseShared(command);
244
- if (f === PARSE_FAIL) return command;
370
+ if (f === PARSE_FAIL) return { posix: command, separator: command };
245
371
  try {
246
372
  const strips = [];
247
373
  const rewrites = [];
374
+ const quoteOnlyRewrites = [];
248
375
  const msgSpans = /* @__PURE__ */ new Set();
249
376
  syntax.Walk(f, (node) => {
250
377
  if (!node) return false;
@@ -289,22 +416,23 @@ function normalizeCommandForPolicyImpl(command) {
289
416
  if (resolved === source) continue;
290
417
  if (resolved === "" || /\s/.test(resolved)) continue;
291
418
  rewrites.push([s, e, resolved]);
419
+ const quoteOnly = source.replace(/['"]/g, "");
420
+ if (quoteOnly !== source) quoteOnlyRewrites.push([s, e, quoteOnly]);
292
421
  }
293
422
  return true;
294
423
  });
295
- const edits = [
296
- ...strips.map(([s, e]) => [s, e, '""']),
297
- ...rewrites
298
- ];
299
- if (edits.length === 0) return command;
300
- edits.sort((a, b) => b[0] - a[0]);
301
- let result = command;
302
- for (const [s, e, rep] of edits) {
303
- result = result.slice(0, s) + rep + result.slice(e);
304
- }
305
- return result;
424
+ const stripEdits = strips.map(([s, e]) => [s, e, '""']);
425
+ const apply = (extra) => {
426
+ const edits = [...stripEdits, ...extra];
427
+ if (edits.length === 0) return command;
428
+ edits.sort((a, b) => b[0] - a[0]);
429
+ let out = command;
430
+ for (const [s, e, rep] of edits) out = out.slice(0, s) + rep + out.slice(e);
431
+ return out;
432
+ };
433
+ return { posix: apply(rewrites), separator: apply(quoteOnlyRewrites) };
306
434
  } catch {
307
- return command;
435
+ return { posix: command, separator: command };
308
436
  }
309
437
  }
310
438
  function scanArgsForDynamicExec(args, startIdx) {
@@ -359,6 +487,20 @@ function detectDangerousShellExec(command) {
359
487
  return null;
360
488
  }
361
489
  }
490
+ function isBashTool(toolName) {
491
+ return BASH_TOOL_NAMES.has(toolName.toLowerCase());
492
+ }
493
+ function isShellShapedTool(toolName, toolInspection) {
494
+ if (isBashTool(toolName)) return true;
495
+ if (!toolInspection) return false;
496
+ const pattern = Object.keys(toolInspection).find((p) => matchesPattern(toolName, p));
497
+ return pattern !== void 0 && toolInspection[pattern] === "command";
498
+ }
499
+ function toolMatchesRule(toolName, ruleTool, toolInspection) {
500
+ if (!ruleTool) return true;
501
+ if (matchesPattern(toolName, ruleTool)) return true;
502
+ return isShellShapedTool(toolName, toolInspection) && matchesPattern("bash", ruleTool);
503
+ }
362
504
  function isProtectedHomePath(rawPath) {
363
505
  let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
364
506
  let underHome = false;
@@ -517,105 +659,6 @@ function analyzeFsOperationImpl(command) {
517
659
  return null;
518
660
  }
519
661
  }
520
- function validateRegex(pattern) {
521
- if (!pattern) return "Pattern is required";
522
- if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
523
- try {
524
- new RegExp(pattern);
525
- } catch (e) {
526
- return `Invalid regex syntax: ${e.message}`;
527
- }
528
- if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
529
- if (!safeRegex2(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
530
- return null;
531
- }
532
- function getCompiledRegex(pattern, flags = "") {
533
- if (flags && !/^[gimsuy]+$/.test(flags)) return null;
534
- const key = `${pattern}\0${flags}`;
535
- if (regexCache.has(key)) {
536
- const cached = regexCache.get(key);
537
- regexCache.delete(key);
538
- regexCache.set(key, cached);
539
- return cached;
540
- }
541
- if (validateRegex(pattern) !== null) return null;
542
- try {
543
- const re = new RegExp(pattern, flags);
544
- if (regexCache.size >= REGEX_CACHE_MAX) {
545
- const oldest = regexCache.keys().next().value;
546
- if (oldest) regexCache.delete(oldest);
547
- }
548
- regexCache.set(key, re);
549
- return re;
550
- } catch {
551
- return null;
552
- }
553
- }
554
- function matchesPattern(text, patterns) {
555
- const p = Array.isArray(patterns) ? patterns : [patterns];
556
- if (p.length === 0) return false;
557
- const isMatch = pm(p, { nocase: true, dot: true });
558
- const target = text.toLowerCase();
559
- const directMatch = isMatch(target);
560
- if (directMatch) return true;
561
- const withoutDotSlash = text.replace(/^\.\//, "");
562
- return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
563
- }
564
- function getNestedValue(obj, path11) {
565
- if (!obj || typeof obj !== "object") return null;
566
- const segments = path11.split(".");
567
- for (const seg of segments) {
568
- if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
569
- }
570
- return segments.reduce((prev, curr) => prev?.[curr], obj);
571
- }
572
- function evaluateSmartConditions(args, rule) {
573
- if (!rule.conditions || rule.conditions.length === 0) return true;
574
- const mode = rule.conditionMode ?? "all";
575
- const fieldCache = /* @__PURE__ */ new Map();
576
- const resolveField = (field) => {
577
- if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
578
- const rawVal = getNestedValue(args, field);
579
- const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
580
- const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
581
- const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
582
- fieldCache.set(field, val);
583
- return val;
584
- };
585
- const results = rule.conditions.map((cond) => {
586
- const val = resolveField(cond.field);
587
- switch (cond.op) {
588
- case "exists":
589
- return val !== null && val !== "";
590
- case "notExists":
591
- return val === null || val === "";
592
- case "contains":
593
- return val !== null && cond.value ? val.includes(cond.value) : false;
594
- case "notContains":
595
- return val !== null && cond.value ? !val.includes(cond.value) : true;
596
- case "matches": {
597
- if (val === null || !cond.value) return false;
598
- const reM = getCompiledRegex(cond.value, cond.flags ?? "");
599
- if (!reM) return false;
600
- return reM.test(val);
601
- }
602
- case "notMatches": {
603
- if (!cond.value) return false;
604
- if (val === null) return true;
605
- const reN = getCompiledRegex(cond.value, cond.flags ?? "");
606
- if (!reN) return false;
607
- return !reN.test(val);
608
- }
609
- case "matchesGlob":
610
- return val !== null && cond.value ? pm.isMatch(val, cond.value) : false;
611
- case "notMatchesGlob":
612
- return val !== null && cond.value ? !pm.isMatch(val, cond.value) : false;
613
- default:
614
- return false;
615
- }
616
- });
617
- return mode === "any" ? results.some((r) => r) : results.every((r) => r);
618
- }
619
662
  function isShieldVerdict(v) {
620
663
  return v === "allow" || v === "review" || v === "block";
621
664
  }
@@ -669,7 +712,7 @@ function assertBuiltinShieldRegexesAreSafe() {
669
712
  }
670
713
  }
671
714
  }
672
- var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, COST_PER_LOOP_ITER_USD, LONG_OUTPUT_THRESHOLD_BYTES;
715
+ var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, COST_PER_LOOP_ITER_USD, LONG_OUTPUT_THRESHOLD_BYTES;
673
716
  var init_dist = __esm({
674
717
  "packages/policy-engine/dist/index.mjs"() {
675
718
  "use strict";
@@ -1159,6 +1202,10 @@ var init_dist = __esm({
1159
1202
  MAX_DEPTH = 5;
1160
1203
  MAX_STRING_BYTES = 1e5;
1161
1204
  MAX_JSON_PARSE_BYTES = 1e4;
1205
+ MAX_REGEX_LENGTH = 256;
1206
+ REGEX_CACHE_MAX = 500;
1207
+ regexCache = /* @__PURE__ */ new Map();
1208
+ FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1162
1209
  ({ syntax } = mvdanSh);
1163
1210
  sharedParser = syntax.NewParser();
1164
1211
  MESSAGE_FLAGS = /* @__PURE__ */ new Set([
@@ -1192,9 +1239,34 @@ var init_dist = __esm({
1192
1239
  "vi",
1193
1240
  "emacs",
1194
1241
  "code",
1195
- "type"
1242
+ "type",
1243
+ // — the 22 that were missing —
1244
+ "grep",
1245
+ "egrep",
1246
+ "fgrep",
1247
+ "rg",
1248
+ "ag",
1249
+ "ack",
1250
+ "awk",
1251
+ "gawk",
1252
+ "sed",
1253
+ "cut",
1254
+ "tr",
1255
+ "jq",
1256
+ "yq",
1257
+ "od",
1258
+ "xxd",
1259
+ "hexdump",
1260
+ "strings",
1261
+ "sort",
1262
+ "uniq",
1263
+ "tac",
1264
+ "nl",
1265
+ "dd"
1196
1266
  ]);
1197
- FS_OP_PRESCREEN_RE = /(?:^|[\s|;&(`\n])(?:rm|cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\b/;
1267
+ FS_OP_PRESCREEN_RE = new RegExp(
1268
+ `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
1269
+ );
1198
1270
  HOME_CACHE_ALLOWLIST = [
1199
1271
  ".cache",
1200
1272
  ".npm/_npx",
@@ -1233,9 +1305,37 @@ var init_dist = __esm({
1233
1305
  // for the canonical test-asserted contract.
1234
1306
  rule: "shield:project-jail:block-read-env",
1235
1307
  reason: "Reading .env files is blocked by project-jail shield",
1236
- match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
1237
- p
1238
- )
1308
+ // Structural, not a list. The previous form enumerated seven suffixes and
1309
+ // anchored on `$`, so `.env.prod`, `.env.ci` and `.env.local.bak` — all
1310
+ // gitignored, all routinely holding real secrets — were never covered. A
1311
+ // hand-written list of what to protect is only ever as complete as the day
1312
+ // it was typed; this says "`.env` plus any suffix chain" and then names the
1313
+ // exceptions, which is the direction that fails safe.
1314
+ //
1315
+ // \.env the segment itself
1316
+ // (?![\w-]) a boundary, so `.environment` and `.envrc` are NOT .env
1317
+ // files. Without it a flat suffix class swallows both.
1318
+ // (?![\w-]) a boundary, so `.environment` and `.envrc` are NOT .env
1319
+ // [\w.-]*$ any suffix chain. Flat class, no nested quantifier —
1320
+ // `(\.[\w-]+)*` reads the same but is rejected by
1321
+ // safe-regex2, and this pattern runs on the hook hot path.
1322
+ //
1323
+ // The two exclusions are NOT the same shape, because the words do not mean
1324
+ // the same thing:
1325
+ //
1326
+ // (?!\.(?:example|sample|template)\b) — "this file is a fixture", and it
1327
+ // stays a fixture whatever follows, so `.env.example.md` is allowed too.
1328
+ // These are checked into git by convention: already public, so blocking
1329
+ // them buys nothing and costs the most common legitimate agent read.
1330
+ //
1331
+ // (?!\.test$) — anchored, because `test` names an ENVIRONMENT, not a
1332
+ // fixture. `.env.test` is the committed template and stays allowed, but
1333
+ // `.env.test.local` is gitignored by the `.env*.local` convention and
1334
+ // holds real values, so it must block. Using `\b` here — the obvious
1335
+ // symmetry — silently exempts every `.env.test.*` file.
1336
+ //
1337
+ // shields.test.ts:983-995 is the canonical contract; keep both in step.
1338
+ match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$/i.test(p)
1239
1339
  },
1240
1340
  {
1241
1341
  // verdict: 'review' (not 'block') is a deliberate design choice
@@ -1269,6 +1369,13 @@ var init_dist = __esm({
1269
1369
  )
1270
1370
  }
1271
1371
  ];
1372
+ BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
1373
+ "bash",
1374
+ "execute_bash",
1375
+ "run_shell_command",
1376
+ "shell",
1377
+ "exec_command"
1378
+ ]);
1272
1379
  AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
1273
1380
  "block-rm-rf-home",
1274
1381
  "shield:project-jail:block-read-ssh",
@@ -1292,10 +1399,6 @@ var init_dist = __esm({
1292
1399
  deriveRedirOp("cat <<X\nX"),
1293
1400
  deriveRedirOp("cat <<-X\nX")
1294
1401
  ]);
1295
- MAX_REGEX_LENGTH = 100;
1296
- REGEX_CACHE_MAX = 500;
1297
- regexCache = /* @__PURE__ */ new Map();
1298
- FORBIDDEN_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1299
1402
  aws_default = {
1300
1403
  name: "aws",
1301
1404
  description: "Protects AWS infrastructure from destructive AI operations",
@@ -2461,6 +2564,7 @@ var init_managed = __esm({
2461
2564
  var init_build = __esm({
2462
2565
  "src/shields/build.ts"() {
2463
2566
  "use strict";
2567
+ init_dist();
2464
2568
  }
2465
2569
  });
2466
2570
 
@@ -2493,8 +2597,14 @@ var init_config = __esm({
2493
2597
  settings: {
2494
2598
  mode: "standard",
2495
2599
  autoStartDaemon: true,
2496
- enableUndo: true,
2497
- // 🔥 ALWAYS TRUE BY DEFAULT for the safety net
2600
+ // OFF by default. The snapshot store is a per-project bare git repo with
2601
+ // no size ceiling, and eviction drops the index row without deleting the
2602
+ // objects — on one machine it reached 378G (352G of it orphaned tmp_pack_*
2603
+ // from interrupted `git gc`) and filled the disk. A security tool must not
2604
+ // be what fills a customer's disk. Re-enable per install with
2605
+ // `{"settings":{"enableUndo":true}}`; the default flips back when the
2606
+ // bounded copy-store lands (doc/undo-v2-copy-store-design.md).
2607
+ enableUndo: false,
2498
2608
  enableHookLogDebug: true,
2499
2609
  approvalTimeoutMs: 12e4,
2500
2610
  // 120-second auto-deny timeout
@@ -2693,7 +2803,8 @@ var init_config = __esm({
2693
2803
  skillPinning: { enabled: false, mode: "warn", roots: [] },
2694
2804
  trustedHosts: [],
2695
2805
  trustedHostsManaged: false,
2696
- appPermissions: {}
2806
+ appPermissions: {},
2807
+ managedJailPaths: []
2697
2808
  },
2698
2809
  environments: {}
2699
2810
  };
@@ -3034,6 +3145,7 @@ var init_scan_watermark = __esm({
3034
3145
  "src/daemon/scan-watermark.ts"() {
3035
3146
  "use strict";
3036
3147
  init_dlp();
3148
+ init_config();
3037
3149
  init_dist();
3038
3150
  MAX_LINE_BYTES = 2 * 1024 * 1024;
3039
3151
  }
@@ -3813,6 +3925,7 @@ var init_policy = __esm({
3813
3925
  init_trusted_hosts();
3814
3926
  init_dist();
3815
3927
  init_dist();
3928
+ init_dist();
3816
3929
  }
3817
3930
  });
3818
3931
 
@@ -4187,7 +4300,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
4187
4300
  }
4188
4301
  }
4189
4302
  let astFsMatched = false;
4190
- const astRanForBash = toolNameLower === "bash" || toolNameLower === "execute_bash";
4303
+ const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
4191
4304
  if (astRanForBash) {
4192
4305
  astFsMatched = pushFsOpAstFinding(
4193
4306
  String(input.command ?? ""),
@@ -4205,7 +4318,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
4205
4318
  for (const source of ruleSources) {
4206
4319
  const { rule } = source;
4207
4320
  if (rule.verdict === "allow") continue;
4208
- if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
4321
+ if (!toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
4209
4322
  if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
4210
4323
  if (!evaluateSmartConditions(input, rule)) continue;
4211
4324
  const inputPreview = preview(input, 120);
@@ -4487,7 +4600,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
4487
4600
  }
4488
4601
  }
4489
4602
  let astFsMatched = false;
4490
- const astRanForBash = toolNameLower === "run_shell_command" || toolNameLower === "shell";
4603
+ const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
4491
4604
  if (astRanForBash) {
4492
4605
  astFsMatched = pushFsOpAstFinding(
4493
4606
  String(input.command ?? ""),
@@ -4505,7 +4618,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
4505
4618
  for (const source of ruleSources) {
4506
4619
  const { rule } = source;
4507
4620
  if (rule.verdict === "allow") continue;
4508
- if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
4621
+ if (!toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
4509
4622
  if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
4510
4623
  if (!evaluateSmartConditions(input, rule)) continue;
4511
4624
  const inputPreview = preview(input, 120);
@@ -4727,7 +4840,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4727
4840
  }
4728
4841
  }
4729
4842
  let astFsMatched = false;
4730
- const astRanForBash = toolNameLower === "exec_command" || toolNameLower === "bash";
4843
+ const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
4731
4844
  if (astRanForBash) {
4732
4845
  astFsMatched = pushFsOpAstFinding(
4733
4846
  String(input["command"] ?? ""),
@@ -4745,8 +4858,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4745
4858
  for (const source of ruleSources) {
4746
4859
  const { rule } = source;
4747
4860
  if (rule.verdict === "allow") continue;
4748
- if (rule.tool && !matchesPattern(toolNameLower === "exec_command" ? "bash" : toolNameLower, rule.tool))
4749
- continue;
4861
+ if (rule.tool && !toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
4750
4862
  if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
4751
4863
  if (!evaluateSmartConditions(input, rule)) continue;
4752
4864
  const inputPreview = preview(input, 120);
@@ -4807,7 +4919,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4807
4919
  }
4808
4920
  return result;
4809
4921
  }
4810
- var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
4922
+ var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
4811
4923
  var init_scan = __esm({
4812
4924
  "src/cli/commands/scan.ts"() {
4813
4925
  "use strict";
@@ -4828,6 +4940,7 @@ var init_scan = __esm({
4828
4940
  init_protection();
4829
4941
  init_scan_json();
4830
4942
  init_scan_history();
4943
+ toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
4831
4944
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
4832
4945
  ".ts",
4833
4946
  ".tsx",