@node9/proxy 2.33.1 → 2.34.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/cli.js +1277 -249
- package/dist/cli.mjs +1285 -257
- package/dist/dashboard.mjs +230 -13
- package/dist/index.js +1087 -110
- package/dist/index.mjs +1087 -110
- package/dist/scan-ink.mjs +134 -11
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1210,6 +1210,120 @@ function expandPlainParam(p) {
|
|
|
1210
1210
|
if (recorded !== void 0) return recorded;
|
|
1211
1211
|
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
1212
1212
|
}
|
|
1213
|
+
function functionBodies(f) {
|
|
1214
|
+
const out = /* @__PURE__ */ new Map();
|
|
1215
|
+
syntax.Walk(f, (node) => {
|
|
1216
|
+
const n = node;
|
|
1217
|
+
if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1218
|
+
const list = out.get(n.Name.Value) ?? [];
|
|
1219
|
+
list.push(n.Body);
|
|
1220
|
+
out.set(n.Name.Value, list);
|
|
1221
|
+
}
|
|
1222
|
+
return true;
|
|
1223
|
+
});
|
|
1224
|
+
return out;
|
|
1225
|
+
}
|
|
1226
|
+
function rebindsPositionals(body) {
|
|
1227
|
+
let found = false;
|
|
1228
|
+
syntax.Walk(body, (node) => {
|
|
1229
|
+
const n = node;
|
|
1230
|
+
if (found || !n) return false;
|
|
1231
|
+
if (syntax.NodeType(n) === "CallExpr") {
|
|
1232
|
+
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1233
|
+
if (words[0] === "shift") found = true;
|
|
1234
|
+
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
1235
|
+
found = true;
|
|
1236
|
+
}
|
|
1237
|
+
return !found;
|
|
1238
|
+
});
|
|
1239
|
+
return found;
|
|
1240
|
+
}
|
|
1241
|
+
function tableBindings() {
|
|
1242
|
+
if (!assignmentTable) return "";
|
|
1243
|
+
const out = [];
|
|
1244
|
+
for (const [name, rec] of assignmentTable)
|
|
1245
|
+
if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
|
|
1246
|
+
return out.sort().join("");
|
|
1247
|
+
}
|
|
1248
|
+
function walkWithCalls(f, visit2) {
|
|
1249
|
+
const bodies = functionBodies(f);
|
|
1250
|
+
if (bodies.size === 0) {
|
|
1251
|
+
syntax.Walk(f, visit2);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
1255
|
+
const inline = (call) => {
|
|
1256
|
+
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1257
|
+
const list = words[0] ? bodies.get(words[0]) : void 0;
|
|
1258
|
+
if (!list || callDepth >= MAX_CALL_DEPTH) return;
|
|
1259
|
+
const args = words.slice(1);
|
|
1260
|
+
const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
|
|
1261
|
+
if (seenCalls.has(key)) return;
|
|
1262
|
+
seenCalls.add(key);
|
|
1263
|
+
const outerTable = assignmentTable;
|
|
1264
|
+
const outerOffset = currentStmtOffset;
|
|
1265
|
+
for (const body of list) {
|
|
1266
|
+
if (callWalks >= MAX_CALL_WALKS) return;
|
|
1267
|
+
callWalks++;
|
|
1268
|
+
const table = /* @__PURE__ */ new Map();
|
|
1269
|
+
for (const [name, rec] of outerTable ?? [])
|
|
1270
|
+
if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
|
|
1271
|
+
for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
|
|
1272
|
+
table.delete(name);
|
|
1273
|
+
if (!rebindsPositionals(body)) {
|
|
1274
|
+
args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
|
|
1275
|
+
if (args.length === 1) {
|
|
1276
|
+
table.set("@", { value: args[0], at: -1 });
|
|
1277
|
+
table.set("*", { value: args[0], at: -1 });
|
|
1278
|
+
}
|
|
1279
|
+
table.set("#", { value: String(args.length), at: -1 });
|
|
1280
|
+
}
|
|
1281
|
+
assignmentTable = table;
|
|
1282
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1283
|
+
callDepth++;
|
|
1284
|
+
try {
|
|
1285
|
+
if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
|
|
1286
|
+
syntax.Walk(body, step);
|
|
1287
|
+
} finally {
|
|
1288
|
+
callDepth--;
|
|
1289
|
+
assignmentTable = outerTable;
|
|
1290
|
+
currentStmtOffset = outerOffset;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
const step = (node) => {
|
|
1295
|
+
if (!node) return false;
|
|
1296
|
+
const go = visit2(node);
|
|
1297
|
+
if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
|
|
1298
|
+
return go;
|
|
1299
|
+
};
|
|
1300
|
+
syntax.Walk(f, step);
|
|
1301
|
+
}
|
|
1302
|
+
function withAssignments(f, body) {
|
|
1303
|
+
const outerTable = assignmentTable;
|
|
1304
|
+
const outerOffset = currentStmtOffset;
|
|
1305
|
+
const outerWalks = callWalks;
|
|
1306
|
+
callWalks = 0;
|
|
1307
|
+
assignmentTable = new Map(
|
|
1308
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
1309
|
+
);
|
|
1310
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1311
|
+
try {
|
|
1312
|
+
recordTopLevelAssignments(f);
|
|
1313
|
+
return body();
|
|
1314
|
+
} finally {
|
|
1315
|
+
assignmentTable = outerTable;
|
|
1316
|
+
currentStmtOffset = outerOffset;
|
|
1317
|
+
callWalks = outerWalks;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function trackStatement(n) {
|
|
1321
|
+
try {
|
|
1322
|
+
currentStmtOffset = n.Pos().Offset();
|
|
1323
|
+
} catch {
|
|
1324
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1213
1327
|
function resolveWordLiteral(w) {
|
|
1214
1328
|
const parts = w?.Parts || [];
|
|
1215
1329
|
let s = "";
|
|
@@ -1330,27 +1444,31 @@ function extractShellDestinations(command) {
|
|
|
1330
1444
|
const out = [];
|
|
1331
1445
|
const seen = /* @__PURE__ */ new Set();
|
|
1332
1446
|
try {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1447
|
+
withAssignments(
|
|
1448
|
+
f,
|
|
1449
|
+
() => walkWithCalls(f, (node) => {
|
|
1450
|
+
if (!node) return false;
|
|
1451
|
+
const n = node;
|
|
1452
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1453
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1454
|
+
const callArgs = n.Args || [];
|
|
1455
|
+
if (callArgs.length === 0) return true;
|
|
1456
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1457
|
+
const at = unwrapCommandHead(words);
|
|
1458
|
+
const name = (words[at] || "").toLowerCase().split("/").pop() ?? "";
|
|
1459
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1460
|
+
const rest = words.slice(at + 1);
|
|
1461
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1462
|
+
const host2 = parseDestHost(raw);
|
|
1463
|
+
if (!host2) continue;
|
|
1464
|
+
const key = `${name}:${host2}`;
|
|
1465
|
+
if (seen.has(key)) continue;
|
|
1466
|
+
seen.add(key);
|
|
1467
|
+
out.push({ host: host2, binary: name, raw });
|
|
1468
|
+
}
|
|
1469
|
+
return true;
|
|
1470
|
+
})
|
|
1471
|
+
);
|
|
1354
1472
|
} catch {
|
|
1355
1473
|
return out;
|
|
1356
1474
|
}
|
|
@@ -1362,39 +1480,45 @@ function extractShellDestTokens(command) {
|
|
|
1362
1480
|
const out = [];
|
|
1363
1481
|
const seen = /* @__PURE__ */ new Set();
|
|
1364
1482
|
try {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
tok = tok.
|
|
1483
|
+
withAssignments(
|
|
1484
|
+
f,
|
|
1485
|
+
() => walkWithCalls(f, (node) => {
|
|
1486
|
+
if (!node) return false;
|
|
1487
|
+
const n = node;
|
|
1488
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1489
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1490
|
+
const callArgs = n.Args || [];
|
|
1491
|
+
if (callArgs.length === 0) return true;
|
|
1492
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1493
|
+
const head = unwrapCommandHead(words);
|
|
1494
|
+
const name = (words[head] || "").toLowerCase().split("/").pop() ?? "";
|
|
1495
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1496
|
+
const rest = words.slice(head + 1);
|
|
1497
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1498
|
+
if (!raw) continue;
|
|
1499
|
+
let tok = raw.trim();
|
|
1500
|
+
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(tok);
|
|
1501
|
+
const hasScheme = scheme !== null;
|
|
1502
|
+
if (hasScheme) tok = tok.slice(scheme[0].length);
|
|
1503
|
+
tok = tok.split(/[/?#]/)[0];
|
|
1504
|
+
const at = tok.lastIndexOf("@");
|
|
1505
|
+
if (at >= 0) tok = tok.slice(at + 1);
|
|
1506
|
+
if (tok.startsWith("[")) {
|
|
1507
|
+
const close = tok.indexOf("]");
|
|
1508
|
+
if (close > 0) tok = tok.slice(0, close + 1);
|
|
1509
|
+
} else {
|
|
1510
|
+
tok = tok.split(":")[0];
|
|
1511
|
+
}
|
|
1512
|
+
if (!tok) continue;
|
|
1513
|
+
if (!hasScheme && /^\d+$/.test(tok) && Number(tok) < 16777216) continue;
|
|
1514
|
+
const key = `${name}:${tok}`;
|
|
1515
|
+
if (seen.has(key)) continue;
|
|
1516
|
+
seen.add(key);
|
|
1517
|
+
out.push({ token: tok, binary: name });
|
|
1388
1518
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if (seen.has(key)) continue;
|
|
1393
|
-
seen.add(key);
|
|
1394
|
-
out.push({ token: tok, binary: name });
|
|
1395
|
-
}
|
|
1396
|
-
return true;
|
|
1397
|
-
});
|
|
1519
|
+
return true;
|
|
1520
|
+
})
|
|
1521
|
+
);
|
|
1398
1522
|
} catch {
|
|
1399
1523
|
return out;
|
|
1400
1524
|
}
|
|
@@ -1513,6 +1637,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1513
1637
|
if (depth === 0) {
|
|
1514
1638
|
seenPayloads = /* @__PURE__ */ new Set();
|
|
1515
1639
|
payloadBudget = PAYLOAD_BUDGET;
|
|
1640
|
+
if (callDepth === 0) callWalks = 0;
|
|
1516
1641
|
}
|
|
1517
1642
|
assignmentTable = new Map(
|
|
1518
1643
|
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
@@ -1520,7 +1645,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1520
1645
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1521
1646
|
try {
|
|
1522
1647
|
recordTopLevelAssignments(f);
|
|
1523
|
-
|
|
1648
|
+
walkWithCalls(f, (node) => {
|
|
1524
1649
|
if (!node || result?.verdict === "block") return false;
|
|
1525
1650
|
const n = node;
|
|
1526
1651
|
const nodeType = syntax.NodeType(n);
|
|
@@ -2309,6 +2434,19 @@ function runnablePrefix(command) {
|
|
|
2309
2434
|
}
|
|
2310
2435
|
return null;
|
|
2311
2436
|
}
|
|
2437
|
+
function uploadedFile(tool, flag, value) {
|
|
2438
|
+
const v = value.value;
|
|
2439
|
+
const file2 = (f) => f && f !== "-" && f !== "." ? { value: f } : null;
|
|
2440
|
+
if (tool === "curl") {
|
|
2441
|
+
if (["-d", "--data", "--data-ascii", "--data-binary", "--json"].includes(flag))
|
|
2442
|
+
return v.startsWith("@") ? file2(v.slice(1)) : null;
|
|
2443
|
+
if (flag === "--data-urlencode") return file2(/^[^=@]*@(.+)$/.exec(v)?.[1]);
|
|
2444
|
+
if (flag === "-F" || flag === "--form") return file2(/^[^=]*=[@<]([^;]+)/.exec(v)?.[1]);
|
|
2445
|
+
if (flag === "-T" || flag === "--upload-file") return file2(v);
|
|
2446
|
+
}
|
|
2447
|
+
if (tool === "wget" && ["--post-file", "--body-file"].includes(flag)) return file2(v);
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2312
2450
|
function merge(a, b) {
|
|
2313
2451
|
a.cwd = unique([...a.cwd, ...b.cwd]);
|
|
2314
2452
|
for (const key of /* @__PURE__ */ new Set([...a.vars.keys(), ...b.vars.keys()])) {
|
|
@@ -2382,6 +2520,9 @@ function wordValues(word, state) {
|
|
|
2382
2520
|
function collectShellFileAccesses(command, context = {}) {
|
|
2383
2521
|
const out = [];
|
|
2384
2522
|
let payloads = 256;
|
|
2523
|
+
const functions = /* @__PURE__ */ new Map();
|
|
2524
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
2525
|
+
let callWalks2 = MAX_CALL_WALKS2;
|
|
2385
2526
|
const initial = {
|
|
2386
2527
|
cwd: [context.cwd ?? ""],
|
|
2387
2528
|
vars: /* @__PURE__ */ new Map([["HOME", [context.homeDirs?.[0] ?? "~"]]])
|
|
@@ -2639,8 +2780,98 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2639
2780
|
}
|
|
2640
2781
|
return;
|
|
2641
2782
|
}
|
|
2783
|
+
if (name === "wget" || name === "curl" || name === "aria2c") {
|
|
2784
|
+
downloadOutputs(name, args, state);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
if (["http", "https", "xh", "xhs"].includes(name)) {
|
|
2788
|
+
for (const token of args) {
|
|
2789
|
+
if (!token || token.dynamic || /^[a-z][a-z0-9+.-]*:\/\//i.test(token.value)) continue;
|
|
2790
|
+
const m = /^(?:@|[^=:@\s]+@|[^=:@\s]*=@)(.+)$/.exec(token.value);
|
|
2791
|
+
if (m && m[1] !== "-") add("read", { value: m[1] }, state, { upload: true });
|
|
2792
|
+
}
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
if (name === "scp" || name === "rsync") {
|
|
2796
|
+
const operands2 = strings.filter((s) => s && !s.startsWith("-"));
|
|
2797
|
+
const remote = operands2.slice(1).some((o) => /^[^/\s]+:/.test(o));
|
|
2798
|
+
for (const p of copySourcePaths(words))
|
|
2799
|
+
add("read", locate(p), state, remote ? { upload: true } : {});
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2642
2802
|
for (const p of copySourcePaths(words)) add("read", locate(p), state);
|
|
2643
2803
|
}
|
|
2804
|
+
function downloadOutputs(name, args, state) {
|
|
2805
|
+
const valueShort = DOWNLOAD_SHORT_VALUE[name];
|
|
2806
|
+
const outputs = [];
|
|
2807
|
+
const urls = [];
|
|
2808
|
+
let dir2 = null;
|
|
2809
|
+
let remoteName = false;
|
|
2810
|
+
let document = false;
|
|
2811
|
+
let serverNamed = false;
|
|
2812
|
+
const take = (flag, value) => {
|
|
2813
|
+
if (!value) return;
|
|
2814
|
+
const sent = uploadedFile(name, flag, value);
|
|
2815
|
+
if (sent) add("read", sent, state, { upload: true });
|
|
2816
|
+
if (DOWNLOAD_OUTPUT_FLAGS[name].includes(flag)) {
|
|
2817
|
+
if (["-O", "--output-document", "--out"].includes(flag) || name === "aria2c" && flag === "-o")
|
|
2818
|
+
document = true;
|
|
2819
|
+
if (value.value !== "-" || value.pattern || value.dynamic) outputs.push(value);
|
|
2820
|
+
} else if (DOWNLOAD_DIR_FLAGS[name].includes(flag)) dir2 = value;
|
|
2821
|
+
else if (name === "wget" && (flag === "-i" || flag === "--input-file")) serverNamed = true;
|
|
2822
|
+
};
|
|
2823
|
+
for (let i = 0; i < args.length; i++) {
|
|
2824
|
+
const token = args[i];
|
|
2825
|
+
if (!token) continue;
|
|
2826
|
+
const v = token.value;
|
|
2827
|
+
if (v.startsWith("--")) {
|
|
2828
|
+
const eq = v.indexOf("=");
|
|
2829
|
+
const flag = eq < 0 ? v : v.slice(0, eq);
|
|
2830
|
+
if (["--remote-name", "--remote-name-all"].includes(flag)) remoteName = true;
|
|
2831
|
+
if (["--remote-header-name", "--recursive", "--mirror", "--page-requisites"].includes(flag))
|
|
2832
|
+
serverNamed = true;
|
|
2833
|
+
if (DOWNLOAD_LONG_VALUE[name].includes(flag))
|
|
2834
|
+
take(flag, eq < 0 ? args[++i] ?? null : { ...token, value: v.slice(eq + 1) });
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
if (v.startsWith("-") && v.length > 1 && !token.dynamic) {
|
|
2838
|
+
for (let j = 1; j < v.length; j++) {
|
|
2839
|
+
const c = v[j];
|
|
2840
|
+
if (name === "wget" && c === "n") {
|
|
2841
|
+
j++;
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
if (name === "curl" && c === "O") remoteName = true;
|
|
2845
|
+
if (name === "curl" && c === "J") serverNamed = true;
|
|
2846
|
+
if (name === "wget" && (c === "r" || c === "m" || c === "p")) serverNamed = true;
|
|
2847
|
+
if (valueShort.includes(c)) {
|
|
2848
|
+
const rest = v.slice(j + 1);
|
|
2849
|
+
take(`-${c}`, rest ? { value: rest } : args[++i] ?? null);
|
|
2850
|
+
break;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
if (/^(?:https?|ftps?):\/\//i.test(v)) urls.push(v);
|
|
2856
|
+
}
|
|
2857
|
+
for (const out2 of outputs) {
|
|
2858
|
+
const spelled = name === "aria2c" && dir2?.value && !out2.value.startsWith("/") ? { ...out2, value: `${dir2.value.replace(/\/+$/, "")}/${out2.value}` } : out2;
|
|
2859
|
+
if (name === "curl" && /#\d/.test(spelled.value))
|
|
2860
|
+
add("write", { value: "", pattern: spelled.value.replace(/#\d+/g, "*"), dot: true }, state);
|
|
2861
|
+
else add("write", spelled, state);
|
|
2862
|
+
}
|
|
2863
|
+
const remote = name === "curl" ? remoteName || serverNamed : !document || serverNamed;
|
|
2864
|
+
if (!remote) return;
|
|
2865
|
+
const into = dir2?.value ? dir2.value.replace(/\/+$/, "") : ".";
|
|
2866
|
+
if (serverNamed || urls.length === 0) {
|
|
2867
|
+
add("write", { value: "", pattern: `${quoteGlob(into)}/*`, dot: true }, state);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
for (const url of urls) {
|
|
2871
|
+
const file2 = url.replace(/[?#].*$/, "").replace(/^[a-z]+:\/\/[^/]*/i, "").split("/").pop();
|
|
2872
|
+
add("write", { value: `${into}/${file2 || "index.html"}` }, state);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2644
2875
|
function findActions2(tokens, words, head, state, nested, depth) {
|
|
2645
2876
|
const starts = findStartPoints(words, head).starts;
|
|
2646
2877
|
const roots = (starts.length ? starts : ["."]).slice(0, MAX_ALTERNATIVES);
|
|
@@ -2712,7 +2943,35 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2712
2943
|
const words = wordValues(node, state);
|
|
2713
2944
|
alternatives = alternatives.flatMap((prefix) => (words.length ? words : [null]).map((w) => [...prefix, w])).slice(0, MAX_ALTERNATIVES);
|
|
2714
2945
|
}
|
|
2715
|
-
for (const tokens of alternatives)
|
|
2946
|
+
for (const tokens of alternatives) {
|
|
2947
|
+
fromWords(tokens, state, nested, depth);
|
|
2948
|
+
inlineCall(tokens, state, depth);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
function inlineCall(tokens, state, depth) {
|
|
2952
|
+
const head = tokens[0];
|
|
2953
|
+
if (!head || head.dynamic || head.pattern) return;
|
|
2954
|
+
const bodies = functions.get(head.value);
|
|
2955
|
+
if (!bodies || (state.callDepth ?? 0) >= MAX_CALL_DEPTH2) return;
|
|
2956
|
+
const args = tokens.slice(1).map((a) => a && !a.dynamic ? a.value : null);
|
|
2957
|
+
const vars = JSON.stringify([...state.vars].sort(([a], [b]) => a < b ? -1 : 1));
|
|
2958
|
+
const key = `${head.value}\0${JSON.stringify(args)}\0${state.cwd.join("")}\0${vars}`;
|
|
2959
|
+
if (seenCalls.has(key)) return;
|
|
2960
|
+
seenCalls.add(key);
|
|
2961
|
+
for (const body of bodies) {
|
|
2962
|
+
if (callWalks2-- <= 0) return;
|
|
2963
|
+
const local = clone(state);
|
|
2964
|
+
local.callDepth = (state.callDepth ?? 0) + 1;
|
|
2965
|
+
for (const name of POSITIONAL) local.vars.delete(name);
|
|
2966
|
+
if (!rebindsPositionals(body)) {
|
|
2967
|
+
args.slice(0, 9).forEach((v, i) => local.vars.set(String(i + 1), v === null ? null : [v]));
|
|
2968
|
+
const all = args.every((v) => v !== null) && args.length ? unique(args) : null;
|
|
2969
|
+
local.vars.set("@", all);
|
|
2970
|
+
local.vars.set("*", all);
|
|
2971
|
+
local.vars.set("#", [String(args.length)]);
|
|
2972
|
+
}
|
|
2973
|
+
statement(body, local, depth + 1);
|
|
2974
|
+
}
|
|
2716
2975
|
}
|
|
2717
2976
|
function readsScriptFromStdin(stmt) {
|
|
2718
2977
|
const cmd = stmt?.Cmd;
|
|
@@ -2749,11 +3008,13 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2749
3008
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
2750
3009
|
state.onSuccess = void 0;
|
|
2751
3010
|
if (stmt.Background) state = clone(state);
|
|
3011
|
+
const headWord = stmt.Cmd && syntax2.NodeType(stmt.Cmd) === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
3012
|
+
const sends = SENDING_STDIN.has(headWord);
|
|
2752
3013
|
for (const redir of stmt.Redirs ?? []) {
|
|
2753
3014
|
substitutions(redir.Word, state, depth);
|
|
2754
3015
|
for (const word of wordValues(redir.Word, state)) {
|
|
2755
3016
|
if (WRITES.has(redir.Op)) add("write", word, state);
|
|
2756
|
-
if (READS.has(redir.Op)) add("read", word, state);
|
|
3017
|
+
if (READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
|
|
2757
3018
|
if (redir.Op === DUP_OUT && word.value && !/^\d+$/.test(word.value))
|
|
2758
3019
|
add("write", word, state);
|
|
2759
3020
|
}
|
|
@@ -2862,7 +3123,10 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2862
3123
|
}
|
|
2863
3124
|
const parsed = parseShared(command);
|
|
2864
3125
|
const program2 = typeof parsed !== "symbol" ? parsed : runnablePrefix(command);
|
|
2865
|
-
if (program2)
|
|
3126
|
+
if (program2) {
|
|
3127
|
+
for (const [name, bodies] of functionBodies(program2)) functions.set(name, bodies);
|
|
3128
|
+
statements(program2.Stmts ?? [], initial, 0);
|
|
3129
|
+
}
|
|
2866
3130
|
return out;
|
|
2867
3131
|
}
|
|
2868
3132
|
function isAgentInstructionPath(absPath, root) {
|
|
@@ -2978,6 +3242,33 @@ function hits(access, paths, directories, context) {
|
|
|
2978
3242
|
}
|
|
2979
3243
|
return false;
|
|
2980
3244
|
}
|
|
3245
|
+
function hitsSystemDir(access, context, dirs = SYSTEM_DIRS) {
|
|
3246
|
+
const home = context.homeDirs?.[0] ?? "~";
|
|
3247
|
+
const candidates2 = access.candidates ?? [
|
|
3248
|
+
normalizeFilePath(access.path, access.cwd ?? context.cwd, home)
|
|
3249
|
+
];
|
|
3250
|
+
for (const p of candidates2) {
|
|
3251
|
+
if (dirs.some((d) => within(p, d)) && !exempt(p)) return true;
|
|
3252
|
+
if (access.recursive && dirs.some((d) => within(d, p))) return true;
|
|
3253
|
+
}
|
|
3254
|
+
if (!access.glob) return false;
|
|
3255
|
+
const pattern = normalizeFilePath(access.glob, access.cwd ?? context.cwd, home);
|
|
3256
|
+
const magic = pattern.search(/[*?[{]/);
|
|
3257
|
+
const literal = magic < 0 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", magic) + 1);
|
|
3258
|
+
if (literal && exempt(literal.replace(/\/$/, "") || "/")) return false;
|
|
3259
|
+
const options = { dot: access.dotGlob === true, nonegate: true };
|
|
3260
|
+
const components = pattern.split("/");
|
|
3261
|
+
for (const root of dirs) {
|
|
3262
|
+
const count = root.split("/").length;
|
|
3263
|
+
if (components.length > count && picomatch(components.slice(0, count).join("/"), options)(root))
|
|
3264
|
+
return true;
|
|
3265
|
+
if (access.recursive && picomatch(pattern, options)(root)) return true;
|
|
3266
|
+
}
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
function uploadDirs(context) {
|
|
3270
|
+
return context.homeDirs?.[0] === "/root" ? UPLOAD_SYSTEM_DIRS.filter((d) => d !== "/root") : UPLOAD_SYSTEM_DIRS;
|
|
3271
|
+
}
|
|
2981
3272
|
function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
2982
3273
|
const homes = [.../* @__PURE__ */ new Set([...context.homeDirs ?? ["~"], "/root"])];
|
|
2983
3274
|
const startup = homes.flatMap(
|
|
@@ -2999,6 +3290,14 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2999
3290
|
"files.shell-startup",
|
|
3000
3291
|
"review-shell-startup-mutation",
|
|
3001
3292
|
"Changing shell startup files requires approval"
|
|
3293
|
+
] : mutation && hitsSystemDir(access, context) ? [
|
|
3294
|
+
"files.system-dirs",
|
|
3295
|
+
"review-system-dir-mutation",
|
|
3296
|
+
"Changing files in a system directory requires approval"
|
|
3297
|
+
] : !mutation && access.upload && hitsSystemDir(access, context, uploadDirs(context)) ? [
|
|
3298
|
+
"files.system-dirs",
|
|
3299
|
+
"review-system-file-upload",
|
|
3300
|
+
"Uploading a system file requires approval"
|
|
3002
3301
|
] : !mutation && hits(access, PASSWORD_HASH_FILES, [], context) ? [
|
|
3003
3302
|
"files.password-hashes",
|
|
3004
3303
|
"review-password-hash-read",
|
|
@@ -3326,16 +3625,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
|
|
|
3326
3625
|
if (EMAIL_VALUE_RE.test(value)) return false;
|
|
3327
3626
|
if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
|
|
3328
3627
|
if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
|
|
3628
|
+
if (CONCATENATION_RE.test(value)) return false;
|
|
3329
3629
|
if (rejectedByStructuredValidator(value)) return false;
|
|
3330
3630
|
return shannonEntropy(value) >= minEntropy;
|
|
3331
3631
|
}
|
|
3332
3632
|
function nameHoldsCredential(name) {
|
|
3333
3633
|
return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
|
|
3334
3634
|
}
|
|
3635
|
+
function isOtpDocSecret(value) {
|
|
3636
|
+
return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
|
|
3637
|
+
}
|
|
3335
3638
|
function validateQueryCredential(raw) {
|
|
3336
3639
|
const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
|
|
3337
3640
|
if (value === null) return false;
|
|
3338
|
-
if (OTP_SECRET_RE.test(value))
|
|
3641
|
+
if (OTP_SECRET_RE.test(value))
|
|
3642
|
+
return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
|
|
3339
3643
|
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
|
|
3340
3644
|
return looksGenerated(value, 12, 3);
|
|
3341
3645
|
}
|
|
@@ -3349,13 +3653,40 @@ function keywordEndsAPart(name) {
|
|
|
3349
3653
|
}
|
|
3350
3654
|
function validateAssignedSecret(raw, before = "") {
|
|
3351
3655
|
const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
|
|
3352
|
-
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
|
|
3656
|
+
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
|
|
3353
3657
|
if (value === null || name === null) return false;
|
|
3354
|
-
if (!keywordEndsAPart(name)
|
|
3355
|
-
|
|
3658
|
+
if (!keywordEndsAPart(name)) return false;
|
|
3659
|
+
if (ARN_CONTEXT_RE.test(before)) return false;
|
|
3660
|
+
const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
|
|
3356
3661
|
if (!nameHoldsCredential(prefix + name)) return false;
|
|
3357
3662
|
return looksGenerated(value, 12, 3.5);
|
|
3358
3663
|
}
|
|
3664
|
+
function validateBasicCredential(raw) {
|
|
3665
|
+
const b64 = capturedValue(raw, BASIC_VALUE_RE);
|
|
3666
|
+
if (b64 === null) return false;
|
|
3667
|
+
const bare = b64.replace(/=+$/, "");
|
|
3668
|
+
if (bare.length % 4 === 1) return false;
|
|
3669
|
+
const bytes = Buffer.from(b64, "base64");
|
|
3670
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
|
|
3671
|
+
const text = bytes.toString("utf8");
|
|
3672
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) return false;
|
|
3673
|
+
for (let i = 0; i < text.length; i++) {
|
|
3674
|
+
const c = text.charCodeAt(i);
|
|
3675
|
+
if (c < 32 || c === 127) return false;
|
|
3676
|
+
}
|
|
3677
|
+
const pair = BASIC_PAIR_RE.exec(text);
|
|
3678
|
+
if (!pair) return false;
|
|
3679
|
+
const password = pair[2];
|
|
3680
|
+
if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
|
|
3681
|
+
const lower2 = password.toLowerCase();
|
|
3682
|
+
if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
|
|
3683
|
+
return shannonEntropy(password) >= 3;
|
|
3684
|
+
}
|
|
3685
|
+
function validateOtpSecret(raw) {
|
|
3686
|
+
const secret = capturedValue(raw, OTP_CAPTURE_RE);
|
|
3687
|
+
if (secret === null || isOtpDocSecret(secret)) return false;
|
|
3688
|
+
return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
|
|
3689
|
+
}
|
|
3359
3690
|
function matchSensitivePath2(resolvedPath, originalPath, options = {}) {
|
|
3360
3691
|
const normalised = resolvedPath.replace(/\\/g, "/");
|
|
3361
3692
|
if (!options.systemAccessHandled && isLegacySystemPath(normalised))
|
|
@@ -3664,6 +3995,156 @@ function parsePySpec(raw) {
|
|
|
3664
3995
|
const exact = PY_EXACT_RE.exec(m[2].trim());
|
|
3665
3996
|
return { name, version: exact ? exact[1] : void 0 };
|
|
3666
3997
|
}
|
|
3998
|
+
function exactVersion(v) {
|
|
3999
|
+
if (!v) return void 0;
|
|
4000
|
+
const m = EXACT_VERSION_RE.exec(v.trim());
|
|
4001
|
+
return m ? m[1] : void 0;
|
|
4002
|
+
}
|
|
4003
|
+
function push(out, ecosystem, manager, name, version2, raw) {
|
|
4004
|
+
out.push({ ecosystem, manager, name, ...version2 ? { version: version2 } : {}, raw });
|
|
4005
|
+
}
|
|
4006
|
+
function gemInstall(words, from, out) {
|
|
4007
|
+
const dash = words.indexOf("--", from);
|
|
4008
|
+
const own = dash < 0 ? words : words.slice(0, dash);
|
|
4009
|
+
const pinned = exactVersion(flagValues(own, from, ["-v", "--version"])[0]);
|
|
4010
|
+
for (const raw of operands(own, from, GEM_VALUE_FLAGS)) {
|
|
4011
|
+
if (NON_REGISTRY_RE.test(raw) || /\.gem$/i.test(raw)) continue;
|
|
4012
|
+
const colon = raw.indexOf(":");
|
|
4013
|
+
const name = colon < 0 ? raw : raw.slice(0, colon);
|
|
4014
|
+
if (!GEM_NAME_RE.test(name)) continue;
|
|
4015
|
+
push(
|
|
4016
|
+
out,
|
|
4017
|
+
"RubyGems",
|
|
4018
|
+
"gem",
|
|
4019
|
+
name,
|
|
4020
|
+
colon < 0 ? pinned : exactVersion(raw.slice(colon + 1)),
|
|
4021
|
+
raw
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
function cargoCrates(words, from, manager, out) {
|
|
4026
|
+
const flags = manager === "cargo add" ? CARGO_ADD_VALUE_FLAGS : CARGO_INSTALL_VALUE_FLAGS;
|
|
4027
|
+
if (flagValues(words, from, ["--git", "--path", "--registry", "--index"]).length > 0) return;
|
|
4028
|
+
if (words.some((w) => w === "--git" || w === "--path")) return;
|
|
4029
|
+
const flagVersion = manager === "cargo install" ? exactVersion(flagValues(words, from, ["--version", "--vers"])[0]) : void 0;
|
|
4030
|
+
for (const raw of operands(words, from, flags)) {
|
|
4031
|
+
const at = raw.indexOf("@");
|
|
4032
|
+
const name = at < 0 ? raw : raw.slice(0, at);
|
|
4033
|
+
if (!CRATE_NAME_RE.test(name)) continue;
|
|
4034
|
+
push(
|
|
4035
|
+
out,
|
|
4036
|
+
"crates.io",
|
|
4037
|
+
manager,
|
|
4038
|
+
name,
|
|
4039
|
+
at < 0 ? flagVersion : exactVersion(raw.slice(at + 1)),
|
|
4040
|
+
raw
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
function goModules(words, from, manager, out) {
|
|
4045
|
+
let specs = operands(words, from, GO_VALUE_FLAGS);
|
|
4046
|
+
if (manager === "go run") specs = specs.slice(0, 1).filter((s) => s.includes("@"));
|
|
4047
|
+
for (const raw of specs) {
|
|
4048
|
+
const at = raw.indexOf("@");
|
|
4049
|
+
const pathPart = (at < 0 ? raw : raw.slice(0, at)).replace(/\/\.\.\.$/, "");
|
|
4050
|
+
const ver = at < 0 ? void 0 : raw.slice(at + 1);
|
|
4051
|
+
if (ver === "none") continue;
|
|
4052
|
+
if (!GO_PATH_RE.test(pathPart) || /\.go$/i.test(pathPart)) continue;
|
|
4053
|
+
const exact = ver && /^v\d/.test(ver) ? exactVersion(ver) : void 0;
|
|
4054
|
+
push(out, "Go", manager, pathPart, exact, raw);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
function dotnetAdd(words, from, out) {
|
|
4058
|
+
let k = -1;
|
|
4059
|
+
for (let i = from; i < Math.min(words.length, from + 2); i++) {
|
|
4060
|
+
if (lower(words[i]) === "package") {
|
|
4061
|
+
k = i;
|
|
4062
|
+
break;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
if (k < 0) return;
|
|
4066
|
+
const name = operands(words, k + 1, DOTNET_ADD_VALUE_FLAGS)[0];
|
|
4067
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4068
|
+
const version2 = exactVersion(flagValues(words, k + 1, ["-v", "--version"])[0]);
|
|
4069
|
+
push(out, "NuGet", "dotnet add package", name, version2, name);
|
|
4070
|
+
}
|
|
4071
|
+
function dotnetTool(words, from, out) {
|
|
4072
|
+
const name = operands(words, from, DOTNET_TOOL_VALUE_FLAGS)[0];
|
|
4073
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4074
|
+
const version2 = exactVersion(flagValues(words, from, ["--version"])[0]);
|
|
4075
|
+
push(out, "NuGet", "dotnet tool", name, version2, name);
|
|
4076
|
+
}
|
|
4077
|
+
function nugetInstall(words, from, out) {
|
|
4078
|
+
const lowered = words.map((w) => w !== null && w.startsWith("-") ? w.toLowerCase() : w);
|
|
4079
|
+
const name = operands(lowered, from, NUGET_VALUE_FLAGS)[0];
|
|
4080
|
+
if (!name || !NUGET_NAME_RE.test(name) || /\.config$/i.test(name)) return;
|
|
4081
|
+
const version2 = exactVersion(flagValues(lowered, from, ["-version"])[0]);
|
|
4082
|
+
push(out, "NuGet", "nuget install", name, version2, name);
|
|
4083
|
+
}
|
|
4084
|
+
function mavenGet(words, from, out) {
|
|
4085
|
+
const goal = words.slice(from).some(
|
|
4086
|
+
(w) => w !== null && /(?:^|:)dependency:get$|^org\.apache\.maven\.plugins:maven-dependency-plugin:(?:[\w.-]+:)?get$/.test(
|
|
4087
|
+
w
|
|
4088
|
+
)
|
|
4089
|
+
);
|
|
4090
|
+
if (!goal) return;
|
|
4091
|
+
const prop = (key) => {
|
|
4092
|
+
for (let i = from; i < words.length; i++) {
|
|
4093
|
+
const w = words[i];
|
|
4094
|
+
if (w === null) continue;
|
|
4095
|
+
if (w.startsWith(`-D${key}=`)) return w.slice(key.length + 3);
|
|
4096
|
+
if (w === "-D" && words[i + 1]?.startsWith(`${key}=`))
|
|
4097
|
+
return words[i + 1].slice(key.length + 1);
|
|
4098
|
+
}
|
|
4099
|
+
return void 0;
|
|
4100
|
+
};
|
|
4101
|
+
const artifact = prop("artifact");
|
|
4102
|
+
let coord;
|
|
4103
|
+
let ver;
|
|
4104
|
+
if (artifact) {
|
|
4105
|
+
const parts = artifact.split(":");
|
|
4106
|
+
if (parts.length < 2) return;
|
|
4107
|
+
coord = `${parts[0]}:${parts[1]}`;
|
|
4108
|
+
ver = parts[2];
|
|
4109
|
+
} else {
|
|
4110
|
+
const g = prop("groupId");
|
|
4111
|
+
const a = prop("artifactId");
|
|
4112
|
+
if (!g || !a) return;
|
|
4113
|
+
coord = `${g}:${a}`;
|
|
4114
|
+
ver = prop("version");
|
|
4115
|
+
}
|
|
4116
|
+
if (!MAVEN_COORD_RE.test(coord)) return;
|
|
4117
|
+
const exact = ver && !/^(?:LATEST|RELEASE)$/i.test(ver) ? exactVersion(ver) : void 0;
|
|
4118
|
+
push(out, "Maven", "mvn dependency:get", coord, exact, artifact ?? coord);
|
|
4119
|
+
}
|
|
4120
|
+
function composerPackages(words, from, manager, out) {
|
|
4121
|
+
const ops = operands(words, from, COMPOSER_VALUE_FLAGS);
|
|
4122
|
+
const specs = manager === "composer create-project" ? ops.slice(0, 1) : ops;
|
|
4123
|
+
for (const raw of specs) {
|
|
4124
|
+
const sep = raw.search(/[:=]/);
|
|
4125
|
+
const name = sep < 0 ? raw : raw.slice(0, sep);
|
|
4126
|
+
if (!COMPOSER_NAME_RE.test(name)) continue;
|
|
4127
|
+
let ver = sep < 0 ? void 0 : raw.slice(sep + 1);
|
|
4128
|
+
if (manager === "composer create-project" && ver === void 0) ver = ops[2] ?? void 0;
|
|
4129
|
+
push(out, "Packagist", manager, name.toLowerCase(), exactVersion(ver), raw);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
function editorExtensions(words, from, out) {
|
|
4133
|
+
for (const raw of flagValues(words, from, ["--install-extension"])) {
|
|
4134
|
+
if (/\.vsix$/i.test(raw) || NON_REGISTRY_RE.test(raw)) continue;
|
|
4135
|
+
const at = raw.lastIndexOf("@");
|
|
4136
|
+
const name = at <= 0 ? raw : raw.slice(0, at);
|
|
4137
|
+
if (!EXTENSION_ID_RE.test(name)) continue;
|
|
4138
|
+
push(
|
|
4139
|
+
out,
|
|
4140
|
+
"VSCode",
|
|
4141
|
+
"code --install-extension",
|
|
4142
|
+
name,
|
|
4143
|
+
at <= 0 ? void 0 : exactVersion(raw.slice(at + 1)),
|
|
4144
|
+
raw
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
3667
4148
|
function basename(w) {
|
|
3668
4149
|
return (w ?? "").toLowerCase().split("/").pop() ?? "";
|
|
3669
4150
|
}
|
|
@@ -3895,7 +4376,63 @@ function fromCall(words, out, depth) {
|
|
|
3895
4376
|
pushPy(out, "pipenv", operands(words, v + 1, PIPENV_VALUE_FLAGS));
|
|
3896
4377
|
return;
|
|
3897
4378
|
}
|
|
4379
|
+
case "gem": {
|
|
4380
|
+
const v = verbIndex(words, start + 1, GEM_VALUE_FLAGS);
|
|
4381
|
+
if (v >= 0 && ["install", "i"].includes(lower(words[v]))) gemInstall(words, v + 1, out);
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
case "bundle": {
|
|
4385
|
+
const v = verbIndex(words, start + 1, BUNDLE_VALUE_FLAGS);
|
|
4386
|
+
if (v < 0 || lower(words[v]) !== "add") return;
|
|
4387
|
+
if (flagValues(words, v + 1, ["--git", "--github", "--path"]).length > 0) return;
|
|
4388
|
+
const version2 = exactVersion(flagValues(words, v + 1, ["-v", "--version"])[0]);
|
|
4389
|
+
for (const raw of operands(words, v + 1, BUNDLE_VALUE_FLAGS))
|
|
4390
|
+
if (GEM_NAME_RE.test(raw)) push(out, "RubyGems", "bundle add", raw, version2, raw);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
case "cargo": {
|
|
4394
|
+
const from = words[start + 1]?.startsWith("+") ? start + 2 : start + 1;
|
|
4395
|
+
const v = verbIndex(words, from, CARGO_ADD_VALUE_FLAGS);
|
|
4396
|
+
const verb = lower(words[v]);
|
|
4397
|
+
if (verb === "add") cargoCrates(words, v + 1, "cargo add", out);
|
|
4398
|
+
else if (verb === "install") cargoCrates(words, v + 1, "cargo install", out);
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
case "go": {
|
|
4402
|
+
const v = verbIndex(words, start + 1, GO_VALUE_FLAGS);
|
|
4403
|
+
const verb = lower(words[v]);
|
|
4404
|
+
if (verb === "get" || verb === "install" || verb === "run")
|
|
4405
|
+
goModules(words, v + 1, `go ${verb}`, out);
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
case "dotnet": {
|
|
4409
|
+
const v = verbIndex(words, start + 1, DOTNET_ADD_VALUE_FLAGS);
|
|
4410
|
+
const verb = lower(words[v]);
|
|
4411
|
+
if (verb === "add") dotnetAdd(words, v + 1, out);
|
|
4412
|
+
else if (verb === "tool" && ["install", "update"].includes(lower(words[v + 1])))
|
|
4413
|
+
dotnetTool(words, v + 2, out);
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
case "nuget": {
|
|
4417
|
+
if (lower(words[start + 1]) === "install") nugetInstall(words, start + 2, out);
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
case "mvn":
|
|
4421
|
+
case "mvnw":
|
|
4422
|
+
mavenGet(words, start + 1, out);
|
|
4423
|
+
return;
|
|
4424
|
+
case "composer": {
|
|
4425
|
+
let v = verbIndex(words, start + 1, COMPOSER_VALUE_FLAGS);
|
|
4426
|
+
if (v >= 0 && lower(words[v]) === "global") v = verbIndex(words, v + 1, COMPOSER_VALUE_FLAGS);
|
|
4427
|
+
const verb = lower(words[v]);
|
|
4428
|
+
if (verb === "require" || verb === "req" || verb === "r")
|
|
4429
|
+
composerPackages(words, v + 1, "composer require", out);
|
|
4430
|
+
else if (verb === "create-project")
|
|
4431
|
+
composerPackages(words, v + 1, "composer create-project", out);
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
3898
4434
|
default:
|
|
4435
|
+
if (EDITOR_HEADS.includes(family)) editorExtensions(words, start + 1, out);
|
|
3899
4436
|
return;
|
|
3900
4437
|
}
|
|
3901
4438
|
}
|
|
@@ -5421,12 +5958,13 @@ function gitRepoPath(url) {
|
|
|
5421
5958
|
function isDestructiveToolName(bareTool) {
|
|
5422
5959
|
return DESTRUCTIVE_VERB_RE.test(bareTool);
|
|
5423
5960
|
}
|
|
5424
|
-
function* validCardRuns(text, base2) {
|
|
5961
|
+
function* validCardRuns(text, base2, exemptFractions = true) {
|
|
5425
5962
|
const re = new RegExp(base2.source, "g");
|
|
5426
5963
|
let m;
|
|
5427
5964
|
while ((m = re.exec(text)) !== null) {
|
|
5428
5965
|
const digits = m[0].replace(/\D/g, "");
|
|
5429
|
-
|
|
5966
|
+
const fraction = exemptFractions && isDecimalFraction(text, m.index, digits.length === m[0].length);
|
|
5967
|
+
if (!fraction && validateLuhn(digits)) {
|
|
5430
5968
|
yield { index: m.index, length: m[0].length, digits };
|
|
5431
5969
|
} else {
|
|
5432
5970
|
re.lastIndex = m.index + 1;
|
|
@@ -5439,12 +5977,12 @@ function hasValidCard(text) {
|
|
|
5439
5977
|
}
|
|
5440
5978
|
return false;
|
|
5441
5979
|
}
|
|
5442
|
-
function replaceValidCards(text, replace) {
|
|
5980
|
+
function replaceValidCards(text, replace, { exemptFractions = true } = {}) {
|
|
5443
5981
|
let out = text;
|
|
5444
5982
|
for (const base2 of [PII_CC16_RE, PII_CC15_RE]) {
|
|
5445
5983
|
let res = "";
|
|
5446
5984
|
let last = 0;
|
|
5447
|
-
for (const run of validCardRuns(out, base2)) {
|
|
5985
|
+
for (const run of validCardRuns(out, base2, exemptFractions)) {
|
|
5448
5986
|
res += out.slice(last, run.index) + replace(run.digits);
|
|
5449
5987
|
last = run.index + run.length;
|
|
5450
5988
|
}
|
|
@@ -5458,11 +5996,11 @@ function isDecimalFraction(text, index, unseparated) {
|
|
|
5458
5996
|
const a = text[index - 2];
|
|
5459
5997
|
if (sep === ".") {
|
|
5460
5998
|
if (a !== void 0 && a >= "0" && a <= "9") return true;
|
|
5461
|
-
return a === void 0 ||
|
|
5999
|
+
return a === void 0 || (a === "-" || a === "+") && index === 2;
|
|
5462
6000
|
}
|
|
5463
6001
|
if (sep === "," && a === "0") {
|
|
5464
6002
|
const b = text[index - 3];
|
|
5465
|
-
return b === void 0 ||
|
|
6003
|
+
return b === void 0 || b === ";";
|
|
5466
6004
|
}
|
|
5467
6005
|
return false;
|
|
5468
6006
|
}
|
|
@@ -5598,6 +6136,9 @@ function normalizeTrustedEntry(raw) {
|
|
|
5598
6136
|
return full === null ? null : trustedEntryOf(full);
|
|
5599
6137
|
}
|
|
5600
6138
|
function maskPersonalData(text) {
|
|
6139
|
+
return maskText(text, false);
|
|
6140
|
+
}
|
|
6141
|
+
function maskText(text, exemptFractions) {
|
|
5601
6142
|
if (!text) return text;
|
|
5602
6143
|
try {
|
|
5603
6144
|
let out = text;
|
|
@@ -5605,7 +6146,9 @@ function maskPersonalData(text) {
|
|
|
5605
6146
|
const v = m.replace(/[ -]/g, "").toUpperCase();
|
|
5606
6147
|
return validateIban(v) ? `${v.slice(0, 4)} **** **** ${v.slice(-4)}` : m;
|
|
5607
6148
|
});
|
|
5608
|
-
out = replaceValidCards(out, (digits) => `**** **** **** ${digits.slice(-4)}
|
|
6149
|
+
out = replaceValidCards(out, (digits) => `**** **** **** ${digits.slice(-4)}`, {
|
|
6150
|
+
exemptFractions
|
|
6151
|
+
});
|
|
5609
6152
|
out = out.replace(
|
|
5610
6153
|
ACCOUNT_VALUE_RE,
|
|
5611
6154
|
(m, v) => IBAN_LENGTH[v.slice(0, 2).toUpperCase()] === v.length ? `"****${v.slice(-4)}"` : m
|
|
@@ -5628,7 +6171,7 @@ function maskLeaf(value, key, depth) {
|
|
|
5628
6171
|
if (depth > MAX_MASK_DEPTH) return "[REDACTED]";
|
|
5629
6172
|
if (typeof value === "string") {
|
|
5630
6173
|
try {
|
|
5631
|
-
const json = `{${JSON.stringify(key)}:${JSON.stringify(value)}}`;
|
|
6174
|
+
const json = `{${JSON.stringify(key)}:${JSON.stringify(maskPersonalData(value))}}`;
|
|
5632
6175
|
return Object.values(JSON.parse(maskPersonalData(json)))[0];
|
|
5633
6176
|
} catch {
|
|
5634
6177
|
return "[REDACTED]";
|
|
@@ -5636,13 +6179,20 @@ function maskLeaf(value, key, depth) {
|
|
|
5636
6179
|
}
|
|
5637
6180
|
if (typeof value === "number") {
|
|
5638
6181
|
const text = String(value);
|
|
5639
|
-
const masked =
|
|
6182
|
+
const masked = maskText(text, true);
|
|
5640
6183
|
return masked === text ? value : masked;
|
|
5641
6184
|
}
|
|
5642
6185
|
if (Array.isArray(value)) return value.map((v) => maskLeaf(v, key, depth + 1));
|
|
5643
6186
|
if (value && typeof value === "object") {
|
|
6187
|
+
const used = /* @__PURE__ */ new Set();
|
|
5644
6188
|
return Object.fromEntries(
|
|
5645
|
-
Object.entries(value).map(([k, v]) =>
|
|
6189
|
+
Object.entries(value).map(([k, v]) => {
|
|
6190
|
+
const base2 = maskPersonalData(k);
|
|
6191
|
+
let name = base2;
|
|
6192
|
+
for (let n = 2; used.has(name); n++) name = `${base2}#${n}`;
|
|
6193
|
+
used.add(name);
|
|
6194
|
+
return [name, maskLeaf(v, k, depth + 1)];
|
|
6195
|
+
})
|
|
5646
6196
|
);
|
|
5647
6197
|
}
|
|
5648
6198
|
return value;
|
|
@@ -6650,7 +7200,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
6650
7200
|
}
|
|
6651
7201
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
6652
7202
|
}
|
|
6653
|
-
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE,
|
|
7203
|
+
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, exempt, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
|
|
6654
7204
|
var init_dist = __esm({
|
|
6655
7205
|
"packages/policy-engine/dist/index.mjs"() {
|
|
6656
7206
|
"use strict";
|
|
@@ -7451,6 +8001,11 @@ var init_dist = __esm({
|
|
|
7451
8001
|
example: "cat /etc/shadow",
|
|
7452
8002
|
advice: "Keep Review, or choose Block when agents must never read system password hashes."
|
|
7453
8003
|
},
|
|
8004
|
+
"files.system-dirs": {
|
|
8005
|
+
plain: "The agent writes, creates or deletes a file in a system directory, or sends a system file in a request. /usr/local and a container working directory under /usr/src are ordinary project space and are not included.",
|
|
8006
|
+
example: "wget -O /usr/bin/tool https://example.com/tool",
|
|
8007
|
+
advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
|
|
8008
|
+
},
|
|
7454
8009
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
7455
8010
|
"commands.inline-exec": {
|
|
7456
8011
|
plain: "The agent runs a small program written inside the command itself.",
|
|
@@ -7647,7 +8202,7 @@ var init_dist = __esm({
|
|
|
7647
8202
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
7648
8203
|
},
|
|
7649
8204
|
"loading.malicious-package": {
|
|
7650
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
|
|
8205
|
+
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago. Covers npm, PyPI, RubyGems, crates.io, Go, NuGet, Maven, Packagist and VS Code extensions; the publish-age and install-script signals are npm and PyPI only.",
|
|
7651
8206
|
example: "npm install of a package flagged as malware",
|
|
7652
8207
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
7653
8208
|
}
|
|
@@ -7693,6 +8248,15 @@ var init_dist = __esm({
|
|
|
7693
8248
|
defaultValue: "review",
|
|
7694
8249
|
legacy: ["review-password-hash-read"]
|
|
7695
8250
|
},
|
|
8251
|
+
{
|
|
8252
|
+
id: "files.system-dirs",
|
|
8253
|
+
group: "files",
|
|
8254
|
+
title: "System directories",
|
|
8255
|
+
catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
|
|
8256
|
+
values: ALL,
|
|
8257
|
+
defaultValue: "review",
|
|
8258
|
+
legacy: ["review-system-dir-mutation", "review-system-file-upload"]
|
|
8259
|
+
},
|
|
7696
8260
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
7697
8261
|
{
|
|
7698
8262
|
id: "commands.inline-exec",
|
|
@@ -8990,6 +9554,10 @@ var init_dist = __esm({
|
|
|
8990
9554
|
AND_OP = probeBinOp("a && b");
|
|
8991
9555
|
OR_OP = probeBinOp("a || b");
|
|
8992
9556
|
AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
9557
|
+
MAX_CALL_DEPTH = 4;
|
|
9558
|
+
MAX_CALL_WALKS = 256;
|
|
9559
|
+
callDepth = 0;
|
|
9560
|
+
callWalks = 0;
|
|
8993
9561
|
FS_OP_CACHE_MAX = 5e3;
|
|
8994
9562
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
8995
9563
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
@@ -9042,10 +9610,79 @@ var init_dist = __esm({
|
|
|
9042
9610
|
MAX_ARGV_CHARACTERS = 262144;
|
|
9043
9611
|
({ syntax: syntax2 } = mvdanSh2);
|
|
9044
9612
|
MAX_ALTERNATIVES = 64;
|
|
9613
|
+
MAX_CALL_DEPTH2 = 4;
|
|
9614
|
+
MAX_CALL_WALKS2 = 256;
|
|
9615
|
+
POSITIONAL = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"];
|
|
9045
9616
|
MAX_DEPTH = 64;
|
|
9046
9617
|
MAX_PAYLOAD_DEPTH = 24;
|
|
9047
9618
|
unique = (a) => [...new Set(a)].slice(0, MAX_ALTERNATIVES);
|
|
9048
|
-
clone = (s) => ({
|
|
9619
|
+
clone = (s) => ({
|
|
9620
|
+
cwd: [...s.cwd],
|
|
9621
|
+
vars: new Map(s.vars),
|
|
9622
|
+
...s.callDepth ? { callDepth: s.callDepth } : {}
|
|
9623
|
+
});
|
|
9624
|
+
SENDING_STDIN = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "socat", "telnet", "curl", "wget"]);
|
|
9625
|
+
DOWNLOAD_SHORT_VALUE = {
|
|
9626
|
+
curl: "odDeEFHKmPrtTuUwxXyYzAbcCQ",
|
|
9627
|
+
wget: "OoaPiBetTwQlARDXIUY",
|
|
9628
|
+
aria2c: "odisx"
|
|
9629
|
+
};
|
|
9630
|
+
DOWNLOAD_LONG_VALUE = {
|
|
9631
|
+
curl: [
|
|
9632
|
+
"--output",
|
|
9633
|
+
"--output-dir",
|
|
9634
|
+
"--dump-header",
|
|
9635
|
+
"--cookie-jar",
|
|
9636
|
+
"--data",
|
|
9637
|
+
"--data-binary",
|
|
9638
|
+
"--data-raw",
|
|
9639
|
+
"--data-ascii",
|
|
9640
|
+
"--data-urlencode",
|
|
9641
|
+
"--form",
|
|
9642
|
+
"--header",
|
|
9643
|
+
"--request",
|
|
9644
|
+
"--user",
|
|
9645
|
+
"--user-agent",
|
|
9646
|
+
"--referer",
|
|
9647
|
+
"--cookie",
|
|
9648
|
+
"--config",
|
|
9649
|
+
"--json",
|
|
9650
|
+
"--upload-file",
|
|
9651
|
+
"--max-time",
|
|
9652
|
+
"--proxy",
|
|
9653
|
+
"--write-out",
|
|
9654
|
+
"--url"
|
|
9655
|
+
],
|
|
9656
|
+
wget: [
|
|
9657
|
+
"--output-document",
|
|
9658
|
+
"--output-file",
|
|
9659
|
+
"--append-output",
|
|
9660
|
+
"--directory-prefix",
|
|
9661
|
+
"--input-file",
|
|
9662
|
+
"--post-data",
|
|
9663
|
+
"--post-file",
|
|
9664
|
+
"--body-data",
|
|
9665
|
+
"--body-file",
|
|
9666
|
+
"--method",
|
|
9667
|
+
"--header",
|
|
9668
|
+
"--user-agent",
|
|
9669
|
+
"--user",
|
|
9670
|
+
"--password",
|
|
9671
|
+
"--tries",
|
|
9672
|
+
"--timeout"
|
|
9673
|
+
],
|
|
9674
|
+
aria2c: ["--out", "--dir", "--input-file"]
|
|
9675
|
+
};
|
|
9676
|
+
DOWNLOAD_OUTPUT_FLAGS = {
|
|
9677
|
+
curl: ["-o", "--output", "-D", "--dump-header", "-c", "--cookie-jar"],
|
|
9678
|
+
wget: ["-O", "--output-document", "-o", "--output-file", "-a", "--append-output"],
|
|
9679
|
+
aria2c: ["-o", "--out"]
|
|
9680
|
+
};
|
|
9681
|
+
DOWNLOAD_DIR_FLAGS = {
|
|
9682
|
+
curl: ["--output-dir"],
|
|
9683
|
+
wget: ["-P", "--directory-prefix"],
|
|
9684
|
+
aria2c: ["-d", "--dir"]
|
|
9685
|
+
};
|
|
9049
9686
|
base = (s) => (s ?? "").replace(/\/+$/, "").split("/").pop() ?? "";
|
|
9050
9687
|
quoteGlob = (s) => s.replace(/[\\*?\[\]{}()!+@]/g, "\\$&");
|
|
9051
9688
|
asWord = (p) => ({ value: p.value, ...p.glob ? { pattern: p.pattern } : {} });
|
|
@@ -9070,6 +9707,18 @@ var init_dist = __esm({
|
|
|
9070
9707
|
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
9071
9708
|
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
9072
9709
|
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
9710
|
+
SYSTEM_DIRS = [
|
|
9711
|
+
"/usr",
|
|
9712
|
+
"/etc",
|
|
9713
|
+
"/boot",
|
|
9714
|
+
"/bin",
|
|
9715
|
+
"/sbin",
|
|
9716
|
+
"/lib",
|
|
9717
|
+
"/lib64",
|
|
9718
|
+
"/var/log"
|
|
9719
|
+
];
|
|
9720
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
9721
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
9073
9722
|
AGENT_INSTRUCTION_FILES = [
|
|
9074
9723
|
".gitlab-ci.yml",
|
|
9075
9724
|
"CLAUDE.md",
|
|
@@ -9087,6 +9736,7 @@ var init_dist = __esm({
|
|
|
9087
9736
|
".husky"
|
|
9088
9737
|
];
|
|
9089
9738
|
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9739
|
+
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
9090
9740
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
9091
9741
|
BIP39_ENGLISH = `abandon
|
|
9092
9742
|
ability
|
|
@@ -11413,15 +12063,20 @@ zoo`.split("\n");
|
|
|
11413
12063
|
IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
|
|
11414
12064
|
PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
|
|
11415
12065
|
DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
|
|
12066
|
+
CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
|
|
11416
12067
|
EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
|
|
11417
12068
|
FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
|
|
11418
|
-
DERIVATIVE_NAME_RE = /(?:hash|digest|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
|
|
12069
|
+
DERIVATIVE_NAME_RE = /(?:hash|digest|fingerprint|thumbprint|checksum|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
|
|
11419
12070
|
QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
|
|
11420
12071
|
OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
|
|
11421
|
-
|
|
12072
|
+
OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
|
|
12073
|
+
ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
|
|
11422
12074
|
ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
|
|
11423
|
-
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
|
|
12075
|
+
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|pass)/i;
|
|
11424
12076
|
ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
|
|
12077
|
+
BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
|
|
12078
|
+
BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
|
|
12079
|
+
OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
|
|
11425
12080
|
DLP_PATTERNS = [
|
|
11426
12081
|
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
11427
12082
|
{
|
|
@@ -11759,6 +12414,22 @@ zoo`.split("\n");
|
|
|
11759
12414
|
contextBoost: true,
|
|
11760
12415
|
minEntropy: 3
|
|
11761
12416
|
},
|
|
12417
|
+
// ── HTTP Basic credential ─────────────────────────────────────────────────
|
|
12418
|
+
// `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
|
|
12419
|
+
// config list. The validator decodes the value and accepts only a
|
|
12420
|
+
// `user:password` whose password looks generated (letters and digits, 8+
|
|
12421
|
+
// characters, the query-credential rule), so prose ("Basic authentication")
|
|
12422
|
+
// and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
|
|
12423
|
+
// review: the same weight as a bare Bearer token.
|
|
12424
|
+
{
|
|
12425
|
+
name: "HTTP Basic Credential",
|
|
12426
|
+
regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
|
|
12427
|
+
severity: "review",
|
|
12428
|
+
keywords: ["basic"],
|
|
12429
|
+
validate: validateBasicCredential,
|
|
12430
|
+
redactGroup: 1,
|
|
12431
|
+
argumentsOnly: true
|
|
12432
|
+
},
|
|
11762
12433
|
// ── Resend ────────────────────────────────────────────────────────────────
|
|
11763
12434
|
{
|
|
11764
12435
|
name: "Resend API Key",
|
|
@@ -11900,6 +12571,19 @@ zoo`.split("\n");
|
|
|
11900
12571
|
validate: validateQueryCredential,
|
|
11901
12572
|
argumentsOnly: true
|
|
11902
12573
|
},
|
|
12574
|
+
// ── OTP secret in a constructor ───────────────────────────────────────────
|
|
12575
|
+
// `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
|
|
12576
|
+
// the shared secret of a one-time-password generator written as a literal.
|
|
12577
|
+
// The otpauth URI form is `Credential in URL Query`. review.
|
|
12578
|
+
{
|
|
12579
|
+
name: "OTP Secret",
|
|
12580
|
+
regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
|
|
12581
|
+
severity: "review",
|
|
12582
|
+
keywords: ["otp"],
|
|
12583
|
+
validate: validateOtpSecret,
|
|
12584
|
+
redactGroup: 1,
|
|
12585
|
+
argumentsOnly: true
|
|
12586
|
+
},
|
|
11903
12587
|
// ── Secret assigned to a telling name ─────────────────────────────────────
|
|
11904
12588
|
// `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
|
|
11905
12589
|
// not a finding: most such lines hold a reference (`process.env.X`), a
|
|
@@ -11907,12 +12591,14 @@ zoo`.split("\n");
|
|
|
11907
12591
|
// generated: 12+ characters, not an identifier, Shannon entropy of 3.5
|
|
11908
12592
|
// bits per character or more. review, never block (node9Firewall#348,
|
|
11909
12593
|
// item 4). Tuned on CredData's public split; its look-alike lines are the
|
|
11910
|
-
// negative control.
|
|
12594
|
+
// negative control. A quote may be escaped (`\"access_token\":\"...\"`):
|
|
12595
|
+
// JSON written inside a string literal. The two optional quotes after the
|
|
12596
|
+
// name are written out, not as a counted group, to keep star height 1.
|
|
11911
12597
|
{
|
|
11912
12598
|
name: "Assigned Secret",
|
|
11913
|
-
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]
|
|
12599
|
+
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|\bpass(?![A-Za-z0-9])|_pass(?![A-Za-z0-9]))[A-Za-z0-9_]{0,40}\\?["'\]]?\\?["'\]]?[ \t]{0,8}(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})/i,
|
|
11914
12600
|
severity: "review",
|
|
11915
|
-
keywords: ["
|
|
12601
|
+
keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
|
|
11916
12602
|
validate: validateAssignedSecret,
|
|
11917
12603
|
argumentsOnly: true
|
|
11918
12604
|
}
|
|
@@ -12180,7 +12866,169 @@ zoo`.split("\n");
|
|
|
12180
12866
|
]);
|
|
12181
12867
|
PY_SPEC_RE = /^([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?:\[[^\]]*\])?\s*(.*)$/;
|
|
12182
12868
|
PY_EXACT_RE = /^===?\s*v?(\d+(?:\.\d+)*(?:[a-zA-Z0-9.+!-]*))$/;
|
|
12869
|
+
EXACT_VERSION_RE = /^=?v?(\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?)$/;
|
|
12870
|
+
GEM_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12871
|
+
GEM_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12872
|
+
"-v",
|
|
12873
|
+
"--version",
|
|
12874
|
+
"-i",
|
|
12875
|
+
"--install-dir",
|
|
12876
|
+
"-n",
|
|
12877
|
+
"--bindir",
|
|
12878
|
+
"--document",
|
|
12879
|
+
"--build-root",
|
|
12880
|
+
"-P",
|
|
12881
|
+
"--trust-policy",
|
|
12882
|
+
"-s",
|
|
12883
|
+
"--source",
|
|
12884
|
+
"-p",
|
|
12885
|
+
"--http-proxy",
|
|
12886
|
+
"--platform",
|
|
12887
|
+
"-g",
|
|
12888
|
+
"--file",
|
|
12889
|
+
"--without"
|
|
12890
|
+
]);
|
|
12891
|
+
BUNDLE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12892
|
+
"-v",
|
|
12893
|
+
"--version",
|
|
12894
|
+
"-g",
|
|
12895
|
+
"--group",
|
|
12896
|
+
"-s",
|
|
12897
|
+
"--source",
|
|
12898
|
+
"-r",
|
|
12899
|
+
"--require",
|
|
12900
|
+
"--path",
|
|
12901
|
+
"--git",
|
|
12902
|
+
"--github",
|
|
12903
|
+
"--branch",
|
|
12904
|
+
"--ref",
|
|
12905
|
+
"--glob"
|
|
12906
|
+
]);
|
|
12907
|
+
CRATE_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*$/u;
|
|
12908
|
+
CARGO_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12909
|
+
"-F",
|
|
12910
|
+
"--features",
|
|
12911
|
+
"--rename",
|
|
12912
|
+
"--registry",
|
|
12913
|
+
"--manifest-path",
|
|
12914
|
+
"-p",
|
|
12915
|
+
"--package",
|
|
12916
|
+
"--git",
|
|
12917
|
+
"--branch",
|
|
12918
|
+
"--tag",
|
|
12919
|
+
"--rev",
|
|
12920
|
+
"--path",
|
|
12921
|
+
"--base",
|
|
12922
|
+
"--target",
|
|
12923
|
+
"--color",
|
|
12924
|
+
"--config",
|
|
12925
|
+
"-Z",
|
|
12926
|
+
"--lockfile-path"
|
|
12927
|
+
]);
|
|
12928
|
+
CARGO_INSTALL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12929
|
+
"--version",
|
|
12930
|
+
"--vers",
|
|
12931
|
+
"--git",
|
|
12932
|
+
"--branch",
|
|
12933
|
+
"--tag",
|
|
12934
|
+
"--rev",
|
|
12935
|
+
"--path",
|
|
12936
|
+
"--root",
|
|
12937
|
+
"--index",
|
|
12938
|
+
"--registry",
|
|
12939
|
+
"-F",
|
|
12940
|
+
"--features",
|
|
12941
|
+
"--profile",
|
|
12942
|
+
"--target",
|
|
12943
|
+
"--target-dir",
|
|
12944
|
+
"-j",
|
|
12945
|
+
"--jobs",
|
|
12946
|
+
"--bin",
|
|
12947
|
+
"--example",
|
|
12948
|
+
"--color",
|
|
12949
|
+
"--config",
|
|
12950
|
+
"-Z"
|
|
12951
|
+
]);
|
|
12952
|
+
GO_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12953
|
+
"-C",
|
|
12954
|
+
"-tags",
|
|
12955
|
+
"-ldflags",
|
|
12956
|
+
"-gcflags",
|
|
12957
|
+
"-asmflags",
|
|
12958
|
+
"-mod",
|
|
12959
|
+
"-modfile",
|
|
12960
|
+
"-o",
|
|
12961
|
+
"-p",
|
|
12962
|
+
"-pkgdir",
|
|
12963
|
+
"-toolexec",
|
|
12964
|
+
"-exec",
|
|
12965
|
+
"-overlay",
|
|
12966
|
+
"-pgo",
|
|
12967
|
+
"-buildmode",
|
|
12968
|
+
"-compiler",
|
|
12969
|
+
"-gccgoflags",
|
|
12970
|
+
"-installsuffix",
|
|
12971
|
+
"-covermode",
|
|
12972
|
+
"-coverpkg"
|
|
12973
|
+
]);
|
|
12974
|
+
GO_PATH_RE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+(?:\/[\p{L}\p{N}_.~+-]+)*$/u;
|
|
12975
|
+
NUGET_NAME_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u;
|
|
12976
|
+
DOTNET_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12977
|
+
"-v",
|
|
12978
|
+
"--version",
|
|
12979
|
+
"-s",
|
|
12980
|
+
"--source",
|
|
12981
|
+
"-f",
|
|
12982
|
+
"--framework",
|
|
12983
|
+
"--package-directory"
|
|
12984
|
+
]);
|
|
12985
|
+
DOTNET_TOOL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12986
|
+
"--version",
|
|
12987
|
+
"--add-source",
|
|
12988
|
+
"--configfile",
|
|
12989
|
+
"--framework",
|
|
12990
|
+
"--tool-path",
|
|
12991
|
+
"-a",
|
|
12992
|
+
"--arch",
|
|
12993
|
+
"--tool-manifest",
|
|
12994
|
+
"-v",
|
|
12995
|
+
"--verbosity"
|
|
12996
|
+
]);
|
|
12997
|
+
NUGET_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12998
|
+
"-version",
|
|
12999
|
+
"-outputdirectory",
|
|
13000
|
+
"-o",
|
|
13001
|
+
"-source",
|
|
13002
|
+
"-configfile",
|
|
13003
|
+
"-fallbacksource",
|
|
13004
|
+
"-framework",
|
|
13005
|
+
"-packagesavemode",
|
|
13006
|
+
"-dependencyversion",
|
|
13007
|
+
"-solutiondirectory",
|
|
13008
|
+
"-verbosity"
|
|
13009
|
+
]);
|
|
13010
|
+
MAVEN_COORD_RE = /^[\p{L}\p{N}_.-]+:[\p{L}\p{N}_.-]+$/u;
|
|
13011
|
+
COMPOSER_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*\/[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
13012
|
+
COMPOSER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
13013
|
+
"-d",
|
|
13014
|
+
"--working-dir",
|
|
13015
|
+
"--stability",
|
|
13016
|
+
"--repository",
|
|
13017
|
+
"--add-repository"
|
|
13018
|
+
]);
|
|
13019
|
+
EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
|
|
13020
|
+
EXTENSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*\.[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12183
13021
|
MANAGER_HEADS = /* @__PURE__ */ new Set([
|
|
13022
|
+
"gem",
|
|
13023
|
+
"bundle",
|
|
13024
|
+
"cargo",
|
|
13025
|
+
"go",
|
|
13026
|
+
"dotnet",
|
|
13027
|
+
"nuget",
|
|
13028
|
+
"mvn",
|
|
13029
|
+
"mvnw",
|
|
13030
|
+
"composer",
|
|
13031
|
+
...EDITOR_HEADS,
|
|
12184
13032
|
"npm",
|
|
12185
13033
|
"npx",
|
|
12186
13034
|
"pnpm",
|
|
@@ -12259,6 +13107,7 @@ zoo`.split("\n");
|
|
|
12259
13107
|
MAP_GOVERNED = /* @__PURE__ */ new Set([
|
|
12260
13108
|
"files.shell-startup",
|
|
12261
13109
|
"files.password-hashes",
|
|
13110
|
+
"files.system-dirs",
|
|
12262
13111
|
"commands.inline-exec",
|
|
12263
13112
|
"commands.eval-dynamic",
|
|
12264
13113
|
"commands.curl-pipe-shell",
|
|
@@ -12527,7 +13376,6 @@ zoo`.split("\n");
|
|
|
12527
13376
|
PII_CC16_RE = /\b(?:4\d{3}|5[1-5]\d{2}|6\d{3})[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/;
|
|
12528
13377
|
PII_CC15_RE = /\b3[47]\d{2}[-\s]?\d{6}[-\s]?\d{5}\b/;
|
|
12529
13378
|
PII_IBAN_RE = /\b[A-Z]{2}\d{2}(?:[A-Z0-9]|[ -][A-Z0-9]){11,30}\b/;
|
|
12530
|
-
NUMBER_LEAD_RE = /[\s:=;([{"'+-]/;
|
|
12531
13379
|
REALTIME_PII_PATTERNS = ["SSN", "Credit Card", "IBAN"];
|
|
12532
13380
|
MAX_PII_SCAN_BYTES = 1e5;
|
|
12533
13381
|
MASKED_KINDS = /* @__PURE__ */ new Set(["email", "iban", "account"]);
|
|
@@ -12749,7 +13597,7 @@ zoo`.split("\n");
|
|
|
12749
13597
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
12750
13598
|
];
|
|
12751
13599
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
12752
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
13600
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v28";
|
|
12753
13601
|
DEDUPE_PREVIEW_LEN = 120;
|
|
12754
13602
|
ENGINE_VERSION = "1.4.0";
|
|
12755
13603
|
}
|
|
@@ -16158,14 +17006,23 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
16158
17006
|
clearTimeout(timer);
|
|
16159
17007
|
}
|
|
16160
17008
|
}
|
|
16161
|
-
async function waitForDaemonDecision(id, signal) {
|
|
17009
|
+
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
16162
17010
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16163
17011
|
const waitCtrl = new AbortController();
|
|
16164
|
-
|
|
17012
|
+
let capped = false;
|
|
17013
|
+
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
17014
|
+
capped = true;
|
|
17015
|
+
waitCtrl.abort();
|
|
17016
|
+
}, maxWaitMs) : void 0;
|
|
16165
17017
|
const onAbort = () => waitCtrl.abort();
|
|
16166
17018
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
16167
17019
|
try {
|
|
16168
|
-
const waitRes = await fetch(`${base2}/wait/${id}`, { signal: waitCtrl.signal })
|
|
17020
|
+
const waitRes = await fetch(`${base2}/wait/${id}`, { signal: waitCtrl.signal }).catch(
|
|
17021
|
+
(err2) => {
|
|
17022
|
+
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
17023
|
+
throw err2;
|
|
17024
|
+
}
|
|
17025
|
+
);
|
|
16169
17026
|
if (!waitRes.ok) return { decision: "deny" };
|
|
16170
17027
|
const { decision, source, reason } = await waitRes.json();
|
|
16171
17028
|
if (decision === "allow") return { decision: "allow", source };
|
|
@@ -17791,13 +18648,13 @@ function maskRiskMetadataForCloud(rm) {
|
|
|
17791
18648
|
const r = rm;
|
|
17792
18649
|
const dests = Array.isArray(r.exposureDestinations) ? r.exposureDestinations.filter((d) => typeof d === "string") : [];
|
|
17793
18650
|
const swaps = dests.filter(isMaskedKind).map((d) => [destinationValue(d), destinationValue(maskDestination(d))]).filter(([full]) => full.length > 0).sort((a, b) => b[0].length - a[0].length);
|
|
17794
|
-
const
|
|
18651
|
+
const maskText2 = (text) => {
|
|
17795
18652
|
let out2 = text;
|
|
17796
18653
|
for (const [full, masked] of swaps) out2 = out2.split(full).join(masked);
|
|
17797
18654
|
return maskPersonalData(out2);
|
|
17798
18655
|
};
|
|
17799
18656
|
const out = {};
|
|
17800
|
-
for (const [k, v] of Object.entries(r)) out[k] = typeof v === "string" ?
|
|
18657
|
+
for (const [k, v] of Object.entries(r)) out[k] = typeof v === "string" ? maskText2(v) : v;
|
|
17801
18658
|
if (dests.length > 0) {
|
|
17802
18659
|
out.exposureDestinations = dests.map(maskDestination);
|
|
17803
18660
|
out.exposureDestinationHashes = dests.map((d) => isMaskedKind(d) ? hashDestination(d) : "");
|
|
@@ -18100,7 +18957,20 @@ function ecoDir(eco) {
|
|
|
18100
18957
|
return path24.join(osvRoot(), eco);
|
|
18101
18958
|
}
|
|
18102
18959
|
function indexKey(eco, name) {
|
|
18103
|
-
|
|
18960
|
+
switch (eco) {
|
|
18961
|
+
case "PyPI":
|
|
18962
|
+
return normalizePyPiName(name);
|
|
18963
|
+
case "crates.io":
|
|
18964
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
18965
|
+
case "Go":
|
|
18966
|
+
case "Maven":
|
|
18967
|
+
return name;
|
|
18968
|
+
default:
|
|
18969
|
+
return name.toLowerCase();
|
|
18970
|
+
}
|
|
18971
|
+
}
|
|
18972
|
+
function recordEcosystemMatches(eco, recordEco) {
|
|
18973
|
+
return typeof recordEco === "string" && (recordEco === eco || recordEco.startsWith(`${eco}:`));
|
|
18104
18974
|
}
|
|
18105
18975
|
function shardOf(key) {
|
|
18106
18976
|
return createHash5("sha1").update(key).digest("hex").slice(0, 2);
|
|
@@ -18146,7 +19016,8 @@ function entriesFromRecord(eco, record2) {
|
|
|
18146
19016
|
const modifiedMs = typeof r.modified === "string" ? Date.parse(r.modified) || 0 : 0;
|
|
18147
19017
|
const rows = /* @__PURE__ */ new Map();
|
|
18148
19018
|
for (const a of r.affected ?? []) {
|
|
18149
|
-
if (a?.package?.ecosystem
|
|
19019
|
+
if (!recordEcosystemMatches(eco, a?.package?.ecosystem) || typeof a?.package?.name !== "string")
|
|
19020
|
+
continue;
|
|
18150
19021
|
const key = indexKey(eco, a.package.name);
|
|
18151
19022
|
const entry = rows.get(key) ?? { id: r.id };
|
|
18152
19023
|
if (Array.isArray(a.versions)) {
|
|
@@ -18386,15 +19257,18 @@ async function pypiInfo(name, version2) {
|
|
|
18386
19257
|
};
|
|
18387
19258
|
}
|
|
18388
19259
|
async function registryInfo(eco, name, version2, freshWindowMs, raw) {
|
|
18389
|
-
|
|
19260
|
+
if (eco === "npm") return npmInfo(name, version2, freshWindowMs, raw);
|
|
19261
|
+
if (eco === "PyPI") return pypiInfo(name, version2);
|
|
19262
|
+
return null;
|
|
18390
19263
|
}
|
|
18391
|
-
var TIMEOUT_MS2, MAX_BYTES;
|
|
19264
|
+
var TIMEOUT_MS2, MAX_BYTES, HAS_REGISTRY_CLIENT;
|
|
18392
19265
|
var init_registry2 = __esm({
|
|
18393
19266
|
"src/supply-chain/registry.ts"() {
|
|
18394
19267
|
"use strict";
|
|
18395
19268
|
init_net();
|
|
18396
19269
|
TIMEOUT_MS2 = 1500;
|
|
18397
19270
|
MAX_BYTES = 2 * 1024 * 1024;
|
|
19271
|
+
HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
18398
19272
|
}
|
|
18399
19273
|
});
|
|
18400
19274
|
|
|
@@ -18441,25 +19315,39 @@ function commandOf(toolName, args) {
|
|
|
18441
19315
|
return typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : null;
|
|
18442
19316
|
}
|
|
18443
19317
|
function label(p, version2) {
|
|
18444
|
-
return `${p.ecosystem
|
|
19318
|
+
return `${KIND[p.ecosystem]} "${p.name}${version2 ? `@${version2}` : ""}"`;
|
|
19319
|
+
}
|
|
19320
|
+
function osvNames(p) {
|
|
19321
|
+
if (p.ecosystem !== "Go") return [p.name];
|
|
19322
|
+
const parts = p.name.split("/");
|
|
19323
|
+
const out = [];
|
|
19324
|
+
for (let n = parts.length; n >= 2 && out.length < 6; n--) out.push(parts.slice(0, n).join("/"));
|
|
19325
|
+
return out;
|
|
18445
19326
|
}
|
|
18446
19327
|
async function maliciousFor(p, version2, cfg, misses, networkCapped) {
|
|
18447
|
-
const
|
|
18448
|
-
const
|
|
18449
|
-
|
|
19328
|
+
const names = osvNames(p);
|
|
19329
|
+
const local = names.map((n) => lookupIndex(p.ecosystem, n));
|
|
19330
|
+
const hits2 = local.flatMap((l) => l.status === "hit" ? l.entries : []);
|
|
19331
|
+
const entries = hits2.length > 0 ? hits2 : null;
|
|
19332
|
+
const freshClean = local.every((l) => l.status === "clean" && l.fresh);
|
|
19333
|
+
const status = local.some((l) => l.status === "unavailable") ? "unavailable" : local[0].status;
|
|
19334
|
+
if (!entries && !freshClean) {
|
|
18450
19335
|
if (!cfg.onlineFallback || networkCapped) {
|
|
18451
19336
|
misses.push(
|
|
18452
|
-
`${label(p, version2)}: local index ${
|
|
19337
|
+
`${label(p, version2)}: local index ${status}, no online lookup (${networkCapped ? "over the per-command network cap" : "online fallback off"})`
|
|
18453
19338
|
);
|
|
18454
19339
|
return null;
|
|
18455
19340
|
}
|
|
18456
|
-
let
|
|
19341
|
+
let perName = null;
|
|
18457
19342
|
try {
|
|
18458
|
-
|
|
19343
|
+
perName = await queryOsvMalicious(
|
|
19344
|
+
names.map((name) => ({ ecosystem: p.ecosystem, name, version: version2 }))
|
|
19345
|
+
);
|
|
18459
19346
|
} catch (err2) {
|
|
18460
19347
|
misses.push(`${label(p, version2)}: OSV online ${err2.message}`);
|
|
18461
19348
|
return null;
|
|
18462
19349
|
}
|
|
19350
|
+
const ids = perName ? [[...new Set(perName.flat())]] : null;
|
|
18463
19351
|
if (!ids) {
|
|
18464
19352
|
misses.push(`${label(p, version2)}: OSV online unavailable`);
|
|
18465
19353
|
return null;
|
|
@@ -18529,7 +19417,7 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
18529
19417
|
}
|
|
18530
19418
|
const findings = [];
|
|
18531
19419
|
let info = null;
|
|
18532
|
-
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
19420
|
+
const wantsRegistry = (cfg.newPackage !== "off" || cfg.installScript !== "off") && HAS_REGISTRY_CLIENT.has(p.ecosystem);
|
|
18533
19421
|
if (wantsRegistry && !networkCapped) {
|
|
18534
19422
|
try {
|
|
18535
19423
|
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
@@ -18609,7 +19497,7 @@ async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
|
18609
19497
|
return { ...ALLOW, misses: [`package check error: ${err2?.message ?? err2}`] };
|
|
18610
19498
|
}
|
|
18611
19499
|
}
|
|
18612
|
-
var MAX_NETWORK_PACKAGES, ALLOW;
|
|
19500
|
+
var MAX_NETWORK_PACKAGES, ALLOW, KIND;
|
|
18613
19501
|
var init_check = __esm({
|
|
18614
19502
|
"src/supply-chain/check.ts"() {
|
|
18615
19503
|
"use strict";
|
|
@@ -18620,6 +19508,164 @@ var init_check = __esm({
|
|
|
18620
19508
|
init_local_resolve();
|
|
18621
19509
|
MAX_NETWORK_PACKAGES = 10;
|
|
18622
19510
|
ALLOW = { verdict: "allow", findings: [], misses: [], checked: 0 };
|
|
19511
|
+
KIND = {
|
|
19512
|
+
npm: "npm package",
|
|
19513
|
+
PyPI: "PyPI package",
|
|
19514
|
+
RubyGems: "RubyGems package",
|
|
19515
|
+
"crates.io": "crates.io package",
|
|
19516
|
+
Go: "Go module",
|
|
19517
|
+
NuGet: "NuGet package",
|
|
19518
|
+
Maven: "Maven artifact",
|
|
19519
|
+
Packagist: "Packagist package",
|
|
19520
|
+
VSCode: "VS Code extension"
|
|
19521
|
+
};
|
|
19522
|
+
}
|
|
19523
|
+
});
|
|
19524
|
+
|
|
19525
|
+
// src/policy/negotiation.ts
|
|
19526
|
+
function buildNegotiationMessage(blockedByLabel, isHumanDecision2, humanReason, recoveryCommand, context) {
|
|
19527
|
+
if (isHumanDecision2) {
|
|
19528
|
+
return `NODE9: The human user rejected this action.
|
|
19529
|
+
REASON: ${humanReason || "No specific reason provided."}
|
|
19530
|
+
INSTRUCTIONS:
|
|
19531
|
+
- Do NOT retry this exact command.
|
|
19532
|
+
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
19533
|
+
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
19534
|
+
}
|
|
19535
|
+
if (context?.kind === "protected-path") {
|
|
19536
|
+
const subject = context.path ? `${context.path} is a protected file` : "This file is protected";
|
|
19537
|
+
return `NODE9: Blocked. ${subject}, and node9 stopped this call to it. The call did not run, so nothing was exposed.
|
|
19538
|
+
INSTRUCTIONS:
|
|
19539
|
+
- Do NOT retry, and do NOT reach the file another way (cat, grep, a copy, a script, a redirect).
|
|
19540
|
+
- If this step needs that file, ask the user to provide what you need or to do the step themselves.
|
|
19541
|
+
- Tell the user node9 protected this file, then ask how to proceed.`;
|
|
19542
|
+
}
|
|
19543
|
+
const label3 = blockedByLabel.toLowerCase();
|
|
19544
|
+
if (label3.includes("dlp") || label3.includes("secret detected") || label3.includes("credential review")) {
|
|
19545
|
+
return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
|
|
19546
|
+
CRITICAL INSTRUCTION: Do NOT retry this action.
|
|
19547
|
+
REQUIRED ACTIONS:
|
|
19548
|
+
1. Remove the hardcoded credential from your command or code.
|
|
19549
|
+
2. Use an environment variable or a dedicated secrets manager instead.
|
|
19550
|
+
3. Treat the leaked credential as compromised and rotate it immediately.
|
|
19551
|
+
Do NOT attempt to bypass this check or pass the credential through another tool.
|
|
19552
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19553
|
+
}
|
|
19554
|
+
if (label3.includes("package check")) {
|
|
19555
|
+
return `NODE9: Blocked \u2014 ${humanReason || "a package in this command is known to be malicious."}
|
|
19556
|
+
INSTRUCTIONS:
|
|
19557
|
+
- Do NOT install this package, and do NOT fetch it another way (a different package manager, a tarball URL, a git URL, a mirror).
|
|
19558
|
+
- If a dependency is needed, suggest a well-known alternative and let the user choose.
|
|
19559
|
+
- Tell the user node9 blocked this install and name the advisory, then ask how to proceed.`;
|
|
19560
|
+
}
|
|
19561
|
+
if (label3.includes("sql safety") && label3.includes("delete without where")) {
|
|
19562
|
+
return `NODE9: Blocked \u2014 DELETE without WHERE clause would wipe the entire table.
|
|
19563
|
+
INSTRUCTION: Add a WHERE clause to scope the deletion (e.g. WHERE id = <value>).
|
|
19564
|
+
Do NOT retry without a WHERE clause.
|
|
19565
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19566
|
+
}
|
|
19567
|
+
if (label3.includes("sql safety") && label3.includes("update without where")) {
|
|
19568
|
+
return `NODE9: Blocked \u2014 UPDATE without WHERE clause would update every row.
|
|
19569
|
+
INSTRUCTION: Add a WHERE clause to scope the update (e.g. WHERE id = <value>).
|
|
19570
|
+
Do NOT retry without a WHERE clause.
|
|
19571
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19572
|
+
}
|
|
19573
|
+
if (label3.includes("dangerous word")) {
|
|
19574
|
+
const match = blockedByLabel.match(/dangerous word: "([^"]+)"/i);
|
|
19575
|
+
const word = match?.[1] ?? "a dangerous keyword";
|
|
19576
|
+
return `NODE9: Blocked \u2014 command contains forbidden keyword "${word}".
|
|
19577
|
+
INSTRUCTION: Do NOT use "${word}". Use a non-destructive alternative.
|
|
19578
|
+
Do NOT attempt to bypass this with shell tricks or aliases \u2014 it will be blocked again.
|
|
19579
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19580
|
+
}
|
|
19581
|
+
if (label3.includes("path blocked") || label3.includes("sandbox")) {
|
|
19582
|
+
return `NODE9: Blocked \u2014 operation targets a path outside the allowed sandbox.
|
|
19583
|
+
INSTRUCTION: Move your output to an allowed directory such as /tmp/ or the project directory.
|
|
19584
|
+
Do NOT retry on the same path.
|
|
19585
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19586
|
+
}
|
|
19587
|
+
if (label3.includes("inline execution")) {
|
|
19588
|
+
return `NODE9: Blocked \u2014 inline code execution (e.g. bash -c "...") is not allowed.
|
|
19589
|
+
INSTRUCTION: Use individual tool calls instead of embedding code in a shell string.
|
|
19590
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19591
|
+
}
|
|
19592
|
+
if (label3.includes("strict mode")) {
|
|
19593
|
+
return `NODE9: Blocked \u2014 strict mode is active. All tool calls require explicit human approval.
|
|
19594
|
+
INSTRUCTION: Inform the user this action is pending approval. Wait for them to approve via the dashboard or run "node9 pause".`;
|
|
19595
|
+
}
|
|
19596
|
+
if (label3.includes("rule") && label3.includes("default block")) {
|
|
19597
|
+
const match = blockedByLabel.match(/rule "([^"]+)"/i);
|
|
19598
|
+
const rule = match?.[1] ?? "a policy rule";
|
|
19599
|
+
return `NODE9: Blocked \u2014 action "${rule}" is forbidden by security policy.
|
|
19600
|
+
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
19601
|
+
Do NOT attempt to bypass this rule.
|
|
19602
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19603
|
+
}
|
|
19604
|
+
const recovery = recoveryCommand ? `
|
|
19605
|
+
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
19606
|
+
return `NODE9: Action blocked by security policy [${agentFacingLabel(blockedByLabel)}].
|
|
19607
|
+
INSTRUCTIONS:
|
|
19608
|
+
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
19609
|
+
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
19610
|
+
}
|
|
19611
|
+
function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
19612
|
+
const why = ruleDescription || (reason && reason !== blockedByLabel ? reason : void 0) || blockedByLabel || "this action needs your review";
|
|
19613
|
+
const trimmed = why.trimEnd();
|
|
19614
|
+
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
19615
|
+
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
19616
|
+
}
|
|
19617
|
+
function isHumanDecision(label3, blockedBy) {
|
|
19618
|
+
if (blockedBy !== void 0 && blockedBy !== "local-decision") return false;
|
|
19619
|
+
const l = label3.toLowerCase();
|
|
19620
|
+
return l.includes("user") || l.includes("daemon") || l.includes("decision");
|
|
19621
|
+
}
|
|
19622
|
+
function agentFacingLabel(label3) {
|
|
19623
|
+
const plain = label3.replace(/[\p{Extended_Pictographic}\u200D\uFE0F]/gu, "").replace(/\bNode9\b/g, "node9").replace(/\s+/g, " ").trim();
|
|
19624
|
+
return plain || "Security Policy";
|
|
19625
|
+
}
|
|
19626
|
+
function asApprovalRequest(description) {
|
|
19627
|
+
const is = description.lastIndexOf(BLOCKED_BY);
|
|
19628
|
+
if (is > 0) {
|
|
19629
|
+
const by = description.slice(is + BLOCKED_BY.length).replace(/\.$/, "");
|
|
19630
|
+
if (by.endsWith(SHIELD)) return `${description.slice(0, is)} needs your approval (${by})`;
|
|
19631
|
+
}
|
|
19632
|
+
const dash = description.lastIndexOf(DASH_BLOCKED_BY);
|
|
19633
|
+
if (dash > 0) {
|
|
19634
|
+
const by = description.slice(dash + DASH_BLOCKED_BY.length).replace(/\.$/, "");
|
|
19635
|
+
if (by.endsWith(SHIELD)) return `${description.slice(0, dash)}. Needs your approval (${by})`;
|
|
19636
|
+
}
|
|
19637
|
+
return description;
|
|
19638
|
+
}
|
|
19639
|
+
var BLOCKED_BY, DASH_BLOCKED_BY, SHIELD;
|
|
19640
|
+
var init_negotiation = __esm({
|
|
19641
|
+
"src/policy/negotiation.ts"() {
|
|
19642
|
+
"use strict";
|
|
19643
|
+
BLOCKED_BY = " is blocked by ";
|
|
19644
|
+
DASH_BLOCKED_BY = " \u2014 blocked by ";
|
|
19645
|
+
SHIELD = " shield";
|
|
19646
|
+
}
|
|
19647
|
+
});
|
|
19648
|
+
|
|
19649
|
+
// src/auth/decision-sources.ts
|
|
19650
|
+
function backgroundPassSource(result) {
|
|
19651
|
+
if (result.checkedBy === "cloud" || result.blockedBy === "team-policy") return "cloud";
|
|
19652
|
+
if (result.blockedBy === "timeout") return "timeout";
|
|
19653
|
+
return "policy";
|
|
19654
|
+
}
|
|
19655
|
+
var HUMAN_DECISION_SOURCES, REDIRECT_SOURCE, DECISION_ROUTE_SOURCES;
|
|
19656
|
+
var init_decision_sources = __esm({
|
|
19657
|
+
"src/auth/decision-sources.ts"() {
|
|
19658
|
+
"use strict";
|
|
19659
|
+
HUMAN_DECISION_SOURCES = /* @__PURE__ */ new Set([
|
|
19660
|
+
"terminal",
|
|
19661
|
+
"browser",
|
|
19662
|
+
"native"
|
|
19663
|
+
]);
|
|
19664
|
+
REDIRECT_SOURCE = "terminal-redirect";
|
|
19665
|
+
DECISION_ROUTE_SOURCES = /* @__PURE__ */ new Set([
|
|
19666
|
+
...HUMAN_DECISION_SOURCES,
|
|
19667
|
+
REDIRECT_SOURCE
|
|
19668
|
+
]);
|
|
18623
19669
|
}
|
|
18624
19670
|
});
|
|
18625
19671
|
|
|
@@ -18739,6 +19785,10 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
18739
19785
|
}
|
|
18740
19786
|
return _authorizeHeadlessCore(toolName, args, meta, options);
|
|
18741
19787
|
}
|
|
19788
|
+
function daemonWaitCapMs(approvalTimeoutMs) {
|
|
19789
|
+
if (approvalTimeoutMs === 0) return 0;
|
|
19790
|
+
return Math.max(approvalTimeoutMs ?? 0, 12e4) + 5e3;
|
|
19791
|
+
}
|
|
18742
19792
|
function approvalTimeoutReason(approvalTimeoutMs) {
|
|
18743
19793
|
return `No human response within ${approvalTimeoutMs / 1e3}s \u2014 auto-denied by timeout policy. Approve from your phone next time: run \`node9 login\`.`;
|
|
18744
19794
|
}
|
|
@@ -18774,6 +19824,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18774
19824
|
let statefulRecoveryCommand;
|
|
18775
19825
|
let localSmartRuleMatched = false;
|
|
18776
19826
|
let hardBlockDowngraded = false;
|
|
19827
|
+
let downgradedBlock = null;
|
|
18777
19828
|
let argScan;
|
|
18778
19829
|
const argumentScan = () => argScan ??= scanArgumentSecrets(args);
|
|
18779
19830
|
let fileAccess;
|
|
@@ -19393,6 +20444,16 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19393
20444
|
humanApproverReachable,
|
|
19394
20445
|
overridable: policyResult.overridable
|
|
19395
20446
|
});
|
|
20447
|
+
const blocked = {
|
|
20448
|
+
approved: false,
|
|
20449
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
20450
|
+
blockedBy: "local-config",
|
|
20451
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
20452
|
+
ruleHit: policyResult.ruleName,
|
|
20453
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
20454
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
|
|
20455
|
+
...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
|
|
20456
|
+
};
|
|
19396
20457
|
const hardBlock = () => {
|
|
19397
20458
|
if (!isManual)
|
|
19398
20459
|
appendLocalAudit(
|
|
@@ -19403,17 +20464,9 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19403
20464
|
{ ...meta, ruleName: policyResult.ruleName },
|
|
19404
20465
|
hashAuditArgs
|
|
19405
20466
|
);
|
|
19406
|
-
return
|
|
19407
|
-
approved: false,
|
|
19408
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
19409
|
-
blockedBy: "local-config",
|
|
19410
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
19411
|
-
ruleHit: policyResult.ruleName,
|
|
19412
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
19413
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
|
|
19414
|
-
...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
|
|
19415
|
-
};
|
|
20467
|
+
return blocked;
|
|
19416
20468
|
};
|
|
20469
|
+
downgradedBlock = blocked;
|
|
19417
20470
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
19418
20471
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
19419
20472
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -19470,7 +20523,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19470
20523
|
policyMatchedWord,
|
|
19471
20524
|
policyResult.ruleName
|
|
19472
20525
|
);
|
|
19473
|
-
if (policyRuleDescription)
|
|
20526
|
+
if (policyRuleDescription)
|
|
20527
|
+
riskMetadata.ruleDescription = (hardBlockDowngraded ? asApprovalRequest(policyRuleDescription) : policyRuleDescription).slice(0, 200);
|
|
19474
20528
|
const persistent = policyResult.ruleName || policyResult.tier === 7 || hardBlockDowngraded ? null : getPersistentDecision(toolName);
|
|
19475
20529
|
if (persistent === "allow" && !callSpecificReview()) {
|
|
19476
20530
|
if (!isManual)
|
|
@@ -19634,7 +20688,7 @@ ${packageReview}`;
|
|
|
19634
20688
|
}
|
|
19635
20689
|
}
|
|
19636
20690
|
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
19637
|
-
if (!
|
|
20691
|
+
if (!reviewDemanded()) explainableLabel = "Organization Policy (SaaS)";
|
|
19638
20692
|
} catch {
|
|
19639
20693
|
}
|
|
19640
20694
|
}
|
|
@@ -19776,10 +20830,54 @@ ${packageReview}`;
|
|
|
19776
20830
|
decision: daemonDecision,
|
|
19777
20831
|
source: decisionSource,
|
|
19778
20832
|
reason: daemonReason
|
|
19779
|
-
} = await waitForDaemonDecision(
|
|
20833
|
+
} = await waitForDaemonDecision(
|
|
20834
|
+
daemonEntryId,
|
|
20835
|
+
signal,
|
|
20836
|
+
daemonWaitCapMs(config.settings.approvalTimeoutMs)
|
|
20837
|
+
);
|
|
19780
20838
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
19781
20839
|
const isApproved = daemonDecision === "allow";
|
|
19782
|
-
const isRedirect = decisionSource ===
|
|
20840
|
+
const isRedirect = decisionSource === REDIRECT_SOURCE;
|
|
20841
|
+
if (!isApproved && !isRedirect && !(decisionSource && HUMAN_DECISION_SOURCES.has(decisionSource))) {
|
|
20842
|
+
if (decisionSource === "cloud")
|
|
20843
|
+
return {
|
|
20844
|
+
approved: false,
|
|
20845
|
+
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
20846
|
+
blockedBy: "team-policy",
|
|
20847
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
20848
|
+
decisionSource: "cloud"
|
|
20849
|
+
};
|
|
20850
|
+
const timedOut = decisionSource === "timeout";
|
|
20851
|
+
if (downgradedBlock)
|
|
20852
|
+
return {
|
|
20853
|
+
...downgradedBlock,
|
|
20854
|
+
blockedBy: timedOut ? "timeout" : "local-config"
|
|
20855
|
+
};
|
|
20856
|
+
if (timedOut)
|
|
20857
|
+
return {
|
|
20858
|
+
approved: false,
|
|
20859
|
+
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
20860
|
+
blockedBy: "timeout",
|
|
20861
|
+
blockedByLabel: "Approval Timeout"
|
|
20862
|
+
};
|
|
20863
|
+
if (decisionSource === "policy")
|
|
20864
|
+
return {
|
|
20865
|
+
approved: false,
|
|
20866
|
+
reason: daemonReason || "Action blocked by policy.",
|
|
20867
|
+
blockedBy: "local-config",
|
|
20868
|
+
blockedByLabel: explainableLabel
|
|
20869
|
+
};
|
|
20870
|
+
return {
|
|
20871
|
+
approved: false,
|
|
20872
|
+
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
20873
|
+
blockedBy: "no-approval-mechanism",
|
|
20874
|
+
blockedByLabel: "Approval Unavailable"
|
|
20875
|
+
};
|
|
20876
|
+
}
|
|
20877
|
+
if (isApproved && decisionSource === "cloud")
|
|
20878
|
+
return { approved: true, checkedBy: "cloud", decisionSource: "cloud" };
|
|
20879
|
+
if (isApproved && decisionSource === "policy")
|
|
20880
|
+
return { approved: true, checkedBy: "local-policy" };
|
|
19783
20881
|
const via = "Terminal (node9 tail)";
|
|
19784
20882
|
return {
|
|
19785
20883
|
approved: isApproved,
|
|
@@ -19937,6 +21035,8 @@ var init_orchestrator = __esm({
|
|
|
19937
21035
|
init_shields();
|
|
19938
21036
|
init_jail();
|
|
19939
21037
|
init_safe_text();
|
|
21038
|
+
init_negotiation();
|
|
21039
|
+
init_decision_sources();
|
|
19940
21040
|
SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
19941
21041
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
19942
21042
|
"write",
|
|
@@ -30939,23 +32039,22 @@ function checkApprovalConfig(ctx) {
|
|
|
30939
32039
|
const s = getConfig(ctx.cwd).settings;
|
|
30940
32040
|
const a = s.approvers ?? { native: false, browser: false, cloud: false, terminal: false };
|
|
30941
32041
|
const hasChannel = a.native || a.browser || a.cloud || a.terminal || s.reviewChannel === "ask";
|
|
30942
|
-
|
|
30943
|
-
|
|
30944
|
-
|
|
30945
|
-
|
|
30946
|
-
|
|
30947
|
-
|
|
30948
|
-
|
|
30949
|
-
|
|
30950
|
-
|
|
30951
|
-
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
];
|
|
30956
|
-
}
|
|
32042
|
+
const zeroTimeout = s.approvalTimeoutMs === 0 ? [
|
|
32043
|
+
{
|
|
32044
|
+
category: "Approvals",
|
|
32045
|
+
severity: "medium",
|
|
32046
|
+
title: "Approval timeout is 0: node9 never times a review out",
|
|
32047
|
+
what: "A review waits for a person; node9's own timer is off.",
|
|
32048
|
+
why: "`settings.approvalTimeoutMs` is 0.",
|
|
32049
|
+
who: "With no one at the screen, the agent's hook timeout ends the review, and some agents then run the tool.",
|
|
32050
|
+
detail: [],
|
|
32051
|
+
owner: "node9",
|
|
32052
|
+
fix: "Set a real approval timeout (e.g. 300000 ms) or clear it to use the default."
|
|
32053
|
+
}
|
|
32054
|
+
] : [];
|
|
30957
32055
|
if (!hasChannel) {
|
|
30958
32056
|
return [
|
|
32057
|
+
...zeroTimeout,
|
|
30959
32058
|
{
|
|
30960
32059
|
category: "Approvals",
|
|
30961
32060
|
severity: "high",
|
|
@@ -30969,7 +32068,7 @@ function checkApprovalConfig(ctx) {
|
|
|
30969
32068
|
}
|
|
30970
32069
|
];
|
|
30971
32070
|
}
|
|
30972
|
-
return
|
|
32071
|
+
return zeroTimeout;
|
|
30973
32072
|
}
|
|
30974
32073
|
function checkToolGovernance(ctx) {
|
|
30975
32074
|
const cfg = getConfig(ctx.cwd);
|
|
@@ -33651,7 +34750,17 @@ var init_osv_sync = __esm({
|
|
|
33651
34750
|
init_zip();
|
|
33652
34751
|
init_osv_index();
|
|
33653
34752
|
init_net();
|
|
33654
|
-
ECOSYSTEMS2 = [
|
|
34753
|
+
ECOSYSTEMS2 = [
|
|
34754
|
+
"npm",
|
|
34755
|
+
"PyPI",
|
|
34756
|
+
"RubyGems",
|
|
34757
|
+
"crates.io",
|
|
34758
|
+
"Go",
|
|
34759
|
+
"NuGet",
|
|
34760
|
+
"Maven",
|
|
34761
|
+
"Packagist",
|
|
34762
|
+
"VSCode"
|
|
34763
|
+
];
|
|
33655
34764
|
SYNC_INTERVAL_MS2 = 6 * 60 * 60 * 1e3;
|
|
33656
34765
|
FIRST_SYNC_DELAY_MS = 2 * 60 * 1e3;
|
|
33657
34766
|
FULL_REBUILD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -34211,6 +35320,15 @@ function buildDaemonReport(allEntries, period, now) {
|
|
|
34211
35320
|
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
34212
35321
|
};
|
|
34213
35322
|
}
|
|
35323
|
+
function autoDenyMs() {
|
|
35324
|
+
const ms = getConfig().settings.approvalTimeoutMs;
|
|
35325
|
+
if (ms === 0) return null;
|
|
35326
|
+
return ms ?? AUTO_DENY_MS;
|
|
35327
|
+
}
|
|
35328
|
+
function armAutoDeny(fire) {
|
|
35329
|
+
const ms = autoDenyMs();
|
|
35330
|
+
return ms === null ? void 0 : setTimeout(fire, ms);
|
|
35331
|
+
}
|
|
34214
35332
|
function startDaemon() {
|
|
34215
35333
|
try {
|
|
34216
35334
|
autoMigrateLocalConfig();
|
|
@@ -34321,7 +35439,7 @@ data: ${JSON.stringify({
|
|
|
34321
35439
|
allowCount: (insightCounts.get(e.toolName) ?? 0) + 1
|
|
34322
35440
|
})),
|
|
34323
35441
|
orgName: getOrgName(),
|
|
34324
|
-
autoDenyMs:
|
|
35442
|
+
autoDenyMs: autoDenyMs()
|
|
34325
35443
|
})}
|
|
34326
35444
|
|
|
34327
35445
|
`
|
|
@@ -34384,7 +35502,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34384
35502
|
timestamp: Date.now(),
|
|
34385
35503
|
earlyDecision: null,
|
|
34386
35504
|
waiter: null,
|
|
34387
|
-
timer:
|
|
35505
|
+
timer: armAutoDeny(() => {
|
|
34388
35506
|
if (pending.has(id)) {
|
|
34389
35507
|
const e = pending.get(id);
|
|
34390
35508
|
appendAuditLog({
|
|
@@ -34392,6 +35510,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34392
35510
|
args: e.args,
|
|
34393
35511
|
decision: "auto-deny"
|
|
34394
35512
|
});
|
|
35513
|
+
e.decisionSource = "timeout";
|
|
34395
35514
|
if (e.waiter) e.waiter("deny", "No response \u2014 auto-denied after timeout");
|
|
34396
35515
|
else {
|
|
34397
35516
|
e.earlyDecision = "deny";
|
|
@@ -34400,7 +35519,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34400
35519
|
pending.delete(id);
|
|
34401
35520
|
broadcast("remove", { id, decision: "deny" });
|
|
34402
35521
|
}
|
|
34403
|
-
}
|
|
35522
|
+
})
|
|
34404
35523
|
};
|
|
34405
35524
|
pending.set(id, entry);
|
|
34406
35525
|
if (!fromCLI) {
|
|
@@ -34462,6 +35581,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34462
35581
|
clearTimeout(e.timer);
|
|
34463
35582
|
const decision = result.approved ? "allow" : "deny";
|
|
34464
35583
|
appendAuditLog({ toolName: e.toolName, args: e.args, decision });
|
|
35584
|
+
e.decisionSource = backgroundPassSource(result);
|
|
34465
35585
|
if (e.waiter) {
|
|
34466
35586
|
e.waiter(decision, result.reason);
|
|
34467
35587
|
pending.delete(id);
|
|
@@ -34576,8 +35696,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34576
35696
|
insightCounts.delete(entry.toolName);
|
|
34577
35697
|
saveInsightCounts();
|
|
34578
35698
|
}
|
|
34579
|
-
|
|
34580
|
-
if (source && VALID_SOURCES.has(source)) entry.decisionSource = source;
|
|
35699
|
+
if (source && DECISION_ROUTE_SOURCES.has(source)) entry.decisionSource = source;
|
|
34581
35700
|
if (entry.waiter) {
|
|
34582
35701
|
entry.waiter(resolvedDecision, reason);
|
|
34583
35702
|
pending.delete(id);
|
|
@@ -34715,8 +35834,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34715
35834
|
insightCounts.delete(entry.toolName);
|
|
34716
35835
|
saveInsightCounts();
|
|
34717
35836
|
}
|
|
34718
|
-
|
|
34719
|
-
if (source && VALID_RESOLVE_SOURCES.has(source)) entry.decisionSource = source;
|
|
35837
|
+
if (source && HUMAN_DECISION_SOURCES.has(source)) entry.decisionSource = source;
|
|
34720
35838
|
if (entry.waiter) entry.waiter(resolvedResolveDecision);
|
|
34721
35839
|
else entry.earlyDecision = resolvedResolveDecision;
|
|
34722
35840
|
pending.delete(id);
|
|
@@ -35374,6 +36492,7 @@ var init_server = __esm({
|
|
|
35374
36492
|
"use strict";
|
|
35375
36493
|
init_migrate();
|
|
35376
36494
|
init_trusted_recipients();
|
|
36495
|
+
init_decision_sources();
|
|
35377
36496
|
init_core();
|
|
35378
36497
|
init_keyed_guard();
|
|
35379
36498
|
init_scan();
|
|
@@ -35632,14 +36751,14 @@ async function onboardMachine(apiKey, opts = {}) {
|
|
|
35632
36751
|
detail: `${sync.rules} cloud rule${sync.rules === 1 ? "" : "s"} active`
|
|
35633
36752
|
} : { name: "policy-sync", ok: false, detail: sync.reason }
|
|
35634
36753
|
);
|
|
35635
|
-
const
|
|
36754
|
+
const push2 = await runPolicyPush();
|
|
35636
36755
|
steps.push(
|
|
35637
|
-
|
|
36756
|
+
push2.ok ? { name: "register", ok: true, detail: "machine visible in the dashboard" } : { name: "register", ok: false, detail: push2.reason }
|
|
35638
36757
|
);
|
|
35639
36758
|
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
35640
36759
|
let detail = healed === "repaired" ? "autostart re-enabled (survives reboot)" : isDaemonRunning() ? "running" : healed === "unsupported" ? "no background service on this platform: starts on agent activity" : "starts on agent activity (make it survive reboots: node9 daemon install)";
|
|
35641
36760
|
let daemonOk = true;
|
|
35642
|
-
if (sync.ok &&
|
|
36761
|
+
if (sync.ok && push2.ok && isInteractive() && mayChangeService({ stdoutTTY: !!process.stdout.isTTY, ci: isCI() }) && autostartState() === "absent") {
|
|
35643
36762
|
try {
|
|
35644
36763
|
if (await confirm2({
|
|
35645
36764
|
message: "Run node9 in the background when this computer starts?",
|
|
@@ -63906,108 +65025,14 @@ function parseDuration(str2) {
|
|
|
63906
65025
|
|
|
63907
65026
|
// src/proxy/index.ts
|
|
63908
65027
|
init_orchestrator();
|
|
65028
|
+
init_negotiation();
|
|
65029
|
+
init_platform_shell();
|
|
65030
|
+
init_safe_text();
|
|
63909
65031
|
import readline2 from "readline";
|
|
63910
65032
|
import chalk9 from "chalk";
|
|
63911
65033
|
import { spawn as spawn3 } from "child_process";
|
|
63912
65034
|
import { execa } from "execa";
|
|
63913
65035
|
import { parseCommandString } from "execa";
|
|
63914
|
-
|
|
63915
|
-
// src/policy/negotiation.ts
|
|
63916
|
-
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand, context) {
|
|
63917
|
-
if (isHumanDecision) {
|
|
63918
|
-
return `NODE9: The human user rejected this action.
|
|
63919
|
-
REASON: ${humanReason || "No specific reason provided."}
|
|
63920
|
-
INSTRUCTIONS:
|
|
63921
|
-
- Do NOT retry this exact command.
|
|
63922
|
-
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
63923
|
-
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
63924
|
-
}
|
|
63925
|
-
if (context?.kind === "protected-path") {
|
|
63926
|
-
const subject = context.path ? `${context.path} is a protected file` : "This file is protected";
|
|
63927
|
-
return `NODE9: Blocked. ${subject}, and node9 stopped this call to it. The call did not run, so nothing was exposed.
|
|
63928
|
-
INSTRUCTIONS:
|
|
63929
|
-
- Do NOT retry, and do NOT reach the file another way (cat, grep, a copy, a script, a redirect).
|
|
63930
|
-
- If this step needs that file, ask the user to provide what you need or to do the step themselves.
|
|
63931
|
-
- Tell the user node9 protected this file, then ask how to proceed.`;
|
|
63932
|
-
}
|
|
63933
|
-
const label3 = blockedByLabel.toLowerCase();
|
|
63934
|
-
if (label3.includes("dlp") || label3.includes("secret detected") || label3.includes("credential review")) {
|
|
63935
|
-
return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
|
|
63936
|
-
CRITICAL INSTRUCTION: Do NOT retry this action.
|
|
63937
|
-
REQUIRED ACTIONS:
|
|
63938
|
-
1. Remove the hardcoded credential from your command or code.
|
|
63939
|
-
2. Use an environment variable or a dedicated secrets manager instead.
|
|
63940
|
-
3. Treat the leaked credential as compromised and rotate it immediately.
|
|
63941
|
-
Do NOT attempt to bypass this check or pass the credential through another tool.
|
|
63942
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63943
|
-
}
|
|
63944
|
-
if (label3.includes("package check")) {
|
|
63945
|
-
return `NODE9: Blocked \u2014 ${humanReason || "a package in this command is known to be malicious."}
|
|
63946
|
-
INSTRUCTIONS:
|
|
63947
|
-
- Do NOT install this package, and do NOT fetch it another way (a different package manager, a tarball URL, a git URL, a mirror).
|
|
63948
|
-
- If a dependency is needed, suggest a well-known alternative and let the user choose.
|
|
63949
|
-
- Tell the user node9 blocked this install and name the advisory, then ask how to proceed.`;
|
|
63950
|
-
}
|
|
63951
|
-
if (label3.includes("sql safety") && label3.includes("delete without where")) {
|
|
63952
|
-
return `NODE9: Blocked \u2014 DELETE without WHERE clause would wipe the entire table.
|
|
63953
|
-
INSTRUCTION: Add a WHERE clause to scope the deletion (e.g. WHERE id = <value>).
|
|
63954
|
-
Do NOT retry without a WHERE clause.
|
|
63955
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63956
|
-
}
|
|
63957
|
-
if (label3.includes("sql safety") && label3.includes("update without where")) {
|
|
63958
|
-
return `NODE9: Blocked \u2014 UPDATE without WHERE clause would update every row.
|
|
63959
|
-
INSTRUCTION: Add a WHERE clause to scope the update (e.g. WHERE id = <value>).
|
|
63960
|
-
Do NOT retry without a WHERE clause.
|
|
63961
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63962
|
-
}
|
|
63963
|
-
if (label3.includes("dangerous word")) {
|
|
63964
|
-
const match = blockedByLabel.match(/dangerous word: "([^"]+)"/i);
|
|
63965
|
-
const word = match?.[1] ?? "a dangerous keyword";
|
|
63966
|
-
return `NODE9: Blocked \u2014 command contains forbidden keyword "${word}".
|
|
63967
|
-
INSTRUCTION: Do NOT use "${word}". Use a non-destructive alternative.
|
|
63968
|
-
Do NOT attempt to bypass this with shell tricks or aliases \u2014 it will be blocked again.
|
|
63969
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63970
|
-
}
|
|
63971
|
-
if (label3.includes("path blocked") || label3.includes("sandbox")) {
|
|
63972
|
-
return `NODE9: Blocked \u2014 operation targets a path outside the allowed sandbox.
|
|
63973
|
-
INSTRUCTION: Move your output to an allowed directory such as /tmp/ or the project directory.
|
|
63974
|
-
Do NOT retry on the same path.
|
|
63975
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63976
|
-
}
|
|
63977
|
-
if (label3.includes("inline execution")) {
|
|
63978
|
-
return `NODE9: Blocked \u2014 inline code execution (e.g. bash -c "...") is not allowed.
|
|
63979
|
-
INSTRUCTION: Use individual tool calls instead of embedding code in a shell string.
|
|
63980
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63981
|
-
}
|
|
63982
|
-
if (label3.includes("strict mode")) {
|
|
63983
|
-
return `NODE9: Blocked \u2014 strict mode is active. All tool calls require explicit human approval.
|
|
63984
|
-
INSTRUCTION: Inform the user this action is pending approval. Wait for them to approve via the dashboard or run "node9 pause".`;
|
|
63985
|
-
}
|
|
63986
|
-
if (label3.includes("rule") && label3.includes("default block")) {
|
|
63987
|
-
const match = blockedByLabel.match(/rule "([^"]+)"/i);
|
|
63988
|
-
const rule = match?.[1] ?? "a policy rule";
|
|
63989
|
-
return `NODE9: Blocked \u2014 action "${rule}" is forbidden by security policy.
|
|
63990
|
-
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
63991
|
-
Do NOT attempt to bypass this rule.
|
|
63992
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63993
|
-
}
|
|
63994
|
-
const recovery = recoveryCommand ? `
|
|
63995
|
-
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
63996
|
-
return `NODE9: Action blocked by security policy [${blockedByLabel}].
|
|
63997
|
-
INSTRUCTIONS:
|
|
63998
|
-
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
63999
|
-
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
64000
|
-
}
|
|
64001
|
-
function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
64002
|
-
const why = ruleDescription || (reason && reason !== blockedByLabel ? reason : void 0) || blockedByLabel || "this action needs your review";
|
|
64003
|
-
const trimmed = why.trimEnd();
|
|
64004
|
-
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
64005
|
-
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
64006
|
-
}
|
|
64007
|
-
|
|
64008
|
-
// src/proxy/index.ts
|
|
64009
|
-
init_platform_shell();
|
|
64010
|
-
init_safe_text();
|
|
64011
65036
|
async function runProxy(targetCommand) {
|
|
64012
65037
|
const commandParts = parseCommandString(targetCommand);
|
|
64013
65038
|
const cmd = commandParts[0];
|
|
@@ -64053,7 +65078,7 @@ async function runProxy(targetCommand) {
|
|
|
64053
65078
|
console.error(chalk9.gray(` Reason: ${result.reason || "Security Policy"}
|
|
64054
65079
|
`));
|
|
64055
65080
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
64056
|
-
const isHuman =
|
|
65081
|
+
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
64057
65082
|
const aiInstruction = buildNegotiationMessage(
|
|
64058
65083
|
blockedByLabel,
|
|
64059
65084
|
isHuman,
|
|
@@ -64184,12 +65209,13 @@ init_daemon();
|
|
|
64184
65209
|
init_dist();
|
|
64185
65210
|
init_startup_log();
|
|
64186
65211
|
init_config();
|
|
65212
|
+
init_negotiation();
|
|
65213
|
+
init_daemon_starter();
|
|
64187
65214
|
import chalk13 from "chalk";
|
|
64188
65215
|
import fs70 from "fs";
|
|
64189
65216
|
import { spawn as spawn6 } from "child_process";
|
|
64190
65217
|
import path67 from "path";
|
|
64191
65218
|
import os59 from "os";
|
|
64192
|
-
init_daemon_starter();
|
|
64193
65219
|
|
|
64194
65220
|
// src/skill-pin.ts
|
|
64195
65221
|
init_read_capped();
|
|
@@ -64734,7 +65760,7 @@ RAW: ${raw}
|
|
|
64734
65760
|
const mcpServer = mcpMatch?.[1];
|
|
64735
65761
|
const sendBlock = (msg, result2) => {
|
|
64736
65762
|
const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || (isKeyedForPolicy() ? "Workspace Policy" : "Local Security Policy");
|
|
64737
|
-
const
|
|
65763
|
+
const humanDecision = isHumanDecision(blockedByContext, result2?.blockedBy);
|
|
64738
65764
|
let ttyFd = null;
|
|
64739
65765
|
try {
|
|
64740
65766
|
ttyFd = fs70.openSync("/dev/tty", "w");
|
|
@@ -64771,7 +65797,7 @@ RAW: ${raw}
|
|
|
64771
65797
|
}
|
|
64772
65798
|
const aiFeedbackMessage = buildNegotiationMessage(
|
|
64773
65799
|
blockedByContext,
|
|
64774
|
-
|
|
65800
|
+
humanDecision,
|
|
64775
65801
|
msg,
|
|
64776
65802
|
result2?.recoveryCommand,
|
|
64777
65803
|
{ kind: result2?.blockKind, path: result2?.blockedPath }
|
|
@@ -65179,7 +66205,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65179
66205
|
let size = 0;
|
|
65180
66206
|
let fields = 0;
|
|
65181
66207
|
let truncated = false;
|
|
65182
|
-
const
|
|
66208
|
+
const push2 = (s) => {
|
|
65183
66209
|
if (s.length === 0) return true;
|
|
65184
66210
|
const room = maxBytes - size;
|
|
65185
66211
|
if (s.length > room) {
|
|
@@ -65197,7 +66223,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65197
66223
|
return true;
|
|
65198
66224
|
};
|
|
65199
66225
|
const walk2 = (v, depth) => {
|
|
65200
|
-
if (typeof v === "string") return
|
|
66226
|
+
if (typeof v === "string") return push2(v);
|
|
65201
66227
|
if (depth >= MAX_DEPTH5 || v === null || typeof v !== "object") return true;
|
|
65202
66228
|
const children = Array.isArray(v) ? v : Object.values(v);
|
|
65203
66229
|
for (const child of children) if (!walk2(child, depth + 1)) return false;
|
|
@@ -68359,17 +69385,18 @@ init_orchestrator();
|
|
|
68359
69385
|
init_config();
|
|
68360
69386
|
init_cloud();
|
|
68361
69387
|
init_config();
|
|
68362
|
-
|
|
68363
|
-
import chalk24 from "chalk";
|
|
68364
|
-
import { spawn as spawn8 } from "child_process";
|
|
68365
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
68366
|
-
import { execa as execa2 } from "execa";
|
|
69388
|
+
init_negotiation();
|
|
68367
69389
|
init_provenance();
|
|
68368
69390
|
init_mcp_pin();
|
|
68369
69391
|
init_mcp_cmd();
|
|
68370
69392
|
init_mcp_tools();
|
|
68371
69393
|
init_daemon();
|
|
68372
69394
|
init_safe_text();
|
|
69395
|
+
import readline3 from "readline";
|
|
69396
|
+
import chalk24 from "chalk";
|
|
69397
|
+
import { spawn as spawn8 } from "child_process";
|
|
69398
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
69399
|
+
import { execa as execa2 } from "execa";
|
|
68373
69400
|
|
|
68374
69401
|
// src/mcp-gateway/result-scan.ts
|
|
68375
69402
|
init_dlp();
|
|
@@ -68733,10 +69760,10 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68733
69760
|
console.error(chalk24.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
68734
69761
|
`));
|
|
68735
69762
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
68736
|
-
const
|
|
69763
|
+
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
68737
69764
|
const aiInstruction = buildNegotiationMessage(
|
|
68738
69765
|
blockedByLabel,
|
|
68739
|
-
|
|
69766
|
+
humanDecision,
|
|
68740
69767
|
result.reason,
|
|
68741
69768
|
void 0,
|
|
68742
69769
|
{ kind: result.blockKind, path: result.blockedPath }
|
|
@@ -75823,7 +76850,7 @@ function addHost(list, host2) {
|
|
|
75823
76850
|
if (!cliGuardPolicyWrite(`egress ${list} ${host2}`)) return false;
|
|
75824
76851
|
return guard(() => addEgressHost(list, host2));
|
|
75825
76852
|
}
|
|
75826
|
-
function
|
|
76853
|
+
function exempt2(address) {
|
|
75827
76854
|
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
75828
76855
|
return guard(() => addSsrfExemption(address));
|
|
75829
76856
|
}
|
|
@@ -76006,7 +77033,7 @@ function registerEgressCommand(program2) {
|
|
|
76006
77033
|
process.exitCode = 1;
|
|
76007
77034
|
return;
|
|
76008
77035
|
}
|
|
76009
|
-
if (!
|
|
77036
|
+
if (!exempt2(a)) return;
|
|
76010
77037
|
console.log(chalk34.green(`
|
|
76011
77038
|
\u2713 ${a} is exempt from the floor.`));
|
|
76012
77039
|
if (!m)
|
|
@@ -77795,17 +78822,18 @@ function registerSessionExposureCommand(program2) {
|
|
|
77795
78822
|
init_osv_index();
|
|
77796
78823
|
init_osv_sync();
|
|
77797
78824
|
import chalk42 from "chalk";
|
|
78825
|
+
var ECO_WIDTH = Math.max(...ECOSYSTEMS2.map((e) => e.length));
|
|
77798
78826
|
function registerPackageIndexCommand(program2) {
|
|
77799
78827
|
const cmd = program2.command("package-index").description("Local OSV malicious-package index used to check installs before they run");
|
|
77800
78828
|
cmd.command("status").description("Show when each ecosystem was last synced and how many records it holds").action(() => {
|
|
77801
78829
|
for (const eco of ECOSYSTEMS2) {
|
|
77802
78830
|
const meta = readMeta(eco);
|
|
77803
78831
|
if (!meta) {
|
|
77804
|
-
console.log(`${eco.padEnd(
|
|
78832
|
+
console.log(`${eco.padEnd(ECO_WIDTH)} ${chalk42.yellow("not synced")}`);
|
|
77805
78833
|
continue;
|
|
77806
78834
|
}
|
|
77807
78835
|
console.log(
|
|
77808
|
-
`${eco.padEnd(
|
|
78836
|
+
`${eco.padEnd(ECO_WIDTH)} ${meta.records} records synced ${meta.syncedAt} (${meta.mode})`
|
|
77809
78837
|
);
|
|
77810
78838
|
}
|
|
77811
78839
|
});
|
|
@@ -77815,9 +78843,9 @@ function registerPackageIndexCommand(program2) {
|
|
|
77815
78843
|
for (const r of results) {
|
|
77816
78844
|
if (r.error) {
|
|
77817
78845
|
failed = true;
|
|
77818
|
-
console.error(`${r.ecosystem.padEnd(
|
|
78846
|
+
console.error(`${r.ecosystem.padEnd(ECO_WIDTH)} ${chalk42.red("failed")}: ${r.error}`);
|
|
77819
78847
|
} else {
|
|
77820
|
-
console.log(`${r.ecosystem.padEnd(
|
|
78848
|
+
console.log(`${r.ecosystem.padEnd(ECO_WIDTH)} ${r.mode}: ${r.records} records`);
|
|
77821
78849
|
}
|
|
77822
78850
|
}
|
|
77823
78851
|
process.exitCode = failed ? 1 : 0;
|