@node9/proxy 1.67.0 → 1.67.2
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/cli.js +1718 -1328
- package/dist/cli.mjs +1685 -1295
- package/dist/dashboard.mjs +188 -128
- package/dist/index.js +618 -233
- package/dist/index.mjs +618 -233
- package/package.json +1 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -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
|
|
296
|
-
|
|
297
|
-
...
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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,
|
|
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([
|
|
@@ -1269,6 +1316,13 @@ var init_dist = __esm({
|
|
|
1269
1316
|
)
|
|
1270
1317
|
}
|
|
1271
1318
|
];
|
|
1319
|
+
BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1320
|
+
"bash",
|
|
1321
|
+
"execute_bash",
|
|
1322
|
+
"run_shell_command",
|
|
1323
|
+
"shell",
|
|
1324
|
+
"exec_command"
|
|
1325
|
+
]);
|
|
1272
1326
|
AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
1273
1327
|
"block-rm-rf-home",
|
|
1274
1328
|
"shield:project-jail:block-read-ssh",
|
|
@@ -1292,10 +1346,6 @@ var init_dist = __esm({
|
|
|
1292
1346
|
deriveRedirOp("cat <<X\nX"),
|
|
1293
1347
|
deriveRedirOp("cat <<-X\nX")
|
|
1294
1348
|
]);
|
|
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
1349
|
aws_default = {
|
|
1300
1350
|
name: "aws",
|
|
1301
1351
|
description: "Protects AWS infrastructure from destructive AI operations",
|
|
@@ -2461,6 +2511,7 @@ var init_managed = __esm({
|
|
|
2461
2511
|
var init_build = __esm({
|
|
2462
2512
|
"src/shields/build.ts"() {
|
|
2463
2513
|
"use strict";
|
|
2514
|
+
init_dist();
|
|
2464
2515
|
}
|
|
2465
2516
|
});
|
|
2466
2517
|
|
|
@@ -2493,8 +2544,14 @@ var init_config = __esm({
|
|
|
2493
2544
|
settings: {
|
|
2494
2545
|
mode: "standard",
|
|
2495
2546
|
autoStartDaemon: true,
|
|
2496
|
-
|
|
2497
|
-
//
|
|
2547
|
+
// OFF by default. The snapshot store is a per-project bare git repo with
|
|
2548
|
+
// no size ceiling, and eviction drops the index row without deleting the
|
|
2549
|
+
// objects — on one machine it reached 378G (352G of it orphaned tmp_pack_*
|
|
2550
|
+
// from interrupted `git gc`) and filled the disk. A security tool must not
|
|
2551
|
+
// be what fills a customer's disk. Re-enable per install with
|
|
2552
|
+
// `{"settings":{"enableUndo":true}}`; the default flips back when the
|
|
2553
|
+
// bounded copy-store lands (doc/undo-v2-copy-store-design.md).
|
|
2554
|
+
enableUndo: false,
|
|
2498
2555
|
enableHookLogDebug: true,
|
|
2499
2556
|
approvalTimeoutMs: 12e4,
|
|
2500
2557
|
// 120-second auto-deny timeout
|
|
@@ -2693,7 +2750,8 @@ var init_config = __esm({
|
|
|
2693
2750
|
skillPinning: { enabled: false, mode: "warn", roots: [] },
|
|
2694
2751
|
trustedHosts: [],
|
|
2695
2752
|
trustedHostsManaged: false,
|
|
2696
|
-
appPermissions: {}
|
|
2753
|
+
appPermissions: {},
|
|
2754
|
+
managedJailPaths: []
|
|
2697
2755
|
},
|
|
2698
2756
|
environments: {}
|
|
2699
2757
|
};
|
|
@@ -3034,6 +3092,7 @@ var init_scan_watermark = __esm({
|
|
|
3034
3092
|
"src/daemon/scan-watermark.ts"() {
|
|
3035
3093
|
"use strict";
|
|
3036
3094
|
init_dlp();
|
|
3095
|
+
init_config();
|
|
3037
3096
|
init_dist();
|
|
3038
3097
|
MAX_LINE_BYTES = 2 * 1024 * 1024;
|
|
3039
3098
|
}
|
|
@@ -3813,6 +3872,7 @@ var init_policy = __esm({
|
|
|
3813
3872
|
init_trusted_hosts();
|
|
3814
3873
|
init_dist();
|
|
3815
3874
|
init_dist();
|
|
3875
|
+
init_dist();
|
|
3816
3876
|
}
|
|
3817
3877
|
});
|
|
3818
3878
|
|
|
@@ -4187,7 +4247,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
4187
4247
|
}
|
|
4188
4248
|
}
|
|
4189
4249
|
let astFsMatched = false;
|
|
4190
|
-
const astRanForBash = toolNameLower
|
|
4250
|
+
const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
|
|
4191
4251
|
if (astRanForBash) {
|
|
4192
4252
|
astFsMatched = pushFsOpAstFinding(
|
|
4193
4253
|
String(input.command ?? ""),
|
|
@@ -4205,7 +4265,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
4205
4265
|
for (const source of ruleSources) {
|
|
4206
4266
|
const { rule } = source;
|
|
4207
4267
|
if (rule.verdict === "allow") continue;
|
|
4208
|
-
if (
|
|
4268
|
+
if (!toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
|
|
4209
4269
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
4210
4270
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
4211
4271
|
const inputPreview = preview(input, 120);
|
|
@@ -4487,7 +4547,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
4487
4547
|
}
|
|
4488
4548
|
}
|
|
4489
4549
|
let astFsMatched = false;
|
|
4490
|
-
const astRanForBash = toolNameLower
|
|
4550
|
+
const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
|
|
4491
4551
|
if (astRanForBash) {
|
|
4492
4552
|
astFsMatched = pushFsOpAstFinding(
|
|
4493
4553
|
String(input.command ?? ""),
|
|
@@ -4505,7 +4565,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
4505
4565
|
for (const source of ruleSources) {
|
|
4506
4566
|
const { rule } = source;
|
|
4507
4567
|
if (rule.verdict === "allow") continue;
|
|
4508
|
-
if (
|
|
4568
|
+
if (!toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
|
|
4509
4569
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
4510
4570
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
4511
4571
|
const inputPreview = preview(input, 120);
|
|
@@ -4727,7 +4787,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4727
4787
|
}
|
|
4728
4788
|
}
|
|
4729
4789
|
let astFsMatched = false;
|
|
4730
|
-
const astRanForBash = toolNameLower
|
|
4790
|
+
const astRanForBash = isShellShapedTool(toolNameLower, toolInspectionMap);
|
|
4731
4791
|
if (astRanForBash) {
|
|
4732
4792
|
astFsMatched = pushFsOpAstFinding(
|
|
4733
4793
|
String(input["command"] ?? ""),
|
|
@@ -4745,8 +4805,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4745
4805
|
for (const source of ruleSources) {
|
|
4746
4806
|
const { rule } = source;
|
|
4747
4807
|
if (rule.verdict === "allow") continue;
|
|
4748
|
-
if (rule.tool && !
|
|
4749
|
-
continue;
|
|
4808
|
+
if (rule.tool && !toolMatchesRule(toolNameLower, rule.tool, toolInspectionMap)) continue;
|
|
4750
4809
|
if (astRanForBash && rule.name && AST_FS_REGEX_RULES.has(rule.name)) continue;
|
|
4751
4810
|
if (!evaluateSmartConditions(input, rule)) continue;
|
|
4752
4811
|
const inputPreview = preview(input, 120);
|
|
@@ -4807,7 +4866,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4807
4866
|
}
|
|
4808
4867
|
return result;
|
|
4809
4868
|
}
|
|
4810
|
-
var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
|
|
4869
|
+
var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
|
|
4811
4870
|
var init_scan = __esm({
|
|
4812
4871
|
"src/cli/commands/scan.ts"() {
|
|
4813
4872
|
"use strict";
|
|
@@ -4828,6 +4887,7 @@ var init_scan = __esm({
|
|
|
4828
4887
|
init_protection();
|
|
4829
4888
|
init_scan_json();
|
|
4830
4889
|
init_scan_history();
|
|
4890
|
+
toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
|
|
4831
4891
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4832
4892
|
".ts",
|
|
4833
4893
|
".tsx",
|