@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.js
CHANGED
|
@@ -1193,6 +1193,120 @@ function expandPlainParam(p) {
|
|
|
1193
1193
|
if (recorded !== void 0) return recorded;
|
|
1194
1194
|
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
1195
1195
|
}
|
|
1196
|
+
function functionBodies(f) {
|
|
1197
|
+
const out = /* @__PURE__ */ new Map();
|
|
1198
|
+
syntax.Walk(f, (node) => {
|
|
1199
|
+
const n = node;
|
|
1200
|
+
if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1201
|
+
const list = out.get(n.Name.Value) ?? [];
|
|
1202
|
+
list.push(n.Body);
|
|
1203
|
+
out.set(n.Name.Value, list);
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
});
|
|
1207
|
+
return out;
|
|
1208
|
+
}
|
|
1209
|
+
function rebindsPositionals(body) {
|
|
1210
|
+
let found = false;
|
|
1211
|
+
syntax.Walk(body, (node) => {
|
|
1212
|
+
const n = node;
|
|
1213
|
+
if (found || !n) return false;
|
|
1214
|
+
if (syntax.NodeType(n) === "CallExpr") {
|
|
1215
|
+
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1216
|
+
if (words[0] === "shift") found = true;
|
|
1217
|
+
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
1218
|
+
found = true;
|
|
1219
|
+
}
|
|
1220
|
+
return !found;
|
|
1221
|
+
});
|
|
1222
|
+
return found;
|
|
1223
|
+
}
|
|
1224
|
+
function tableBindings() {
|
|
1225
|
+
if (!assignmentTable) return "";
|
|
1226
|
+
const out = [];
|
|
1227
|
+
for (const [name, rec] of assignmentTable)
|
|
1228
|
+
if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
|
|
1229
|
+
return out.sort().join("");
|
|
1230
|
+
}
|
|
1231
|
+
function walkWithCalls(f, visit2) {
|
|
1232
|
+
const bodies = functionBodies(f);
|
|
1233
|
+
if (bodies.size === 0) {
|
|
1234
|
+
syntax.Walk(f, visit2);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
1238
|
+
const inline = (call) => {
|
|
1239
|
+
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1240
|
+
const list = words[0] ? bodies.get(words[0]) : void 0;
|
|
1241
|
+
if (!list || callDepth >= MAX_CALL_DEPTH) return;
|
|
1242
|
+
const args = words.slice(1);
|
|
1243
|
+
const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
|
|
1244
|
+
if (seenCalls.has(key)) return;
|
|
1245
|
+
seenCalls.add(key);
|
|
1246
|
+
const outerTable = assignmentTable;
|
|
1247
|
+
const outerOffset = currentStmtOffset;
|
|
1248
|
+
for (const body of list) {
|
|
1249
|
+
if (callWalks >= MAX_CALL_WALKS) return;
|
|
1250
|
+
callWalks++;
|
|
1251
|
+
const table = /* @__PURE__ */ new Map();
|
|
1252
|
+
for (const [name, rec] of outerTable ?? [])
|
|
1253
|
+
if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
|
|
1254
|
+
for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
|
|
1255
|
+
table.delete(name);
|
|
1256
|
+
if (!rebindsPositionals(body)) {
|
|
1257
|
+
args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
|
|
1258
|
+
if (args.length === 1) {
|
|
1259
|
+
table.set("@", { value: args[0], at: -1 });
|
|
1260
|
+
table.set("*", { value: args[0], at: -1 });
|
|
1261
|
+
}
|
|
1262
|
+
table.set("#", { value: String(args.length), at: -1 });
|
|
1263
|
+
}
|
|
1264
|
+
assignmentTable = table;
|
|
1265
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1266
|
+
callDepth++;
|
|
1267
|
+
try {
|
|
1268
|
+
if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
|
|
1269
|
+
syntax.Walk(body, step);
|
|
1270
|
+
} finally {
|
|
1271
|
+
callDepth--;
|
|
1272
|
+
assignmentTable = outerTable;
|
|
1273
|
+
currentStmtOffset = outerOffset;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
const step = (node) => {
|
|
1278
|
+
if (!node) return false;
|
|
1279
|
+
const go = visit2(node);
|
|
1280
|
+
if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
|
|
1281
|
+
return go;
|
|
1282
|
+
};
|
|
1283
|
+
syntax.Walk(f, step);
|
|
1284
|
+
}
|
|
1285
|
+
function withAssignments(f, body) {
|
|
1286
|
+
const outerTable = assignmentTable;
|
|
1287
|
+
const outerOffset = currentStmtOffset;
|
|
1288
|
+
const outerWalks = callWalks;
|
|
1289
|
+
callWalks = 0;
|
|
1290
|
+
assignmentTable = new Map(
|
|
1291
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
1292
|
+
);
|
|
1293
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1294
|
+
try {
|
|
1295
|
+
recordTopLevelAssignments(f);
|
|
1296
|
+
return body();
|
|
1297
|
+
} finally {
|
|
1298
|
+
assignmentTable = outerTable;
|
|
1299
|
+
currentStmtOffset = outerOffset;
|
|
1300
|
+
callWalks = outerWalks;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function trackStatement(n) {
|
|
1304
|
+
try {
|
|
1305
|
+
currentStmtOffset = n.Pos().Offset();
|
|
1306
|
+
} catch {
|
|
1307
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1196
1310
|
function resolveWordLiteral(w) {
|
|
1197
1311
|
const parts = w?.Parts || [];
|
|
1198
1312
|
let s = "";
|
|
@@ -1313,27 +1427,31 @@ function extractShellDestinations(command) {
|
|
|
1313
1427
|
const out = [];
|
|
1314
1428
|
const seen = /* @__PURE__ */ new Set();
|
|
1315
1429
|
try {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1430
|
+
withAssignments(
|
|
1431
|
+
f,
|
|
1432
|
+
() => walkWithCalls(f, (node) => {
|
|
1433
|
+
if (!node) return false;
|
|
1434
|
+
const n = node;
|
|
1435
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1436
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1437
|
+
const callArgs = n.Args || [];
|
|
1438
|
+
if (callArgs.length === 0) return true;
|
|
1439
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1440
|
+
const at = unwrapCommandHead(words);
|
|
1441
|
+
const name = (words[at] || "").toLowerCase().split("/").pop() ?? "";
|
|
1442
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1443
|
+
const rest = words.slice(at + 1);
|
|
1444
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1445
|
+
const host2 = parseDestHost(raw);
|
|
1446
|
+
if (!host2) continue;
|
|
1447
|
+
const key = `${name}:${host2}`;
|
|
1448
|
+
if (seen.has(key)) continue;
|
|
1449
|
+
seen.add(key);
|
|
1450
|
+
out.push({ host: host2, binary: name, raw });
|
|
1451
|
+
}
|
|
1452
|
+
return true;
|
|
1453
|
+
})
|
|
1454
|
+
);
|
|
1337
1455
|
} catch {
|
|
1338
1456
|
return out;
|
|
1339
1457
|
}
|
|
@@ -1345,39 +1463,45 @@ function extractShellDestTokens(command) {
|
|
|
1345
1463
|
const out = [];
|
|
1346
1464
|
const seen = /* @__PURE__ */ new Set();
|
|
1347
1465
|
try {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
tok = tok.
|
|
1466
|
+
withAssignments(
|
|
1467
|
+
f,
|
|
1468
|
+
() => walkWithCalls(f, (node) => {
|
|
1469
|
+
if (!node) return false;
|
|
1470
|
+
const n = node;
|
|
1471
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1472
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1473
|
+
const callArgs = n.Args || [];
|
|
1474
|
+
if (callArgs.length === 0) return true;
|
|
1475
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1476
|
+
const head = unwrapCommandHead(words);
|
|
1477
|
+
const name = (words[head] || "").toLowerCase().split("/").pop() ?? "";
|
|
1478
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1479
|
+
const rest = words.slice(head + 1);
|
|
1480
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1481
|
+
if (!raw) continue;
|
|
1482
|
+
let tok = raw.trim();
|
|
1483
|
+
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(tok);
|
|
1484
|
+
const hasScheme = scheme !== null;
|
|
1485
|
+
if (hasScheme) tok = tok.slice(scheme[0].length);
|
|
1486
|
+
tok = tok.split(/[/?#]/)[0];
|
|
1487
|
+
const at = tok.lastIndexOf("@");
|
|
1488
|
+
if (at >= 0) tok = tok.slice(at + 1);
|
|
1489
|
+
if (tok.startsWith("[")) {
|
|
1490
|
+
const close = tok.indexOf("]");
|
|
1491
|
+
if (close > 0) tok = tok.slice(0, close + 1);
|
|
1492
|
+
} else {
|
|
1493
|
+
tok = tok.split(":")[0];
|
|
1494
|
+
}
|
|
1495
|
+
if (!tok) continue;
|
|
1496
|
+
if (!hasScheme && /^\d+$/.test(tok) && Number(tok) < 16777216) continue;
|
|
1497
|
+
const key = `${name}:${tok}`;
|
|
1498
|
+
if (seen.has(key)) continue;
|
|
1499
|
+
seen.add(key);
|
|
1500
|
+
out.push({ token: tok, binary: name });
|
|
1371
1501
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (seen.has(key)) continue;
|
|
1376
|
-
seen.add(key);
|
|
1377
|
-
out.push({ token: tok, binary: name });
|
|
1378
|
-
}
|
|
1379
|
-
return true;
|
|
1380
|
-
});
|
|
1502
|
+
return true;
|
|
1503
|
+
})
|
|
1504
|
+
);
|
|
1381
1505
|
} catch {
|
|
1382
1506
|
return out;
|
|
1383
1507
|
}
|
|
@@ -1496,6 +1620,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1496
1620
|
if (depth === 0) {
|
|
1497
1621
|
seenPayloads = /* @__PURE__ */ new Set();
|
|
1498
1622
|
payloadBudget = PAYLOAD_BUDGET;
|
|
1623
|
+
if (callDepth === 0) callWalks = 0;
|
|
1499
1624
|
}
|
|
1500
1625
|
assignmentTable = new Map(
|
|
1501
1626
|
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
@@ -1503,7 +1628,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1503
1628
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1504
1629
|
try {
|
|
1505
1630
|
recordTopLevelAssignments(f);
|
|
1506
|
-
|
|
1631
|
+
walkWithCalls(f, (node) => {
|
|
1507
1632
|
if (!node || result?.verdict === "block") return false;
|
|
1508
1633
|
const n = node;
|
|
1509
1634
|
const nodeType = syntax.NodeType(n);
|
|
@@ -2292,6 +2417,19 @@ function runnablePrefix(command) {
|
|
|
2292
2417
|
}
|
|
2293
2418
|
return null;
|
|
2294
2419
|
}
|
|
2420
|
+
function uploadedFile(tool, flag, value) {
|
|
2421
|
+
const v = value.value;
|
|
2422
|
+
const file2 = (f) => f && f !== "-" && f !== "." ? { value: f } : null;
|
|
2423
|
+
if (tool === "curl") {
|
|
2424
|
+
if (["-d", "--data", "--data-ascii", "--data-binary", "--json"].includes(flag))
|
|
2425
|
+
return v.startsWith("@") ? file2(v.slice(1)) : null;
|
|
2426
|
+
if (flag === "--data-urlencode") return file2(/^[^=@]*@(.+)$/.exec(v)?.[1]);
|
|
2427
|
+
if (flag === "-F" || flag === "--form") return file2(/^[^=]*=[@<]([^;]+)/.exec(v)?.[1]);
|
|
2428
|
+
if (flag === "-T" || flag === "--upload-file") return file2(v);
|
|
2429
|
+
}
|
|
2430
|
+
if (tool === "wget" && ["--post-file", "--body-file"].includes(flag)) return file2(v);
|
|
2431
|
+
return null;
|
|
2432
|
+
}
|
|
2295
2433
|
function merge(a, b) {
|
|
2296
2434
|
a.cwd = unique([...a.cwd, ...b.cwd]);
|
|
2297
2435
|
for (const key of /* @__PURE__ */ new Set([...a.vars.keys(), ...b.vars.keys()])) {
|
|
@@ -2365,6 +2503,9 @@ function wordValues(word, state) {
|
|
|
2365
2503
|
function collectShellFileAccesses(command, context = {}) {
|
|
2366
2504
|
const out = [];
|
|
2367
2505
|
let payloads = 256;
|
|
2506
|
+
const functions = /* @__PURE__ */ new Map();
|
|
2507
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
2508
|
+
let callWalks2 = MAX_CALL_WALKS2;
|
|
2368
2509
|
const initial = {
|
|
2369
2510
|
cwd: [context.cwd ?? ""],
|
|
2370
2511
|
vars: /* @__PURE__ */ new Map([["HOME", [context.homeDirs?.[0] ?? "~"]]])
|
|
@@ -2622,8 +2763,98 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2622
2763
|
}
|
|
2623
2764
|
return;
|
|
2624
2765
|
}
|
|
2766
|
+
if (name === "wget" || name === "curl" || name === "aria2c") {
|
|
2767
|
+
downloadOutputs(name, args, state);
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
if (["http", "https", "xh", "xhs"].includes(name)) {
|
|
2771
|
+
for (const token of args) {
|
|
2772
|
+
if (!token || token.dynamic || /^[a-z][a-z0-9+.-]*:\/\//i.test(token.value)) continue;
|
|
2773
|
+
const m = /^(?:@|[^=:@\s]+@|[^=:@\s]*=@)(.+)$/.exec(token.value);
|
|
2774
|
+
if (m && m[1] !== "-") add("read", { value: m[1] }, state, { upload: true });
|
|
2775
|
+
}
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
if (name === "scp" || name === "rsync") {
|
|
2779
|
+
const operands2 = strings.filter((s) => s && !s.startsWith("-"));
|
|
2780
|
+
const remote = operands2.slice(1).some((o) => /^[^/\s]+:/.test(o));
|
|
2781
|
+
for (const p of copySourcePaths(words))
|
|
2782
|
+
add("read", locate(p), state, remote ? { upload: true } : {});
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2625
2785
|
for (const p of copySourcePaths(words)) add("read", locate(p), state);
|
|
2626
2786
|
}
|
|
2787
|
+
function downloadOutputs(name, args, state) {
|
|
2788
|
+
const valueShort = DOWNLOAD_SHORT_VALUE[name];
|
|
2789
|
+
const outputs = [];
|
|
2790
|
+
const urls = [];
|
|
2791
|
+
let dir2 = null;
|
|
2792
|
+
let remoteName = false;
|
|
2793
|
+
let document = false;
|
|
2794
|
+
let serverNamed = false;
|
|
2795
|
+
const take = (flag, value) => {
|
|
2796
|
+
if (!value) return;
|
|
2797
|
+
const sent = uploadedFile(name, flag, value);
|
|
2798
|
+
if (sent) add("read", sent, state, { upload: true });
|
|
2799
|
+
if (DOWNLOAD_OUTPUT_FLAGS[name].includes(flag)) {
|
|
2800
|
+
if (["-O", "--output-document", "--out"].includes(flag) || name === "aria2c" && flag === "-o")
|
|
2801
|
+
document = true;
|
|
2802
|
+
if (value.value !== "-" || value.pattern || value.dynamic) outputs.push(value);
|
|
2803
|
+
} else if (DOWNLOAD_DIR_FLAGS[name].includes(flag)) dir2 = value;
|
|
2804
|
+
else if (name === "wget" && (flag === "-i" || flag === "--input-file")) serverNamed = true;
|
|
2805
|
+
};
|
|
2806
|
+
for (let i = 0; i < args.length; i++) {
|
|
2807
|
+
const token = args[i];
|
|
2808
|
+
if (!token) continue;
|
|
2809
|
+
const v = token.value;
|
|
2810
|
+
if (v.startsWith("--")) {
|
|
2811
|
+
const eq = v.indexOf("=");
|
|
2812
|
+
const flag = eq < 0 ? v : v.slice(0, eq);
|
|
2813
|
+
if (["--remote-name", "--remote-name-all"].includes(flag)) remoteName = true;
|
|
2814
|
+
if (["--remote-header-name", "--recursive", "--mirror", "--page-requisites"].includes(flag))
|
|
2815
|
+
serverNamed = true;
|
|
2816
|
+
if (DOWNLOAD_LONG_VALUE[name].includes(flag))
|
|
2817
|
+
take(flag, eq < 0 ? args[++i] ?? null : { ...token, value: v.slice(eq + 1) });
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
if (v.startsWith("-") && v.length > 1 && !token.dynamic) {
|
|
2821
|
+
for (let j = 1; j < v.length; j++) {
|
|
2822
|
+
const c = v[j];
|
|
2823
|
+
if (name === "wget" && c === "n") {
|
|
2824
|
+
j++;
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
if (name === "curl" && c === "O") remoteName = true;
|
|
2828
|
+
if (name === "curl" && c === "J") serverNamed = true;
|
|
2829
|
+
if (name === "wget" && (c === "r" || c === "m" || c === "p")) serverNamed = true;
|
|
2830
|
+
if (valueShort.includes(c)) {
|
|
2831
|
+
const rest = v.slice(j + 1);
|
|
2832
|
+
take(`-${c}`, rest ? { value: rest } : args[++i] ?? null);
|
|
2833
|
+
break;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
continue;
|
|
2837
|
+
}
|
|
2838
|
+
if (/^(?:https?|ftps?):\/\//i.test(v)) urls.push(v);
|
|
2839
|
+
}
|
|
2840
|
+
for (const out2 of outputs) {
|
|
2841
|
+
const spelled = name === "aria2c" && dir2?.value && !out2.value.startsWith("/") ? { ...out2, value: `${dir2.value.replace(/\/+$/, "")}/${out2.value}` } : out2;
|
|
2842
|
+
if (name === "curl" && /#\d/.test(spelled.value))
|
|
2843
|
+
add("write", { value: "", pattern: spelled.value.replace(/#\d+/g, "*"), dot: true }, state);
|
|
2844
|
+
else add("write", spelled, state);
|
|
2845
|
+
}
|
|
2846
|
+
const remote = name === "curl" ? remoteName || serverNamed : !document || serverNamed;
|
|
2847
|
+
if (!remote) return;
|
|
2848
|
+
const into = dir2?.value ? dir2.value.replace(/\/+$/, "") : ".";
|
|
2849
|
+
if (serverNamed || urls.length === 0) {
|
|
2850
|
+
add("write", { value: "", pattern: `${quoteGlob(into)}/*`, dot: true }, state);
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
for (const url of urls) {
|
|
2854
|
+
const file2 = url.replace(/[?#].*$/, "").replace(/^[a-z]+:\/\/[^/]*/i, "").split("/").pop();
|
|
2855
|
+
add("write", { value: `${into}/${file2 || "index.html"}` }, state);
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2627
2858
|
function findActions2(tokens, words, head, state, nested, depth) {
|
|
2628
2859
|
const starts = findStartPoints(words, head).starts;
|
|
2629
2860
|
const roots = (starts.length ? starts : ["."]).slice(0, MAX_ALTERNATIVES);
|
|
@@ -2695,7 +2926,35 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2695
2926
|
const words = wordValues(node, state);
|
|
2696
2927
|
alternatives = alternatives.flatMap((prefix) => (words.length ? words : [null]).map((w) => [...prefix, w])).slice(0, MAX_ALTERNATIVES);
|
|
2697
2928
|
}
|
|
2698
|
-
for (const tokens of alternatives)
|
|
2929
|
+
for (const tokens of alternatives) {
|
|
2930
|
+
fromWords(tokens, state, nested, depth);
|
|
2931
|
+
inlineCall(tokens, state, depth);
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
function inlineCall(tokens, state, depth) {
|
|
2935
|
+
const head = tokens[0];
|
|
2936
|
+
if (!head || head.dynamic || head.pattern) return;
|
|
2937
|
+
const bodies = functions.get(head.value);
|
|
2938
|
+
if (!bodies || (state.callDepth ?? 0) >= MAX_CALL_DEPTH2) return;
|
|
2939
|
+
const args = tokens.slice(1).map((a) => a && !a.dynamic ? a.value : null);
|
|
2940
|
+
const vars = JSON.stringify([...state.vars].sort(([a], [b]) => a < b ? -1 : 1));
|
|
2941
|
+
const key = `${head.value}\0${JSON.stringify(args)}\0${state.cwd.join("")}\0${vars}`;
|
|
2942
|
+
if (seenCalls.has(key)) return;
|
|
2943
|
+
seenCalls.add(key);
|
|
2944
|
+
for (const body of bodies) {
|
|
2945
|
+
if (callWalks2-- <= 0) return;
|
|
2946
|
+
const local = clone(state);
|
|
2947
|
+
local.callDepth = (state.callDepth ?? 0) + 1;
|
|
2948
|
+
for (const name of POSITIONAL) local.vars.delete(name);
|
|
2949
|
+
if (!rebindsPositionals(body)) {
|
|
2950
|
+
args.slice(0, 9).forEach((v, i) => local.vars.set(String(i + 1), v === null ? null : [v]));
|
|
2951
|
+
const all = args.every((v) => v !== null) && args.length ? unique(args) : null;
|
|
2952
|
+
local.vars.set("@", all);
|
|
2953
|
+
local.vars.set("*", all);
|
|
2954
|
+
local.vars.set("#", [String(args.length)]);
|
|
2955
|
+
}
|
|
2956
|
+
statement(body, local, depth + 1);
|
|
2957
|
+
}
|
|
2699
2958
|
}
|
|
2700
2959
|
function readsScriptFromStdin(stmt) {
|
|
2701
2960
|
const cmd = stmt?.Cmd;
|
|
@@ -2732,11 +2991,13 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2732
2991
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
2733
2992
|
state.onSuccess = void 0;
|
|
2734
2993
|
if (stmt.Background) state = clone(state);
|
|
2994
|
+
const headWord = stmt.Cmd && syntax2.NodeType(stmt.Cmd) === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
2995
|
+
const sends = SENDING_STDIN.has(headWord);
|
|
2735
2996
|
for (const redir of stmt.Redirs ?? []) {
|
|
2736
2997
|
substitutions(redir.Word, state, depth);
|
|
2737
2998
|
for (const word of wordValues(redir.Word, state)) {
|
|
2738
2999
|
if (WRITES.has(redir.Op)) add("write", word, state);
|
|
2739
|
-
if (READS.has(redir.Op)) add("read", word, state);
|
|
3000
|
+
if (READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
|
|
2740
3001
|
if (redir.Op === DUP_OUT && word.value && !/^\d+$/.test(word.value))
|
|
2741
3002
|
add("write", word, state);
|
|
2742
3003
|
}
|
|
@@ -2845,7 +3106,10 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2845
3106
|
}
|
|
2846
3107
|
const parsed = parseShared(command);
|
|
2847
3108
|
const program2 = typeof parsed !== "symbol" ? parsed : runnablePrefix(command);
|
|
2848
|
-
if (program2)
|
|
3109
|
+
if (program2) {
|
|
3110
|
+
for (const [name, bodies] of functionBodies(program2)) functions.set(name, bodies);
|
|
3111
|
+
statements(program2.Stmts ?? [], initial, 0);
|
|
3112
|
+
}
|
|
2849
3113
|
return out;
|
|
2850
3114
|
}
|
|
2851
3115
|
function isAgentInstructionPath(absPath, root) {
|
|
@@ -2961,6 +3225,33 @@ function hits(access, paths, directories, context) {
|
|
|
2961
3225
|
}
|
|
2962
3226
|
return false;
|
|
2963
3227
|
}
|
|
3228
|
+
function hitsSystemDir(access, context, dirs = SYSTEM_DIRS) {
|
|
3229
|
+
const home = context.homeDirs?.[0] ?? "~";
|
|
3230
|
+
const candidates2 = access.candidates ?? [
|
|
3231
|
+
normalizeFilePath(access.path, access.cwd ?? context.cwd, home)
|
|
3232
|
+
];
|
|
3233
|
+
for (const p of candidates2) {
|
|
3234
|
+
if (dirs.some((d) => within(p, d)) && !exempt(p)) return true;
|
|
3235
|
+
if (access.recursive && dirs.some((d) => within(d, p))) return true;
|
|
3236
|
+
}
|
|
3237
|
+
if (!access.glob) return false;
|
|
3238
|
+
const pattern = normalizeFilePath(access.glob, access.cwd ?? context.cwd, home);
|
|
3239
|
+
const magic = pattern.search(/[*?[{]/);
|
|
3240
|
+
const literal = magic < 0 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", magic) + 1);
|
|
3241
|
+
if (literal && exempt(literal.replace(/\/$/, "") || "/")) return false;
|
|
3242
|
+
const options = { dot: access.dotGlob === true, nonegate: true };
|
|
3243
|
+
const components = pattern.split("/");
|
|
3244
|
+
for (const root of dirs) {
|
|
3245
|
+
const count = root.split("/").length;
|
|
3246
|
+
if (components.length > count && (0, import_picomatch.default)(components.slice(0, count).join("/"), options)(root))
|
|
3247
|
+
return true;
|
|
3248
|
+
if (access.recursive && (0, import_picomatch.default)(pattern, options)(root)) return true;
|
|
3249
|
+
}
|
|
3250
|
+
return false;
|
|
3251
|
+
}
|
|
3252
|
+
function uploadDirs(context) {
|
|
3253
|
+
return context.homeDirs?.[0] === "/root" ? UPLOAD_SYSTEM_DIRS.filter((d) => d !== "/root") : UPLOAD_SYSTEM_DIRS;
|
|
3254
|
+
}
|
|
2964
3255
|
function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
2965
3256
|
const homes = [.../* @__PURE__ */ new Set([...context.homeDirs ?? ["~"], "/root"])];
|
|
2966
3257
|
const startup = homes.flatMap(
|
|
@@ -2982,6 +3273,14 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2982
3273
|
"files.shell-startup",
|
|
2983
3274
|
"review-shell-startup-mutation",
|
|
2984
3275
|
"Changing shell startup files requires approval"
|
|
3276
|
+
] : mutation && hitsSystemDir(access, context) ? [
|
|
3277
|
+
"files.system-dirs",
|
|
3278
|
+
"review-system-dir-mutation",
|
|
3279
|
+
"Changing files in a system directory requires approval"
|
|
3280
|
+
] : !mutation && access.upload && hitsSystemDir(access, context, uploadDirs(context)) ? [
|
|
3281
|
+
"files.system-dirs",
|
|
3282
|
+
"review-system-file-upload",
|
|
3283
|
+
"Uploading a system file requires approval"
|
|
2985
3284
|
] : !mutation && hits(access, PASSWORD_HASH_FILES, [], context) ? [
|
|
2986
3285
|
"files.password-hashes",
|
|
2987
3286
|
"review-password-hash-read",
|
|
@@ -3309,16 +3608,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
|
|
|
3309
3608
|
if (EMAIL_VALUE_RE.test(value)) return false;
|
|
3310
3609
|
if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
|
|
3311
3610
|
if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
|
|
3611
|
+
if (CONCATENATION_RE.test(value)) return false;
|
|
3312
3612
|
if (rejectedByStructuredValidator(value)) return false;
|
|
3313
3613
|
return shannonEntropy(value) >= minEntropy;
|
|
3314
3614
|
}
|
|
3315
3615
|
function nameHoldsCredential(name) {
|
|
3316
3616
|
return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
|
|
3317
3617
|
}
|
|
3618
|
+
function isOtpDocSecret(value) {
|
|
3619
|
+
return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
|
|
3620
|
+
}
|
|
3318
3621
|
function validateQueryCredential(raw) {
|
|
3319
3622
|
const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
|
|
3320
3623
|
if (value === null) return false;
|
|
3321
|
-
if (OTP_SECRET_RE.test(value))
|
|
3624
|
+
if (OTP_SECRET_RE.test(value))
|
|
3625
|
+
return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
|
|
3322
3626
|
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
|
|
3323
3627
|
return looksGenerated(value, 12, 3);
|
|
3324
3628
|
}
|
|
@@ -3332,13 +3636,40 @@ function keywordEndsAPart(name) {
|
|
|
3332
3636
|
}
|
|
3333
3637
|
function validateAssignedSecret(raw, before = "") {
|
|
3334
3638
|
const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
|
|
3335
|
-
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
|
|
3639
|
+
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
|
|
3336
3640
|
if (value === null || name === null) return false;
|
|
3337
|
-
if (!keywordEndsAPart(name)
|
|
3338
|
-
|
|
3641
|
+
if (!keywordEndsAPart(name)) return false;
|
|
3642
|
+
if (ARN_CONTEXT_RE.test(before)) return false;
|
|
3643
|
+
const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
|
|
3339
3644
|
if (!nameHoldsCredential(prefix + name)) return false;
|
|
3340
3645
|
return looksGenerated(value, 12, 3.5);
|
|
3341
3646
|
}
|
|
3647
|
+
function validateBasicCredential(raw) {
|
|
3648
|
+
const b64 = capturedValue(raw, BASIC_VALUE_RE);
|
|
3649
|
+
if (b64 === null) return false;
|
|
3650
|
+
const bare = b64.replace(/=+$/, "");
|
|
3651
|
+
if (bare.length % 4 === 1) return false;
|
|
3652
|
+
const bytes = Buffer.from(b64, "base64");
|
|
3653
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
|
|
3654
|
+
const text = bytes.toString("utf8");
|
|
3655
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) return false;
|
|
3656
|
+
for (let i = 0; i < text.length; i++) {
|
|
3657
|
+
const c = text.charCodeAt(i);
|
|
3658
|
+
if (c < 32 || c === 127) return false;
|
|
3659
|
+
}
|
|
3660
|
+
const pair = BASIC_PAIR_RE.exec(text);
|
|
3661
|
+
if (!pair) return false;
|
|
3662
|
+
const password = pair[2];
|
|
3663
|
+
if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
|
|
3664
|
+
const lower2 = password.toLowerCase();
|
|
3665
|
+
if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
|
|
3666
|
+
return shannonEntropy(password) >= 3;
|
|
3667
|
+
}
|
|
3668
|
+
function validateOtpSecret(raw) {
|
|
3669
|
+
const secret = capturedValue(raw, OTP_CAPTURE_RE);
|
|
3670
|
+
if (secret === null || isOtpDocSecret(secret)) return false;
|
|
3671
|
+
return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
|
|
3672
|
+
}
|
|
3342
3673
|
function matchSensitivePath2(resolvedPath, originalPath, options = {}) {
|
|
3343
3674
|
const normalised = resolvedPath.replace(/\\/g, "/");
|
|
3344
3675
|
if (!options.systemAccessHandled && isLegacySystemPath(normalised))
|
|
@@ -3647,6 +3978,156 @@ function parsePySpec(raw) {
|
|
|
3647
3978
|
const exact = PY_EXACT_RE.exec(m[2].trim());
|
|
3648
3979
|
return { name, version: exact ? exact[1] : void 0 };
|
|
3649
3980
|
}
|
|
3981
|
+
function exactVersion(v) {
|
|
3982
|
+
if (!v) return void 0;
|
|
3983
|
+
const m = EXACT_VERSION_RE.exec(v.trim());
|
|
3984
|
+
return m ? m[1] : void 0;
|
|
3985
|
+
}
|
|
3986
|
+
function push(out, ecosystem, manager, name, version2, raw) {
|
|
3987
|
+
out.push({ ecosystem, manager, name, ...version2 ? { version: version2 } : {}, raw });
|
|
3988
|
+
}
|
|
3989
|
+
function gemInstall(words, from, out) {
|
|
3990
|
+
const dash = words.indexOf("--", from);
|
|
3991
|
+
const own = dash < 0 ? words : words.slice(0, dash);
|
|
3992
|
+
const pinned = exactVersion(flagValues(own, from, ["-v", "--version"])[0]);
|
|
3993
|
+
for (const raw of operands(own, from, GEM_VALUE_FLAGS)) {
|
|
3994
|
+
if (NON_REGISTRY_RE.test(raw) || /\.gem$/i.test(raw)) continue;
|
|
3995
|
+
const colon = raw.indexOf(":");
|
|
3996
|
+
const name = colon < 0 ? raw : raw.slice(0, colon);
|
|
3997
|
+
if (!GEM_NAME_RE.test(name)) continue;
|
|
3998
|
+
push(
|
|
3999
|
+
out,
|
|
4000
|
+
"RubyGems",
|
|
4001
|
+
"gem",
|
|
4002
|
+
name,
|
|
4003
|
+
colon < 0 ? pinned : exactVersion(raw.slice(colon + 1)),
|
|
4004
|
+
raw
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
function cargoCrates(words, from, manager, out) {
|
|
4009
|
+
const flags = manager === "cargo add" ? CARGO_ADD_VALUE_FLAGS : CARGO_INSTALL_VALUE_FLAGS;
|
|
4010
|
+
if (flagValues(words, from, ["--git", "--path", "--registry", "--index"]).length > 0) return;
|
|
4011
|
+
if (words.some((w) => w === "--git" || w === "--path")) return;
|
|
4012
|
+
const flagVersion = manager === "cargo install" ? exactVersion(flagValues(words, from, ["--version", "--vers"])[0]) : void 0;
|
|
4013
|
+
for (const raw of operands(words, from, flags)) {
|
|
4014
|
+
const at = raw.indexOf("@");
|
|
4015
|
+
const name = at < 0 ? raw : raw.slice(0, at);
|
|
4016
|
+
if (!CRATE_NAME_RE.test(name)) continue;
|
|
4017
|
+
push(
|
|
4018
|
+
out,
|
|
4019
|
+
"crates.io",
|
|
4020
|
+
manager,
|
|
4021
|
+
name,
|
|
4022
|
+
at < 0 ? flagVersion : exactVersion(raw.slice(at + 1)),
|
|
4023
|
+
raw
|
|
4024
|
+
);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
function goModules(words, from, manager, out) {
|
|
4028
|
+
let specs = operands(words, from, GO_VALUE_FLAGS);
|
|
4029
|
+
if (manager === "go run") specs = specs.slice(0, 1).filter((s) => s.includes("@"));
|
|
4030
|
+
for (const raw of specs) {
|
|
4031
|
+
const at = raw.indexOf("@");
|
|
4032
|
+
const pathPart = (at < 0 ? raw : raw.slice(0, at)).replace(/\/\.\.\.$/, "");
|
|
4033
|
+
const ver = at < 0 ? void 0 : raw.slice(at + 1);
|
|
4034
|
+
if (ver === "none") continue;
|
|
4035
|
+
if (!GO_PATH_RE.test(pathPart) || /\.go$/i.test(pathPart)) continue;
|
|
4036
|
+
const exact = ver && /^v\d/.test(ver) ? exactVersion(ver) : void 0;
|
|
4037
|
+
push(out, "Go", manager, pathPart, exact, raw);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
function dotnetAdd(words, from, out) {
|
|
4041
|
+
let k = -1;
|
|
4042
|
+
for (let i = from; i < Math.min(words.length, from + 2); i++) {
|
|
4043
|
+
if (lower(words[i]) === "package") {
|
|
4044
|
+
k = i;
|
|
4045
|
+
break;
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
if (k < 0) return;
|
|
4049
|
+
const name = operands(words, k + 1, DOTNET_ADD_VALUE_FLAGS)[0];
|
|
4050
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4051
|
+
const version2 = exactVersion(flagValues(words, k + 1, ["-v", "--version"])[0]);
|
|
4052
|
+
push(out, "NuGet", "dotnet add package", name, version2, name);
|
|
4053
|
+
}
|
|
4054
|
+
function dotnetTool(words, from, out) {
|
|
4055
|
+
const name = operands(words, from, DOTNET_TOOL_VALUE_FLAGS)[0];
|
|
4056
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4057
|
+
const version2 = exactVersion(flagValues(words, from, ["--version"])[0]);
|
|
4058
|
+
push(out, "NuGet", "dotnet tool", name, version2, name);
|
|
4059
|
+
}
|
|
4060
|
+
function nugetInstall(words, from, out) {
|
|
4061
|
+
const lowered = words.map((w) => w !== null && w.startsWith("-") ? w.toLowerCase() : w);
|
|
4062
|
+
const name = operands(lowered, from, NUGET_VALUE_FLAGS)[0];
|
|
4063
|
+
if (!name || !NUGET_NAME_RE.test(name) || /\.config$/i.test(name)) return;
|
|
4064
|
+
const version2 = exactVersion(flagValues(lowered, from, ["-version"])[0]);
|
|
4065
|
+
push(out, "NuGet", "nuget install", name, version2, name);
|
|
4066
|
+
}
|
|
4067
|
+
function mavenGet(words, from, out) {
|
|
4068
|
+
const goal = words.slice(from).some(
|
|
4069
|
+
(w) => w !== null && /(?:^|:)dependency:get$|^org\.apache\.maven\.plugins:maven-dependency-plugin:(?:[\w.-]+:)?get$/.test(
|
|
4070
|
+
w
|
|
4071
|
+
)
|
|
4072
|
+
);
|
|
4073
|
+
if (!goal) return;
|
|
4074
|
+
const prop = (key) => {
|
|
4075
|
+
for (let i = from; i < words.length; i++) {
|
|
4076
|
+
const w = words[i];
|
|
4077
|
+
if (w === null) continue;
|
|
4078
|
+
if (w.startsWith(`-D${key}=`)) return w.slice(key.length + 3);
|
|
4079
|
+
if (w === "-D" && words[i + 1]?.startsWith(`${key}=`))
|
|
4080
|
+
return words[i + 1].slice(key.length + 1);
|
|
4081
|
+
}
|
|
4082
|
+
return void 0;
|
|
4083
|
+
};
|
|
4084
|
+
const artifact = prop("artifact");
|
|
4085
|
+
let coord;
|
|
4086
|
+
let ver;
|
|
4087
|
+
if (artifact) {
|
|
4088
|
+
const parts = artifact.split(":");
|
|
4089
|
+
if (parts.length < 2) return;
|
|
4090
|
+
coord = `${parts[0]}:${parts[1]}`;
|
|
4091
|
+
ver = parts[2];
|
|
4092
|
+
} else {
|
|
4093
|
+
const g = prop("groupId");
|
|
4094
|
+
const a = prop("artifactId");
|
|
4095
|
+
if (!g || !a) return;
|
|
4096
|
+
coord = `${g}:${a}`;
|
|
4097
|
+
ver = prop("version");
|
|
4098
|
+
}
|
|
4099
|
+
if (!MAVEN_COORD_RE.test(coord)) return;
|
|
4100
|
+
const exact = ver && !/^(?:LATEST|RELEASE)$/i.test(ver) ? exactVersion(ver) : void 0;
|
|
4101
|
+
push(out, "Maven", "mvn dependency:get", coord, exact, artifact ?? coord);
|
|
4102
|
+
}
|
|
4103
|
+
function composerPackages(words, from, manager, out) {
|
|
4104
|
+
const ops = operands(words, from, COMPOSER_VALUE_FLAGS);
|
|
4105
|
+
const specs = manager === "composer create-project" ? ops.slice(0, 1) : ops;
|
|
4106
|
+
for (const raw of specs) {
|
|
4107
|
+
const sep = raw.search(/[:=]/);
|
|
4108
|
+
const name = sep < 0 ? raw : raw.slice(0, sep);
|
|
4109
|
+
if (!COMPOSER_NAME_RE.test(name)) continue;
|
|
4110
|
+
let ver = sep < 0 ? void 0 : raw.slice(sep + 1);
|
|
4111
|
+
if (manager === "composer create-project" && ver === void 0) ver = ops[2] ?? void 0;
|
|
4112
|
+
push(out, "Packagist", manager, name.toLowerCase(), exactVersion(ver), raw);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
function editorExtensions(words, from, out) {
|
|
4116
|
+
for (const raw of flagValues(words, from, ["--install-extension"])) {
|
|
4117
|
+
if (/\.vsix$/i.test(raw) || NON_REGISTRY_RE.test(raw)) continue;
|
|
4118
|
+
const at = raw.lastIndexOf("@");
|
|
4119
|
+
const name = at <= 0 ? raw : raw.slice(0, at);
|
|
4120
|
+
if (!EXTENSION_ID_RE.test(name)) continue;
|
|
4121
|
+
push(
|
|
4122
|
+
out,
|
|
4123
|
+
"VSCode",
|
|
4124
|
+
"code --install-extension",
|
|
4125
|
+
name,
|
|
4126
|
+
at <= 0 ? void 0 : exactVersion(raw.slice(at + 1)),
|
|
4127
|
+
raw
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
3650
4131
|
function basename(w) {
|
|
3651
4132
|
return (w ?? "").toLowerCase().split("/").pop() ?? "";
|
|
3652
4133
|
}
|
|
@@ -3878,7 +4359,63 @@ function fromCall(words, out, depth) {
|
|
|
3878
4359
|
pushPy(out, "pipenv", operands(words, v + 1, PIPENV_VALUE_FLAGS));
|
|
3879
4360
|
return;
|
|
3880
4361
|
}
|
|
4362
|
+
case "gem": {
|
|
4363
|
+
const v = verbIndex(words, start + 1, GEM_VALUE_FLAGS);
|
|
4364
|
+
if (v >= 0 && ["install", "i"].includes(lower(words[v]))) gemInstall(words, v + 1, out);
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
case "bundle": {
|
|
4368
|
+
const v = verbIndex(words, start + 1, BUNDLE_VALUE_FLAGS);
|
|
4369
|
+
if (v < 0 || lower(words[v]) !== "add") return;
|
|
4370
|
+
if (flagValues(words, v + 1, ["--git", "--github", "--path"]).length > 0) return;
|
|
4371
|
+
const version2 = exactVersion(flagValues(words, v + 1, ["-v", "--version"])[0]);
|
|
4372
|
+
for (const raw of operands(words, v + 1, BUNDLE_VALUE_FLAGS))
|
|
4373
|
+
if (GEM_NAME_RE.test(raw)) push(out, "RubyGems", "bundle add", raw, version2, raw);
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
case "cargo": {
|
|
4377
|
+
const from = words[start + 1]?.startsWith("+") ? start + 2 : start + 1;
|
|
4378
|
+
const v = verbIndex(words, from, CARGO_ADD_VALUE_FLAGS);
|
|
4379
|
+
const verb = lower(words[v]);
|
|
4380
|
+
if (verb === "add") cargoCrates(words, v + 1, "cargo add", out);
|
|
4381
|
+
else if (verb === "install") cargoCrates(words, v + 1, "cargo install", out);
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
case "go": {
|
|
4385
|
+
const v = verbIndex(words, start + 1, GO_VALUE_FLAGS);
|
|
4386
|
+
const verb = lower(words[v]);
|
|
4387
|
+
if (verb === "get" || verb === "install" || verb === "run")
|
|
4388
|
+
goModules(words, v + 1, `go ${verb}`, out);
|
|
4389
|
+
return;
|
|
4390
|
+
}
|
|
4391
|
+
case "dotnet": {
|
|
4392
|
+
const v = verbIndex(words, start + 1, DOTNET_ADD_VALUE_FLAGS);
|
|
4393
|
+
const verb = lower(words[v]);
|
|
4394
|
+
if (verb === "add") dotnetAdd(words, v + 1, out);
|
|
4395
|
+
else if (verb === "tool" && ["install", "update"].includes(lower(words[v + 1])))
|
|
4396
|
+
dotnetTool(words, v + 2, out);
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4399
|
+
case "nuget": {
|
|
4400
|
+
if (lower(words[start + 1]) === "install") nugetInstall(words, start + 2, out);
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4403
|
+
case "mvn":
|
|
4404
|
+
case "mvnw":
|
|
4405
|
+
mavenGet(words, start + 1, out);
|
|
4406
|
+
return;
|
|
4407
|
+
case "composer": {
|
|
4408
|
+
let v = verbIndex(words, start + 1, COMPOSER_VALUE_FLAGS);
|
|
4409
|
+
if (v >= 0 && lower(words[v]) === "global") v = verbIndex(words, v + 1, COMPOSER_VALUE_FLAGS);
|
|
4410
|
+
const verb = lower(words[v]);
|
|
4411
|
+
if (verb === "require" || verb === "req" || verb === "r")
|
|
4412
|
+
composerPackages(words, v + 1, "composer require", out);
|
|
4413
|
+
else if (verb === "create-project")
|
|
4414
|
+
composerPackages(words, v + 1, "composer create-project", out);
|
|
4415
|
+
return;
|
|
4416
|
+
}
|
|
3881
4417
|
default:
|
|
4418
|
+
if (EDITOR_HEADS.includes(family)) editorExtensions(words, start + 1, out);
|
|
3882
4419
|
return;
|
|
3883
4420
|
}
|
|
3884
4421
|
}
|
|
@@ -5404,12 +5941,13 @@ function gitRepoPath(url) {
|
|
|
5404
5941
|
function isDestructiveToolName(bareTool) {
|
|
5405
5942
|
return DESTRUCTIVE_VERB_RE.test(bareTool);
|
|
5406
5943
|
}
|
|
5407
|
-
function* validCardRuns(text, base2) {
|
|
5944
|
+
function* validCardRuns(text, base2, exemptFractions = true) {
|
|
5408
5945
|
const re = new RegExp(base2.source, "g");
|
|
5409
5946
|
let m;
|
|
5410
5947
|
while ((m = re.exec(text)) !== null) {
|
|
5411
5948
|
const digits = m[0].replace(/\D/g, "");
|
|
5412
|
-
|
|
5949
|
+
const fraction = exemptFractions && isDecimalFraction(text, m.index, digits.length === m[0].length);
|
|
5950
|
+
if (!fraction && validateLuhn(digits)) {
|
|
5413
5951
|
yield { index: m.index, length: m[0].length, digits };
|
|
5414
5952
|
} else {
|
|
5415
5953
|
re.lastIndex = m.index + 1;
|
|
@@ -5422,12 +5960,12 @@ function hasValidCard(text) {
|
|
|
5422
5960
|
}
|
|
5423
5961
|
return false;
|
|
5424
5962
|
}
|
|
5425
|
-
function replaceValidCards(text, replace) {
|
|
5963
|
+
function replaceValidCards(text, replace, { exemptFractions = true } = {}) {
|
|
5426
5964
|
let out = text;
|
|
5427
5965
|
for (const base2 of [PII_CC16_RE, PII_CC15_RE]) {
|
|
5428
5966
|
let res = "";
|
|
5429
5967
|
let last = 0;
|
|
5430
|
-
for (const run of validCardRuns(out, base2)) {
|
|
5968
|
+
for (const run of validCardRuns(out, base2, exemptFractions)) {
|
|
5431
5969
|
res += out.slice(last, run.index) + replace(run.digits);
|
|
5432
5970
|
last = run.index + run.length;
|
|
5433
5971
|
}
|
|
@@ -5441,11 +5979,11 @@ function isDecimalFraction(text, index, unseparated) {
|
|
|
5441
5979
|
const a = text[index - 2];
|
|
5442
5980
|
if (sep === ".") {
|
|
5443
5981
|
if (a !== void 0 && a >= "0" && a <= "9") return true;
|
|
5444
|
-
return a === void 0 ||
|
|
5982
|
+
return a === void 0 || (a === "-" || a === "+") && index === 2;
|
|
5445
5983
|
}
|
|
5446
5984
|
if (sep === "," && a === "0") {
|
|
5447
5985
|
const b = text[index - 3];
|
|
5448
|
-
return b === void 0 ||
|
|
5986
|
+
return b === void 0 || b === ";";
|
|
5449
5987
|
}
|
|
5450
5988
|
return false;
|
|
5451
5989
|
}
|
|
@@ -5581,6 +6119,9 @@ function normalizeTrustedEntry(raw) {
|
|
|
5581
6119
|
return full === null ? null : trustedEntryOf(full);
|
|
5582
6120
|
}
|
|
5583
6121
|
function maskPersonalData(text) {
|
|
6122
|
+
return maskText(text, false);
|
|
6123
|
+
}
|
|
6124
|
+
function maskText(text, exemptFractions) {
|
|
5584
6125
|
if (!text) return text;
|
|
5585
6126
|
try {
|
|
5586
6127
|
let out = text;
|
|
@@ -5588,7 +6129,9 @@ function maskPersonalData(text) {
|
|
|
5588
6129
|
const v = m.replace(/[ -]/g, "").toUpperCase();
|
|
5589
6130
|
return validateIban(v) ? `${v.slice(0, 4)} **** **** ${v.slice(-4)}` : m;
|
|
5590
6131
|
});
|
|
5591
|
-
out = replaceValidCards(out, (digits) => `**** **** **** ${digits.slice(-4)}
|
|
6132
|
+
out = replaceValidCards(out, (digits) => `**** **** **** ${digits.slice(-4)}`, {
|
|
6133
|
+
exemptFractions
|
|
6134
|
+
});
|
|
5592
6135
|
out = out.replace(
|
|
5593
6136
|
ACCOUNT_VALUE_RE,
|
|
5594
6137
|
(m, v) => IBAN_LENGTH[v.slice(0, 2).toUpperCase()] === v.length ? `"****${v.slice(-4)}"` : m
|
|
@@ -5611,7 +6154,7 @@ function maskLeaf(value, key, depth) {
|
|
|
5611
6154
|
if (depth > MAX_MASK_DEPTH) return "[REDACTED]";
|
|
5612
6155
|
if (typeof value === "string") {
|
|
5613
6156
|
try {
|
|
5614
|
-
const json = `{${JSON.stringify(key)}:${JSON.stringify(value)}}`;
|
|
6157
|
+
const json = `{${JSON.stringify(key)}:${JSON.stringify(maskPersonalData(value))}}`;
|
|
5615
6158
|
return Object.values(JSON.parse(maskPersonalData(json)))[0];
|
|
5616
6159
|
} catch {
|
|
5617
6160
|
return "[REDACTED]";
|
|
@@ -5619,13 +6162,20 @@ function maskLeaf(value, key, depth) {
|
|
|
5619
6162
|
}
|
|
5620
6163
|
if (typeof value === "number") {
|
|
5621
6164
|
const text = String(value);
|
|
5622
|
-
const masked =
|
|
6165
|
+
const masked = maskText(text, true);
|
|
5623
6166
|
return masked === text ? value : masked;
|
|
5624
6167
|
}
|
|
5625
6168
|
if (Array.isArray(value)) return value.map((v) => maskLeaf(v, key, depth + 1));
|
|
5626
6169
|
if (value && typeof value === "object") {
|
|
6170
|
+
const used = /* @__PURE__ */ new Set();
|
|
5627
6171
|
return Object.fromEntries(
|
|
5628
|
-
Object.entries(value).map(([k, v]) =>
|
|
6172
|
+
Object.entries(value).map(([k, v]) => {
|
|
6173
|
+
const base2 = maskPersonalData(k);
|
|
6174
|
+
let name = base2;
|
|
6175
|
+
for (let n = 2; used.has(name); n++) name = `${base2}#${n}`;
|
|
6176
|
+
used.add(name);
|
|
6177
|
+
return [name, maskLeaf(v, k, depth + 1)];
|
|
6178
|
+
})
|
|
5629
6179
|
);
|
|
5630
6180
|
}
|
|
5631
6181
|
return value;
|
|
@@ -6633,7 +7183,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
6633
7183
|
}
|
|
6634
7184
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
6635
7185
|
}
|
|
6636
|
-
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_crypto4, import_mvdan_sh4, import_crypto5, 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,
|
|
7186
|
+
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_crypto4, import_mvdan_sh4, import_crypto5, 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;
|
|
6637
7187
|
var init_dist = __esm({
|
|
6638
7188
|
"packages/policy-engine/dist/index.mjs"() {
|
|
6639
7189
|
"use strict";
|
|
@@ -7447,6 +7997,11 @@ var init_dist = __esm({
|
|
|
7447
7997
|
example: "cat /etc/shadow",
|
|
7448
7998
|
advice: "Keep Review, or choose Block when agents must never read system password hashes."
|
|
7449
7999
|
},
|
|
8000
|
+
"files.system-dirs": {
|
|
8001
|
+
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.",
|
|
8002
|
+
example: "wget -O /usr/bin/tool https://example.com/tool",
|
|
8003
|
+
advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
|
|
8004
|
+
},
|
|
7450
8005
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
7451
8006
|
"commands.inline-exec": {
|
|
7452
8007
|
plain: "The agent runs a small program written inside the command itself.",
|
|
@@ -7643,7 +8198,7 @@ var init_dist = __esm({
|
|
|
7643
8198
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
7644
8199
|
},
|
|
7645
8200
|
"loading.malicious-package": {
|
|
7646
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
|
|
8201
|
+
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.",
|
|
7647
8202
|
example: "npm install of a package flagged as malware",
|
|
7648
8203
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
7649
8204
|
}
|
|
@@ -7689,6 +8244,15 @@ var init_dist = __esm({
|
|
|
7689
8244
|
defaultValue: "review",
|
|
7690
8245
|
legacy: ["review-password-hash-read"]
|
|
7691
8246
|
},
|
|
8247
|
+
{
|
|
8248
|
+
id: "files.system-dirs",
|
|
8249
|
+
group: "files",
|
|
8250
|
+
title: "System directories",
|
|
8251
|
+
catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
|
|
8252
|
+
values: ALL,
|
|
8253
|
+
defaultValue: "review",
|
|
8254
|
+
legacy: ["review-system-dir-mutation", "review-system-file-upload"]
|
|
8255
|
+
},
|
|
7692
8256
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
7693
8257
|
{
|
|
7694
8258
|
id: "commands.inline-exec",
|
|
@@ -8986,6 +9550,10 @@ var init_dist = __esm({
|
|
|
8986
9550
|
AND_OP = probeBinOp("a && b");
|
|
8987
9551
|
OR_OP = probeBinOp("a || b");
|
|
8988
9552
|
AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
9553
|
+
MAX_CALL_DEPTH = 4;
|
|
9554
|
+
MAX_CALL_WALKS = 256;
|
|
9555
|
+
callDepth = 0;
|
|
9556
|
+
callWalks = 0;
|
|
8989
9557
|
FS_OP_CACHE_MAX = 5e3;
|
|
8990
9558
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
8991
9559
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
@@ -9038,10 +9606,79 @@ var init_dist = __esm({
|
|
|
9038
9606
|
MAX_ARGV_CHARACTERS = 262144;
|
|
9039
9607
|
({ syntax: syntax2 } = import_mvdan_sh2.default);
|
|
9040
9608
|
MAX_ALTERNATIVES = 64;
|
|
9609
|
+
MAX_CALL_DEPTH2 = 4;
|
|
9610
|
+
MAX_CALL_WALKS2 = 256;
|
|
9611
|
+
POSITIONAL = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"];
|
|
9041
9612
|
MAX_DEPTH = 64;
|
|
9042
9613
|
MAX_PAYLOAD_DEPTH = 24;
|
|
9043
9614
|
unique = (a) => [...new Set(a)].slice(0, MAX_ALTERNATIVES);
|
|
9044
|
-
clone = (s) => ({
|
|
9615
|
+
clone = (s) => ({
|
|
9616
|
+
cwd: [...s.cwd],
|
|
9617
|
+
vars: new Map(s.vars),
|
|
9618
|
+
...s.callDepth ? { callDepth: s.callDepth } : {}
|
|
9619
|
+
});
|
|
9620
|
+
SENDING_STDIN = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "socat", "telnet", "curl", "wget"]);
|
|
9621
|
+
DOWNLOAD_SHORT_VALUE = {
|
|
9622
|
+
curl: "odDeEFHKmPrtTuUwxXyYzAbcCQ",
|
|
9623
|
+
wget: "OoaPiBetTwQlARDXIUY",
|
|
9624
|
+
aria2c: "odisx"
|
|
9625
|
+
};
|
|
9626
|
+
DOWNLOAD_LONG_VALUE = {
|
|
9627
|
+
curl: [
|
|
9628
|
+
"--output",
|
|
9629
|
+
"--output-dir",
|
|
9630
|
+
"--dump-header",
|
|
9631
|
+
"--cookie-jar",
|
|
9632
|
+
"--data",
|
|
9633
|
+
"--data-binary",
|
|
9634
|
+
"--data-raw",
|
|
9635
|
+
"--data-ascii",
|
|
9636
|
+
"--data-urlencode",
|
|
9637
|
+
"--form",
|
|
9638
|
+
"--header",
|
|
9639
|
+
"--request",
|
|
9640
|
+
"--user",
|
|
9641
|
+
"--user-agent",
|
|
9642
|
+
"--referer",
|
|
9643
|
+
"--cookie",
|
|
9644
|
+
"--config",
|
|
9645
|
+
"--json",
|
|
9646
|
+
"--upload-file",
|
|
9647
|
+
"--max-time",
|
|
9648
|
+
"--proxy",
|
|
9649
|
+
"--write-out",
|
|
9650
|
+
"--url"
|
|
9651
|
+
],
|
|
9652
|
+
wget: [
|
|
9653
|
+
"--output-document",
|
|
9654
|
+
"--output-file",
|
|
9655
|
+
"--append-output",
|
|
9656
|
+
"--directory-prefix",
|
|
9657
|
+
"--input-file",
|
|
9658
|
+
"--post-data",
|
|
9659
|
+
"--post-file",
|
|
9660
|
+
"--body-data",
|
|
9661
|
+
"--body-file",
|
|
9662
|
+
"--method",
|
|
9663
|
+
"--header",
|
|
9664
|
+
"--user-agent",
|
|
9665
|
+
"--user",
|
|
9666
|
+
"--password",
|
|
9667
|
+
"--tries",
|
|
9668
|
+
"--timeout"
|
|
9669
|
+
],
|
|
9670
|
+
aria2c: ["--out", "--dir", "--input-file"]
|
|
9671
|
+
};
|
|
9672
|
+
DOWNLOAD_OUTPUT_FLAGS = {
|
|
9673
|
+
curl: ["-o", "--output", "-D", "--dump-header", "-c", "--cookie-jar"],
|
|
9674
|
+
wget: ["-O", "--output-document", "-o", "--output-file", "-a", "--append-output"],
|
|
9675
|
+
aria2c: ["-o", "--out"]
|
|
9676
|
+
};
|
|
9677
|
+
DOWNLOAD_DIR_FLAGS = {
|
|
9678
|
+
curl: ["--output-dir"],
|
|
9679
|
+
wget: ["-P", "--directory-prefix"],
|
|
9680
|
+
aria2c: ["-d", "--dir"]
|
|
9681
|
+
};
|
|
9045
9682
|
base = (s) => (s ?? "").replace(/\/+$/, "").split("/").pop() ?? "";
|
|
9046
9683
|
quoteGlob = (s) => s.replace(/[\\*?\[\]{}()!+@]/g, "\\$&");
|
|
9047
9684
|
asWord = (p) => ({ value: p.value, ...p.glob ? { pattern: p.pattern } : {} });
|
|
@@ -9066,6 +9703,18 @@ var init_dist = __esm({
|
|
|
9066
9703
|
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
9067
9704
|
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
9068
9705
|
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
9706
|
+
SYSTEM_DIRS = [
|
|
9707
|
+
"/usr",
|
|
9708
|
+
"/etc",
|
|
9709
|
+
"/boot",
|
|
9710
|
+
"/bin",
|
|
9711
|
+
"/sbin",
|
|
9712
|
+
"/lib",
|
|
9713
|
+
"/lib64",
|
|
9714
|
+
"/var/log"
|
|
9715
|
+
];
|
|
9716
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
9717
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
9069
9718
|
AGENT_INSTRUCTION_FILES = [
|
|
9070
9719
|
".gitlab-ci.yml",
|
|
9071
9720
|
"CLAUDE.md",
|
|
@@ -9083,6 +9732,7 @@ var init_dist = __esm({
|
|
|
9083
9732
|
".husky"
|
|
9084
9733
|
];
|
|
9085
9734
|
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9735
|
+
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
9086
9736
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
9087
9737
|
BIP39_ENGLISH = `abandon
|
|
9088
9738
|
ability
|
|
@@ -11409,15 +12059,20 @@ zoo`.split("\n");
|
|
|
11409
12059
|
IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
|
|
11410
12060
|
PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
|
|
11411
12061
|
DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
|
|
12062
|
+
CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
|
|
11412
12063
|
EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
|
|
11413
12064
|
FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
|
|
11414
|
-
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;
|
|
12065
|
+
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;
|
|
11415
12066
|
QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
|
|
11416
12067
|
OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
|
|
11417
|
-
|
|
12068
|
+
OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
|
|
12069
|
+
ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
|
|
11418
12070
|
ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
|
|
11419
|
-
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
|
|
12071
|
+
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;
|
|
11420
12072
|
ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
|
|
12073
|
+
BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
|
|
12074
|
+
BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
|
|
12075
|
+
OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
|
|
11421
12076
|
DLP_PATTERNS = [
|
|
11422
12077
|
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
11423
12078
|
{
|
|
@@ -11755,6 +12410,22 @@ zoo`.split("\n");
|
|
|
11755
12410
|
contextBoost: true,
|
|
11756
12411
|
minEntropy: 3
|
|
11757
12412
|
},
|
|
12413
|
+
// ── HTTP Basic credential ─────────────────────────────────────────────────
|
|
12414
|
+
// `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
|
|
12415
|
+
// config list. The validator decodes the value and accepts only a
|
|
12416
|
+
// `user:password` whose password looks generated (letters and digits, 8+
|
|
12417
|
+
// characters, the query-credential rule), so prose ("Basic authentication")
|
|
12418
|
+
// and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
|
|
12419
|
+
// review: the same weight as a bare Bearer token.
|
|
12420
|
+
{
|
|
12421
|
+
name: "HTTP Basic Credential",
|
|
12422
|
+
regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
|
|
12423
|
+
severity: "review",
|
|
12424
|
+
keywords: ["basic"],
|
|
12425
|
+
validate: validateBasicCredential,
|
|
12426
|
+
redactGroup: 1,
|
|
12427
|
+
argumentsOnly: true
|
|
12428
|
+
},
|
|
11758
12429
|
// ── Resend ────────────────────────────────────────────────────────────────
|
|
11759
12430
|
{
|
|
11760
12431
|
name: "Resend API Key",
|
|
@@ -11896,6 +12567,19 @@ zoo`.split("\n");
|
|
|
11896
12567
|
validate: validateQueryCredential,
|
|
11897
12568
|
argumentsOnly: true
|
|
11898
12569
|
},
|
|
12570
|
+
// ── OTP secret in a constructor ───────────────────────────────────────────
|
|
12571
|
+
// `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
|
|
12572
|
+
// the shared secret of a one-time-password generator written as a literal.
|
|
12573
|
+
// The otpauth URI form is `Credential in URL Query`. review.
|
|
12574
|
+
{
|
|
12575
|
+
name: "OTP Secret",
|
|
12576
|
+
regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
|
|
12577
|
+
severity: "review",
|
|
12578
|
+
keywords: ["otp"],
|
|
12579
|
+
validate: validateOtpSecret,
|
|
12580
|
+
redactGroup: 1,
|
|
12581
|
+
argumentsOnly: true
|
|
12582
|
+
},
|
|
11899
12583
|
// ── Secret assigned to a telling name ─────────────────────────────────────
|
|
11900
12584
|
// `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
|
|
11901
12585
|
// not a finding: most such lines hold a reference (`process.env.X`), a
|
|
@@ -11903,12 +12587,14 @@ zoo`.split("\n");
|
|
|
11903
12587
|
// generated: 12+ characters, not an identifier, Shannon entropy of 3.5
|
|
11904
12588
|
// bits per character or more. review, never block (node9Firewall#348,
|
|
11905
12589
|
// item 4). Tuned on CredData's public split; its look-alike lines are the
|
|
11906
|
-
// negative control.
|
|
12590
|
+
// negative control. A quote may be escaped (`\"access_token\":\"...\"`):
|
|
12591
|
+
// JSON written inside a string literal. The two optional quotes after the
|
|
12592
|
+
// name are written out, not as a counted group, to keep star height 1.
|
|
11907
12593
|
{
|
|
11908
12594
|
name: "Assigned Secret",
|
|
11909
|
-
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]
|
|
12595
|
+
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,
|
|
11910
12596
|
severity: "review",
|
|
11911
|
-
keywords: ["
|
|
12597
|
+
keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
|
|
11912
12598
|
validate: validateAssignedSecret,
|
|
11913
12599
|
argumentsOnly: true
|
|
11914
12600
|
}
|
|
@@ -12176,7 +12862,169 @@ zoo`.split("\n");
|
|
|
12176
12862
|
]);
|
|
12177
12863
|
PY_SPEC_RE = /^([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?:\[[^\]]*\])?\s*(.*)$/;
|
|
12178
12864
|
PY_EXACT_RE = /^===?\s*v?(\d+(?:\.\d+)*(?:[a-zA-Z0-9.+!-]*))$/;
|
|
12865
|
+
EXACT_VERSION_RE = /^=?v?(\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?)$/;
|
|
12866
|
+
GEM_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12867
|
+
GEM_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12868
|
+
"-v",
|
|
12869
|
+
"--version",
|
|
12870
|
+
"-i",
|
|
12871
|
+
"--install-dir",
|
|
12872
|
+
"-n",
|
|
12873
|
+
"--bindir",
|
|
12874
|
+
"--document",
|
|
12875
|
+
"--build-root",
|
|
12876
|
+
"-P",
|
|
12877
|
+
"--trust-policy",
|
|
12878
|
+
"-s",
|
|
12879
|
+
"--source",
|
|
12880
|
+
"-p",
|
|
12881
|
+
"--http-proxy",
|
|
12882
|
+
"--platform",
|
|
12883
|
+
"-g",
|
|
12884
|
+
"--file",
|
|
12885
|
+
"--without"
|
|
12886
|
+
]);
|
|
12887
|
+
BUNDLE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12888
|
+
"-v",
|
|
12889
|
+
"--version",
|
|
12890
|
+
"-g",
|
|
12891
|
+
"--group",
|
|
12892
|
+
"-s",
|
|
12893
|
+
"--source",
|
|
12894
|
+
"-r",
|
|
12895
|
+
"--require",
|
|
12896
|
+
"--path",
|
|
12897
|
+
"--git",
|
|
12898
|
+
"--github",
|
|
12899
|
+
"--branch",
|
|
12900
|
+
"--ref",
|
|
12901
|
+
"--glob"
|
|
12902
|
+
]);
|
|
12903
|
+
CRATE_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*$/u;
|
|
12904
|
+
CARGO_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12905
|
+
"-F",
|
|
12906
|
+
"--features",
|
|
12907
|
+
"--rename",
|
|
12908
|
+
"--registry",
|
|
12909
|
+
"--manifest-path",
|
|
12910
|
+
"-p",
|
|
12911
|
+
"--package",
|
|
12912
|
+
"--git",
|
|
12913
|
+
"--branch",
|
|
12914
|
+
"--tag",
|
|
12915
|
+
"--rev",
|
|
12916
|
+
"--path",
|
|
12917
|
+
"--base",
|
|
12918
|
+
"--target",
|
|
12919
|
+
"--color",
|
|
12920
|
+
"--config",
|
|
12921
|
+
"-Z",
|
|
12922
|
+
"--lockfile-path"
|
|
12923
|
+
]);
|
|
12924
|
+
CARGO_INSTALL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12925
|
+
"--version",
|
|
12926
|
+
"--vers",
|
|
12927
|
+
"--git",
|
|
12928
|
+
"--branch",
|
|
12929
|
+
"--tag",
|
|
12930
|
+
"--rev",
|
|
12931
|
+
"--path",
|
|
12932
|
+
"--root",
|
|
12933
|
+
"--index",
|
|
12934
|
+
"--registry",
|
|
12935
|
+
"-F",
|
|
12936
|
+
"--features",
|
|
12937
|
+
"--profile",
|
|
12938
|
+
"--target",
|
|
12939
|
+
"--target-dir",
|
|
12940
|
+
"-j",
|
|
12941
|
+
"--jobs",
|
|
12942
|
+
"--bin",
|
|
12943
|
+
"--example",
|
|
12944
|
+
"--color",
|
|
12945
|
+
"--config",
|
|
12946
|
+
"-Z"
|
|
12947
|
+
]);
|
|
12948
|
+
GO_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12949
|
+
"-C",
|
|
12950
|
+
"-tags",
|
|
12951
|
+
"-ldflags",
|
|
12952
|
+
"-gcflags",
|
|
12953
|
+
"-asmflags",
|
|
12954
|
+
"-mod",
|
|
12955
|
+
"-modfile",
|
|
12956
|
+
"-o",
|
|
12957
|
+
"-p",
|
|
12958
|
+
"-pkgdir",
|
|
12959
|
+
"-toolexec",
|
|
12960
|
+
"-exec",
|
|
12961
|
+
"-overlay",
|
|
12962
|
+
"-pgo",
|
|
12963
|
+
"-buildmode",
|
|
12964
|
+
"-compiler",
|
|
12965
|
+
"-gccgoflags",
|
|
12966
|
+
"-installsuffix",
|
|
12967
|
+
"-covermode",
|
|
12968
|
+
"-coverpkg"
|
|
12969
|
+
]);
|
|
12970
|
+
GO_PATH_RE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+(?:\/[\p{L}\p{N}_.~+-]+)*$/u;
|
|
12971
|
+
NUGET_NAME_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u;
|
|
12972
|
+
DOTNET_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12973
|
+
"-v",
|
|
12974
|
+
"--version",
|
|
12975
|
+
"-s",
|
|
12976
|
+
"--source",
|
|
12977
|
+
"-f",
|
|
12978
|
+
"--framework",
|
|
12979
|
+
"--package-directory"
|
|
12980
|
+
]);
|
|
12981
|
+
DOTNET_TOOL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12982
|
+
"--version",
|
|
12983
|
+
"--add-source",
|
|
12984
|
+
"--configfile",
|
|
12985
|
+
"--framework",
|
|
12986
|
+
"--tool-path",
|
|
12987
|
+
"-a",
|
|
12988
|
+
"--arch",
|
|
12989
|
+
"--tool-manifest",
|
|
12990
|
+
"-v",
|
|
12991
|
+
"--verbosity"
|
|
12992
|
+
]);
|
|
12993
|
+
NUGET_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12994
|
+
"-version",
|
|
12995
|
+
"-outputdirectory",
|
|
12996
|
+
"-o",
|
|
12997
|
+
"-source",
|
|
12998
|
+
"-configfile",
|
|
12999
|
+
"-fallbacksource",
|
|
13000
|
+
"-framework",
|
|
13001
|
+
"-packagesavemode",
|
|
13002
|
+
"-dependencyversion",
|
|
13003
|
+
"-solutiondirectory",
|
|
13004
|
+
"-verbosity"
|
|
13005
|
+
]);
|
|
13006
|
+
MAVEN_COORD_RE = /^[\p{L}\p{N}_.-]+:[\p{L}\p{N}_.-]+$/u;
|
|
13007
|
+
COMPOSER_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*\/[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
13008
|
+
COMPOSER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
13009
|
+
"-d",
|
|
13010
|
+
"--working-dir",
|
|
13011
|
+
"--stability",
|
|
13012
|
+
"--repository",
|
|
13013
|
+
"--add-repository"
|
|
13014
|
+
]);
|
|
13015
|
+
EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
|
|
13016
|
+
EXTENSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*\.[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12179
13017
|
MANAGER_HEADS = /* @__PURE__ */ new Set([
|
|
13018
|
+
"gem",
|
|
13019
|
+
"bundle",
|
|
13020
|
+
"cargo",
|
|
13021
|
+
"go",
|
|
13022
|
+
"dotnet",
|
|
13023
|
+
"nuget",
|
|
13024
|
+
"mvn",
|
|
13025
|
+
"mvnw",
|
|
13026
|
+
"composer",
|
|
13027
|
+
...EDITOR_HEADS,
|
|
12180
13028
|
"npm",
|
|
12181
13029
|
"npx",
|
|
12182
13030
|
"pnpm",
|
|
@@ -12255,6 +13103,7 @@ zoo`.split("\n");
|
|
|
12255
13103
|
MAP_GOVERNED = /* @__PURE__ */ new Set([
|
|
12256
13104
|
"files.shell-startup",
|
|
12257
13105
|
"files.password-hashes",
|
|
13106
|
+
"files.system-dirs",
|
|
12258
13107
|
"commands.inline-exec",
|
|
12259
13108
|
"commands.eval-dynamic",
|
|
12260
13109
|
"commands.curl-pipe-shell",
|
|
@@ -12523,7 +13372,6 @@ zoo`.split("\n");
|
|
|
12523
13372
|
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/;
|
|
12524
13373
|
PII_CC15_RE = /\b3[47]\d{2}[-\s]?\d{6}[-\s]?\d{5}\b/;
|
|
12525
13374
|
PII_IBAN_RE = /\b[A-Z]{2}\d{2}(?:[A-Z0-9]|[ -][A-Z0-9]){11,30}\b/;
|
|
12526
|
-
NUMBER_LEAD_RE = /[\s:=;([{"'+-]/;
|
|
12527
13375
|
REALTIME_PII_PATTERNS = ["SSN", "Credit Card", "IBAN"];
|
|
12528
13376
|
MAX_PII_SCAN_BYTES = 1e5;
|
|
12529
13377
|
MASKED_KINDS = /* @__PURE__ */ new Set(["email", "iban", "account"]);
|
|
@@ -12745,7 +13593,7 @@ zoo`.split("\n");
|
|
|
12745
13593
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
12746
13594
|
];
|
|
12747
13595
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
12748
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
13596
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v28";
|
|
12749
13597
|
DEDUPE_PREVIEW_LEN = 120;
|
|
12750
13598
|
ENGINE_VERSION = "1.4.0";
|
|
12751
13599
|
}
|
|
@@ -16153,14 +17001,23 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
16153
17001
|
clearTimeout(timer);
|
|
16154
17002
|
}
|
|
16155
17003
|
}
|
|
16156
|
-
async function waitForDaemonDecision(id, signal) {
|
|
17004
|
+
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
16157
17005
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16158
17006
|
const waitCtrl = new AbortController();
|
|
16159
|
-
|
|
17007
|
+
let capped = false;
|
|
17008
|
+
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
17009
|
+
capped = true;
|
|
17010
|
+
waitCtrl.abort();
|
|
17011
|
+
}, maxWaitMs) : void 0;
|
|
16160
17012
|
const onAbort = () => waitCtrl.abort();
|
|
16161
17013
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
16162
17014
|
try {
|
|
16163
|
-
const waitRes = await fetch(`${base2}/wait/${id}`, { signal: waitCtrl.signal })
|
|
17015
|
+
const waitRes = await fetch(`${base2}/wait/${id}`, { signal: waitCtrl.signal }).catch(
|
|
17016
|
+
(err2) => {
|
|
17017
|
+
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
17018
|
+
throw err2;
|
|
17019
|
+
}
|
|
17020
|
+
);
|
|
16164
17021
|
if (!waitRes.ok) return { decision: "deny" };
|
|
16165
17022
|
const { decision, source, reason } = await waitRes.json();
|
|
16166
17023
|
if (decision === "allow") return { decision: "allow", source };
|
|
@@ -17790,13 +18647,13 @@ function maskRiskMetadataForCloud(rm) {
|
|
|
17790
18647
|
const r = rm;
|
|
17791
18648
|
const dests = Array.isArray(r.exposureDestinations) ? r.exposureDestinations.filter((d) => typeof d === "string") : [];
|
|
17792
18649
|
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);
|
|
17793
|
-
const
|
|
18650
|
+
const maskText2 = (text) => {
|
|
17794
18651
|
let out2 = text;
|
|
17795
18652
|
for (const [full, masked] of swaps) out2 = out2.split(full).join(masked);
|
|
17796
18653
|
return maskPersonalData(out2);
|
|
17797
18654
|
};
|
|
17798
18655
|
const out = {};
|
|
17799
|
-
for (const [k, v] of Object.entries(r)) out[k] = typeof v === "string" ?
|
|
18656
|
+
for (const [k, v] of Object.entries(r)) out[k] = typeof v === "string" ? maskText2(v) : v;
|
|
17800
18657
|
if (dests.length > 0) {
|
|
17801
18658
|
out.exposureDestinations = dests.map(maskDestination);
|
|
17802
18659
|
out.exposureDestinationHashes = dests.map((d) => isMaskedKind(d) ? hashDestination(d) : "");
|
|
@@ -18099,7 +18956,20 @@ function ecoDir(eco) {
|
|
|
18099
18956
|
return import_path24.default.join(osvRoot(), eco);
|
|
18100
18957
|
}
|
|
18101
18958
|
function indexKey(eco, name) {
|
|
18102
|
-
|
|
18959
|
+
switch (eco) {
|
|
18960
|
+
case "PyPI":
|
|
18961
|
+
return normalizePyPiName(name);
|
|
18962
|
+
case "crates.io":
|
|
18963
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
18964
|
+
case "Go":
|
|
18965
|
+
case "Maven":
|
|
18966
|
+
return name;
|
|
18967
|
+
default:
|
|
18968
|
+
return name.toLowerCase();
|
|
18969
|
+
}
|
|
18970
|
+
}
|
|
18971
|
+
function recordEcosystemMatches(eco, recordEco) {
|
|
18972
|
+
return typeof recordEco === "string" && (recordEco === eco || recordEco.startsWith(`${eco}:`));
|
|
18103
18973
|
}
|
|
18104
18974
|
function shardOf(key) {
|
|
18105
18975
|
return (0, import_crypto11.createHash)("sha1").update(key).digest("hex").slice(0, 2);
|
|
@@ -18145,7 +19015,8 @@ function entriesFromRecord(eco, record2) {
|
|
|
18145
19015
|
const modifiedMs = typeof r.modified === "string" ? Date.parse(r.modified) || 0 : 0;
|
|
18146
19016
|
const rows = /* @__PURE__ */ new Map();
|
|
18147
19017
|
for (const a of r.affected ?? []) {
|
|
18148
|
-
if (a?.package?.ecosystem
|
|
19018
|
+
if (!recordEcosystemMatches(eco, a?.package?.ecosystem) || typeof a?.package?.name !== "string")
|
|
19019
|
+
continue;
|
|
18149
19020
|
const key = indexKey(eco, a.package.name);
|
|
18150
19021
|
const entry = rows.get(key) ?? { id: r.id };
|
|
18151
19022
|
if (Array.isArray(a.versions)) {
|
|
@@ -18388,9 +19259,11 @@ async function pypiInfo(name, version2) {
|
|
|
18388
19259
|
};
|
|
18389
19260
|
}
|
|
18390
19261
|
async function registryInfo(eco, name, version2, freshWindowMs, raw) {
|
|
18391
|
-
|
|
19262
|
+
if (eco === "npm") return npmInfo(name, version2, freshWindowMs, raw);
|
|
19263
|
+
if (eco === "PyPI") return pypiInfo(name, version2);
|
|
19264
|
+
return null;
|
|
18392
19265
|
}
|
|
18393
|
-
var import_semver, TIMEOUT_MS2, MAX_BYTES;
|
|
19266
|
+
var import_semver, TIMEOUT_MS2, MAX_BYTES, HAS_REGISTRY_CLIENT;
|
|
18394
19267
|
var init_registry2 = __esm({
|
|
18395
19268
|
"src/supply-chain/registry.ts"() {
|
|
18396
19269
|
"use strict";
|
|
@@ -18398,6 +19271,7 @@ var init_registry2 = __esm({
|
|
|
18398
19271
|
init_net();
|
|
18399
19272
|
TIMEOUT_MS2 = 1500;
|
|
18400
19273
|
MAX_BYTES = 2 * 1024 * 1024;
|
|
19274
|
+
HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
18401
19275
|
}
|
|
18402
19276
|
});
|
|
18403
19277
|
|
|
@@ -18442,25 +19316,39 @@ function commandOf(toolName, args) {
|
|
|
18442
19316
|
return typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : null;
|
|
18443
19317
|
}
|
|
18444
19318
|
function label(p, version2) {
|
|
18445
|
-
return `${p.ecosystem
|
|
19319
|
+
return `${KIND[p.ecosystem]} "${p.name}${version2 ? `@${version2}` : ""}"`;
|
|
19320
|
+
}
|
|
19321
|
+
function osvNames(p) {
|
|
19322
|
+
if (p.ecosystem !== "Go") return [p.name];
|
|
19323
|
+
const parts = p.name.split("/");
|
|
19324
|
+
const out = [];
|
|
19325
|
+
for (let n = parts.length; n >= 2 && out.length < 6; n--) out.push(parts.slice(0, n).join("/"));
|
|
19326
|
+
return out;
|
|
18446
19327
|
}
|
|
18447
19328
|
async function maliciousFor(p, version2, cfg, misses, networkCapped) {
|
|
18448
|
-
const
|
|
18449
|
-
const
|
|
18450
|
-
|
|
19329
|
+
const names = osvNames(p);
|
|
19330
|
+
const local = names.map((n) => lookupIndex(p.ecosystem, n));
|
|
19331
|
+
const hits2 = local.flatMap((l) => l.status === "hit" ? l.entries : []);
|
|
19332
|
+
const entries = hits2.length > 0 ? hits2 : null;
|
|
19333
|
+
const freshClean = local.every((l) => l.status === "clean" && l.fresh);
|
|
19334
|
+
const status = local.some((l) => l.status === "unavailable") ? "unavailable" : local[0].status;
|
|
19335
|
+
if (!entries && !freshClean) {
|
|
18451
19336
|
if (!cfg.onlineFallback || networkCapped) {
|
|
18452
19337
|
misses.push(
|
|
18453
|
-
`${label(p, version2)}: local index ${
|
|
19338
|
+
`${label(p, version2)}: local index ${status}, no online lookup (${networkCapped ? "over the per-command network cap" : "online fallback off"})`
|
|
18454
19339
|
);
|
|
18455
19340
|
return null;
|
|
18456
19341
|
}
|
|
18457
|
-
let
|
|
19342
|
+
let perName = null;
|
|
18458
19343
|
try {
|
|
18459
|
-
|
|
19344
|
+
perName = await queryOsvMalicious(
|
|
19345
|
+
names.map((name) => ({ ecosystem: p.ecosystem, name, version: version2 }))
|
|
19346
|
+
);
|
|
18460
19347
|
} catch (err2) {
|
|
18461
19348
|
misses.push(`${label(p, version2)}: OSV online ${err2.message}`);
|
|
18462
19349
|
return null;
|
|
18463
19350
|
}
|
|
19351
|
+
const ids = perName ? [[...new Set(perName.flat())]] : null;
|
|
18464
19352
|
if (!ids) {
|
|
18465
19353
|
misses.push(`${label(p, version2)}: OSV online unavailable`);
|
|
18466
19354
|
return null;
|
|
@@ -18530,7 +19418,7 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
18530
19418
|
}
|
|
18531
19419
|
const findings = [];
|
|
18532
19420
|
let info = null;
|
|
18533
|
-
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
19421
|
+
const wantsRegistry = (cfg.newPackage !== "off" || cfg.installScript !== "off") && HAS_REGISTRY_CLIENT.has(p.ecosystem);
|
|
18534
19422
|
if (wantsRegistry && !networkCapped) {
|
|
18535
19423
|
try {
|
|
18536
19424
|
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
@@ -18610,7 +19498,7 @@ async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
|
18610
19498
|
return { ...ALLOW, misses: [`package check error: ${err2?.message ?? err2}`] };
|
|
18611
19499
|
}
|
|
18612
19500
|
}
|
|
18613
|
-
var import_picomatch3, import_path26, MAX_NETWORK_PACKAGES, ALLOW;
|
|
19501
|
+
var import_picomatch3, import_path26, MAX_NETWORK_PACKAGES, ALLOW, KIND;
|
|
18614
19502
|
var init_check = __esm({
|
|
18615
19503
|
"src/supply-chain/check.ts"() {
|
|
18616
19504
|
"use strict";
|
|
@@ -18623,6 +19511,164 @@ var init_check = __esm({
|
|
|
18623
19511
|
init_local_resolve();
|
|
18624
19512
|
MAX_NETWORK_PACKAGES = 10;
|
|
18625
19513
|
ALLOW = { verdict: "allow", findings: [], misses: [], checked: 0 };
|
|
19514
|
+
KIND = {
|
|
19515
|
+
npm: "npm package",
|
|
19516
|
+
PyPI: "PyPI package",
|
|
19517
|
+
RubyGems: "RubyGems package",
|
|
19518
|
+
"crates.io": "crates.io package",
|
|
19519
|
+
Go: "Go module",
|
|
19520
|
+
NuGet: "NuGet package",
|
|
19521
|
+
Maven: "Maven artifact",
|
|
19522
|
+
Packagist: "Packagist package",
|
|
19523
|
+
VSCode: "VS Code extension"
|
|
19524
|
+
};
|
|
19525
|
+
}
|
|
19526
|
+
});
|
|
19527
|
+
|
|
19528
|
+
// src/policy/negotiation.ts
|
|
19529
|
+
function buildNegotiationMessage(blockedByLabel, isHumanDecision2, humanReason, recoveryCommand, context) {
|
|
19530
|
+
if (isHumanDecision2) {
|
|
19531
|
+
return `NODE9: The human user rejected this action.
|
|
19532
|
+
REASON: ${humanReason || "No specific reason provided."}
|
|
19533
|
+
INSTRUCTIONS:
|
|
19534
|
+
- Do NOT retry this exact command.
|
|
19535
|
+
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
19536
|
+
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
19537
|
+
}
|
|
19538
|
+
if (context?.kind === "protected-path") {
|
|
19539
|
+
const subject = context.path ? `${context.path} is a protected file` : "This file is protected";
|
|
19540
|
+
return `NODE9: Blocked. ${subject}, and node9 stopped this call to it. The call did not run, so nothing was exposed.
|
|
19541
|
+
INSTRUCTIONS:
|
|
19542
|
+
- Do NOT retry, and do NOT reach the file another way (cat, grep, a copy, a script, a redirect).
|
|
19543
|
+
- If this step needs that file, ask the user to provide what you need or to do the step themselves.
|
|
19544
|
+
- Tell the user node9 protected this file, then ask how to proceed.`;
|
|
19545
|
+
}
|
|
19546
|
+
const label3 = blockedByLabel.toLowerCase();
|
|
19547
|
+
if (label3.includes("dlp") || label3.includes("secret detected") || label3.includes("credential review")) {
|
|
19548
|
+
return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
|
|
19549
|
+
CRITICAL INSTRUCTION: Do NOT retry this action.
|
|
19550
|
+
REQUIRED ACTIONS:
|
|
19551
|
+
1. Remove the hardcoded credential from your command or code.
|
|
19552
|
+
2. Use an environment variable or a dedicated secrets manager instead.
|
|
19553
|
+
3. Treat the leaked credential as compromised and rotate it immediately.
|
|
19554
|
+
Do NOT attempt to bypass this check or pass the credential through another tool.
|
|
19555
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19556
|
+
}
|
|
19557
|
+
if (label3.includes("package check")) {
|
|
19558
|
+
return `NODE9: Blocked \u2014 ${humanReason || "a package in this command is known to be malicious."}
|
|
19559
|
+
INSTRUCTIONS:
|
|
19560
|
+
- Do NOT install this package, and do NOT fetch it another way (a different package manager, a tarball URL, a git URL, a mirror).
|
|
19561
|
+
- If a dependency is needed, suggest a well-known alternative and let the user choose.
|
|
19562
|
+
- Tell the user node9 blocked this install and name the advisory, then ask how to proceed.`;
|
|
19563
|
+
}
|
|
19564
|
+
if (label3.includes("sql safety") && label3.includes("delete without where")) {
|
|
19565
|
+
return `NODE9: Blocked \u2014 DELETE without WHERE clause would wipe the entire table.
|
|
19566
|
+
INSTRUCTION: Add a WHERE clause to scope the deletion (e.g. WHERE id = <value>).
|
|
19567
|
+
Do NOT retry without a WHERE clause.
|
|
19568
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19569
|
+
}
|
|
19570
|
+
if (label3.includes("sql safety") && label3.includes("update without where")) {
|
|
19571
|
+
return `NODE9: Blocked \u2014 UPDATE without WHERE clause would update every row.
|
|
19572
|
+
INSTRUCTION: Add a WHERE clause to scope the update (e.g. WHERE id = <value>).
|
|
19573
|
+
Do NOT retry without a WHERE clause.
|
|
19574
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19575
|
+
}
|
|
19576
|
+
if (label3.includes("dangerous word")) {
|
|
19577
|
+
const match = blockedByLabel.match(/dangerous word: "([^"]+)"/i);
|
|
19578
|
+
const word = match?.[1] ?? "a dangerous keyword";
|
|
19579
|
+
return `NODE9: Blocked \u2014 command contains forbidden keyword "${word}".
|
|
19580
|
+
INSTRUCTION: Do NOT use "${word}". Use a non-destructive alternative.
|
|
19581
|
+
Do NOT attempt to bypass this with shell tricks or aliases \u2014 it will be blocked again.
|
|
19582
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19583
|
+
}
|
|
19584
|
+
if (label3.includes("path blocked") || label3.includes("sandbox")) {
|
|
19585
|
+
return `NODE9: Blocked \u2014 operation targets a path outside the allowed sandbox.
|
|
19586
|
+
INSTRUCTION: Move your output to an allowed directory such as /tmp/ or the project directory.
|
|
19587
|
+
Do NOT retry on the same path.
|
|
19588
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19589
|
+
}
|
|
19590
|
+
if (label3.includes("inline execution")) {
|
|
19591
|
+
return `NODE9: Blocked \u2014 inline code execution (e.g. bash -c "...") is not allowed.
|
|
19592
|
+
INSTRUCTION: Use individual tool calls instead of embedding code in a shell string.
|
|
19593
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19594
|
+
}
|
|
19595
|
+
if (label3.includes("strict mode")) {
|
|
19596
|
+
return `NODE9: Blocked \u2014 strict mode is active. All tool calls require explicit human approval.
|
|
19597
|
+
INSTRUCTION: Inform the user this action is pending approval. Wait for them to approve via the dashboard or run "node9 pause".`;
|
|
19598
|
+
}
|
|
19599
|
+
if (label3.includes("rule") && label3.includes("default block")) {
|
|
19600
|
+
const match = blockedByLabel.match(/rule "([^"]+)"/i);
|
|
19601
|
+
const rule = match?.[1] ?? "a policy rule";
|
|
19602
|
+
return `NODE9: Blocked \u2014 action "${rule}" is forbidden by security policy.
|
|
19603
|
+
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
19604
|
+
Do NOT attempt to bypass this rule.
|
|
19605
|
+
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
19606
|
+
}
|
|
19607
|
+
const recovery = recoveryCommand ? `
|
|
19608
|
+
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
19609
|
+
return `NODE9: Action blocked by security policy [${agentFacingLabel(blockedByLabel)}].
|
|
19610
|
+
INSTRUCTIONS:
|
|
19611
|
+
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
19612
|
+
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
19613
|
+
}
|
|
19614
|
+
function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
19615
|
+
const why = ruleDescription || (reason && reason !== blockedByLabel ? reason : void 0) || blockedByLabel || "this action needs your review";
|
|
19616
|
+
const trimmed = why.trimEnd();
|
|
19617
|
+
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
19618
|
+
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
19619
|
+
}
|
|
19620
|
+
function isHumanDecision(label3, blockedBy) {
|
|
19621
|
+
if (blockedBy !== void 0 && blockedBy !== "local-decision") return false;
|
|
19622
|
+
const l = label3.toLowerCase();
|
|
19623
|
+
return l.includes("user") || l.includes("daemon") || l.includes("decision");
|
|
19624
|
+
}
|
|
19625
|
+
function agentFacingLabel(label3) {
|
|
19626
|
+
const plain = label3.replace(/[\p{Extended_Pictographic}\u200D\uFE0F]/gu, "").replace(/\bNode9\b/g, "node9").replace(/\s+/g, " ").trim();
|
|
19627
|
+
return plain || "Security Policy";
|
|
19628
|
+
}
|
|
19629
|
+
function asApprovalRequest(description) {
|
|
19630
|
+
const is = description.lastIndexOf(BLOCKED_BY);
|
|
19631
|
+
if (is > 0) {
|
|
19632
|
+
const by = description.slice(is + BLOCKED_BY.length).replace(/\.$/, "");
|
|
19633
|
+
if (by.endsWith(SHIELD)) return `${description.slice(0, is)} needs your approval (${by})`;
|
|
19634
|
+
}
|
|
19635
|
+
const dash = description.lastIndexOf(DASH_BLOCKED_BY);
|
|
19636
|
+
if (dash > 0) {
|
|
19637
|
+
const by = description.slice(dash + DASH_BLOCKED_BY.length).replace(/\.$/, "");
|
|
19638
|
+
if (by.endsWith(SHIELD)) return `${description.slice(0, dash)}. Needs your approval (${by})`;
|
|
19639
|
+
}
|
|
19640
|
+
return description;
|
|
19641
|
+
}
|
|
19642
|
+
var BLOCKED_BY, DASH_BLOCKED_BY, SHIELD;
|
|
19643
|
+
var init_negotiation = __esm({
|
|
19644
|
+
"src/policy/negotiation.ts"() {
|
|
19645
|
+
"use strict";
|
|
19646
|
+
BLOCKED_BY = " is blocked by ";
|
|
19647
|
+
DASH_BLOCKED_BY = " \u2014 blocked by ";
|
|
19648
|
+
SHIELD = " shield";
|
|
19649
|
+
}
|
|
19650
|
+
});
|
|
19651
|
+
|
|
19652
|
+
// src/auth/decision-sources.ts
|
|
19653
|
+
function backgroundPassSource(result) {
|
|
19654
|
+
if (result.checkedBy === "cloud" || result.blockedBy === "team-policy") return "cloud";
|
|
19655
|
+
if (result.blockedBy === "timeout") return "timeout";
|
|
19656
|
+
return "policy";
|
|
19657
|
+
}
|
|
19658
|
+
var HUMAN_DECISION_SOURCES, REDIRECT_SOURCE, DECISION_ROUTE_SOURCES;
|
|
19659
|
+
var init_decision_sources = __esm({
|
|
19660
|
+
"src/auth/decision-sources.ts"() {
|
|
19661
|
+
"use strict";
|
|
19662
|
+
HUMAN_DECISION_SOURCES = /* @__PURE__ */ new Set([
|
|
19663
|
+
"terminal",
|
|
19664
|
+
"browser",
|
|
19665
|
+
"native"
|
|
19666
|
+
]);
|
|
19667
|
+
REDIRECT_SOURCE = "terminal-redirect";
|
|
19668
|
+
DECISION_ROUTE_SOURCES = /* @__PURE__ */ new Set([
|
|
19669
|
+
...HUMAN_DECISION_SOURCES,
|
|
19670
|
+
REDIRECT_SOURCE
|
|
19671
|
+
]);
|
|
18626
19672
|
}
|
|
18627
19673
|
});
|
|
18628
19674
|
|
|
@@ -18740,6 +19786,10 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
18740
19786
|
}
|
|
18741
19787
|
return _authorizeHeadlessCore(toolName, args, meta, options);
|
|
18742
19788
|
}
|
|
19789
|
+
function daemonWaitCapMs(approvalTimeoutMs) {
|
|
19790
|
+
if (approvalTimeoutMs === 0) return 0;
|
|
19791
|
+
return Math.max(approvalTimeoutMs ?? 0, 12e4) + 5e3;
|
|
19792
|
+
}
|
|
18743
19793
|
function approvalTimeoutReason(approvalTimeoutMs) {
|
|
18744
19794
|
return `No human response within ${approvalTimeoutMs / 1e3}s \u2014 auto-denied by timeout policy. Approve from your phone next time: run \`node9 login\`.`;
|
|
18745
19795
|
}
|
|
@@ -18775,6 +19825,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
18775
19825
|
let statefulRecoveryCommand;
|
|
18776
19826
|
let localSmartRuleMatched = false;
|
|
18777
19827
|
let hardBlockDowngraded = false;
|
|
19828
|
+
let downgradedBlock = null;
|
|
18778
19829
|
let argScan;
|
|
18779
19830
|
const argumentScan = () => argScan ??= scanArgumentSecrets(args);
|
|
18780
19831
|
let fileAccess;
|
|
@@ -19394,6 +20445,16 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19394
20445
|
humanApproverReachable,
|
|
19395
20446
|
overridable: policyResult.overridable
|
|
19396
20447
|
});
|
|
20448
|
+
const blocked = {
|
|
20449
|
+
approved: false,
|
|
20450
|
+
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
20451
|
+
blockedBy: "local-config",
|
|
20452
|
+
blockedByLabel: policyResult.blockedByLabel,
|
|
20453
|
+
ruleHit: policyResult.ruleName,
|
|
20454
|
+
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
20455
|
+
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
|
|
20456
|
+
...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
|
|
20457
|
+
};
|
|
19397
20458
|
const hardBlock = () => {
|
|
19398
20459
|
if (!isManual)
|
|
19399
20460
|
appendLocalAudit(
|
|
@@ -19404,17 +20465,9 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19404
20465
|
{ ...meta, ruleName: policyResult.ruleName },
|
|
19405
20466
|
hashAuditArgs
|
|
19406
20467
|
);
|
|
19407
|
-
return
|
|
19408
|
-
approved: false,
|
|
19409
|
-
reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
|
|
19410
|
-
blockedBy: "local-config",
|
|
19411
|
-
blockedByLabel: policyResult.blockedByLabel,
|
|
19412
|
-
ruleHit: policyResult.ruleName,
|
|
19413
|
-
...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
|
|
19414
|
-
...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
|
|
19415
|
-
...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
|
|
19416
|
-
};
|
|
20468
|
+
return blocked;
|
|
19417
20469
|
};
|
|
20470
|
+
downgradedBlock = blocked;
|
|
19418
20471
|
if (policyResult.dependsOnStatePredicates?.length) {
|
|
19419
20472
|
const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
|
|
19420
20473
|
const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
|
|
@@ -19471,7 +20524,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
19471
20524
|
policyMatchedWord,
|
|
19472
20525
|
policyResult.ruleName
|
|
19473
20526
|
);
|
|
19474
|
-
if (policyRuleDescription)
|
|
20527
|
+
if (policyRuleDescription)
|
|
20528
|
+
riskMetadata.ruleDescription = (hardBlockDowngraded ? asApprovalRequest(policyRuleDescription) : policyRuleDescription).slice(0, 200);
|
|
19475
20529
|
const persistent = policyResult.ruleName || policyResult.tier === 7 || hardBlockDowngraded ? null : getPersistentDecision(toolName);
|
|
19476
20530
|
if (persistent === "allow" && !callSpecificReview()) {
|
|
19477
20531
|
if (!isManual)
|
|
@@ -19635,7 +20689,7 @@ ${packageReview}`;
|
|
|
19635
20689
|
}
|
|
19636
20690
|
}
|
|
19637
20691
|
if (initResult.pending) cloudRequestId = initResult.requestId || null;
|
|
19638
|
-
if (!
|
|
20692
|
+
if (!reviewDemanded()) explainableLabel = "Organization Policy (SaaS)";
|
|
19639
20693
|
} catch {
|
|
19640
20694
|
}
|
|
19641
20695
|
}
|
|
@@ -19777,10 +20831,54 @@ ${packageReview}`;
|
|
|
19777
20831
|
decision: daemonDecision,
|
|
19778
20832
|
source: decisionSource,
|
|
19779
20833
|
reason: daemonReason
|
|
19780
|
-
} = await waitForDaemonDecision(
|
|
20834
|
+
} = await waitForDaemonDecision(
|
|
20835
|
+
daemonEntryId,
|
|
20836
|
+
signal,
|
|
20837
|
+
daemonWaitCapMs(config.settings.approvalTimeoutMs)
|
|
20838
|
+
);
|
|
19781
20839
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
19782
20840
|
const isApproved = daemonDecision === "allow";
|
|
19783
|
-
const isRedirect = decisionSource ===
|
|
20841
|
+
const isRedirect = decisionSource === REDIRECT_SOURCE;
|
|
20842
|
+
if (!isApproved && !isRedirect && !(decisionSource && HUMAN_DECISION_SOURCES.has(decisionSource))) {
|
|
20843
|
+
if (decisionSource === "cloud")
|
|
20844
|
+
return {
|
|
20845
|
+
approved: false,
|
|
20846
|
+
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
20847
|
+
blockedBy: "team-policy",
|
|
20848
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
20849
|
+
decisionSource: "cloud"
|
|
20850
|
+
};
|
|
20851
|
+
const timedOut = decisionSource === "timeout";
|
|
20852
|
+
if (downgradedBlock)
|
|
20853
|
+
return {
|
|
20854
|
+
...downgradedBlock,
|
|
20855
|
+
blockedBy: timedOut ? "timeout" : "local-config"
|
|
20856
|
+
};
|
|
20857
|
+
if (timedOut)
|
|
20858
|
+
return {
|
|
20859
|
+
approved: false,
|
|
20860
|
+
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
20861
|
+
blockedBy: "timeout",
|
|
20862
|
+
blockedByLabel: "Approval Timeout"
|
|
20863
|
+
};
|
|
20864
|
+
if (decisionSource === "policy")
|
|
20865
|
+
return {
|
|
20866
|
+
approved: false,
|
|
20867
|
+
reason: daemonReason || "Action blocked by policy.",
|
|
20868
|
+
blockedBy: "local-config",
|
|
20869
|
+
blockedByLabel: explainableLabel
|
|
20870
|
+
};
|
|
20871
|
+
return {
|
|
20872
|
+
approved: false,
|
|
20873
|
+
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
20874
|
+
blockedBy: "no-approval-mechanism",
|
|
20875
|
+
blockedByLabel: "Approval Unavailable"
|
|
20876
|
+
};
|
|
20877
|
+
}
|
|
20878
|
+
if (isApproved && decisionSource === "cloud")
|
|
20879
|
+
return { approved: true, checkedBy: "cloud", decisionSource: "cloud" };
|
|
20880
|
+
if (isApproved && decisionSource === "policy")
|
|
20881
|
+
return { approved: true, checkedBy: "local-policy" };
|
|
19784
20882
|
const via = "Terminal (node9 tail)";
|
|
19785
20883
|
return {
|
|
19786
20884
|
approved: isApproved,
|
|
@@ -19940,6 +21038,8 @@ var init_orchestrator = __esm({
|
|
|
19940
21038
|
init_shields();
|
|
19941
21039
|
init_jail();
|
|
19942
21040
|
init_safe_text();
|
|
21041
|
+
init_negotiation();
|
|
21042
|
+
init_decision_sources();
|
|
19943
21043
|
SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
19944
21044
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
19945
21045
|
"write",
|
|
@@ -30950,23 +32050,22 @@ function checkApprovalConfig(ctx) {
|
|
|
30950
32050
|
const s = getConfig(ctx.cwd).settings;
|
|
30951
32051
|
const a = s.approvers ?? { native: false, browser: false, cloud: false, terminal: false };
|
|
30952
32052
|
const hasChannel = a.native || a.browser || a.cloud || a.terminal || s.reviewChannel === "ask";
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
|
|
30957
|
-
|
|
30958
|
-
|
|
30959
|
-
|
|
30960
|
-
|
|
30961
|
-
|
|
30962
|
-
|
|
30963
|
-
|
|
30964
|
-
|
|
30965
|
-
|
|
30966
|
-
];
|
|
30967
|
-
}
|
|
32053
|
+
const zeroTimeout = s.approvalTimeoutMs === 0 ? [
|
|
32054
|
+
{
|
|
32055
|
+
category: "Approvals",
|
|
32056
|
+
severity: "medium",
|
|
32057
|
+
title: "Approval timeout is 0: node9 never times a review out",
|
|
32058
|
+
what: "A review waits for a person; node9's own timer is off.",
|
|
32059
|
+
why: "`settings.approvalTimeoutMs` is 0.",
|
|
32060
|
+
who: "With no one at the screen, the agent's hook timeout ends the review, and some agents then run the tool.",
|
|
32061
|
+
detail: [],
|
|
32062
|
+
owner: "node9",
|
|
32063
|
+
fix: "Set a real approval timeout (e.g. 300000 ms) or clear it to use the default."
|
|
32064
|
+
}
|
|
32065
|
+
] : [];
|
|
30968
32066
|
if (!hasChannel) {
|
|
30969
32067
|
return [
|
|
32068
|
+
...zeroTimeout,
|
|
30970
32069
|
{
|
|
30971
32070
|
category: "Approvals",
|
|
30972
32071
|
severity: "high",
|
|
@@ -30980,7 +32079,7 @@ function checkApprovalConfig(ctx) {
|
|
|
30980
32079
|
}
|
|
30981
32080
|
];
|
|
30982
32081
|
}
|
|
30983
|
-
return
|
|
32082
|
+
return zeroTimeout;
|
|
30984
32083
|
}
|
|
30985
32084
|
function checkToolGovernance(ctx) {
|
|
30986
32085
|
const cfg = getConfig(ctx.cwd);
|
|
@@ -33665,7 +34764,17 @@ var init_osv_sync = __esm({
|
|
|
33665
34764
|
init_zip();
|
|
33666
34765
|
init_osv_index();
|
|
33667
34766
|
init_net();
|
|
33668
|
-
ECOSYSTEMS2 = [
|
|
34767
|
+
ECOSYSTEMS2 = [
|
|
34768
|
+
"npm",
|
|
34769
|
+
"PyPI",
|
|
34770
|
+
"RubyGems",
|
|
34771
|
+
"crates.io",
|
|
34772
|
+
"Go",
|
|
34773
|
+
"NuGet",
|
|
34774
|
+
"Maven",
|
|
34775
|
+
"Packagist",
|
|
34776
|
+
"VSCode"
|
|
34777
|
+
];
|
|
33669
34778
|
SYNC_INTERVAL_MS2 = 6 * 60 * 60 * 1e3;
|
|
33670
34779
|
FIRST_SYNC_DELAY_MS = 2 * 60 * 1e3;
|
|
33671
34780
|
FULL_REBUILD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -34218,6 +35327,15 @@ function buildDaemonReport(allEntries, period, now) {
|
|
|
34218
35327
|
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
34219
35328
|
};
|
|
34220
35329
|
}
|
|
35330
|
+
function autoDenyMs() {
|
|
35331
|
+
const ms = getConfig().settings.approvalTimeoutMs;
|
|
35332
|
+
if (ms === 0) return null;
|
|
35333
|
+
return ms ?? AUTO_DENY_MS;
|
|
35334
|
+
}
|
|
35335
|
+
function armAutoDeny(fire) {
|
|
35336
|
+
const ms = autoDenyMs();
|
|
35337
|
+
return ms === null ? void 0 : setTimeout(fire, ms);
|
|
35338
|
+
}
|
|
34221
35339
|
function startDaemon() {
|
|
34222
35340
|
try {
|
|
34223
35341
|
autoMigrateLocalConfig();
|
|
@@ -34328,7 +35446,7 @@ data: ${JSON.stringify({
|
|
|
34328
35446
|
allowCount: (insightCounts.get(e.toolName) ?? 0) + 1
|
|
34329
35447
|
})),
|
|
34330
35448
|
orgName: getOrgName(),
|
|
34331
|
-
autoDenyMs:
|
|
35449
|
+
autoDenyMs: autoDenyMs()
|
|
34332
35450
|
})}
|
|
34333
35451
|
|
|
34334
35452
|
`
|
|
@@ -34391,7 +35509,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34391
35509
|
timestamp: Date.now(),
|
|
34392
35510
|
earlyDecision: null,
|
|
34393
35511
|
waiter: null,
|
|
34394
|
-
timer:
|
|
35512
|
+
timer: armAutoDeny(() => {
|
|
34395
35513
|
if (pending.has(id)) {
|
|
34396
35514
|
const e = pending.get(id);
|
|
34397
35515
|
appendAuditLog({
|
|
@@ -34399,6 +35517,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34399
35517
|
args: e.args,
|
|
34400
35518
|
decision: "auto-deny"
|
|
34401
35519
|
});
|
|
35520
|
+
e.decisionSource = "timeout";
|
|
34402
35521
|
if (e.waiter) e.waiter("deny", "No response \u2014 auto-denied after timeout");
|
|
34403
35522
|
else {
|
|
34404
35523
|
e.earlyDecision = "deny";
|
|
@@ -34407,7 +35526,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34407
35526
|
pending.delete(id);
|
|
34408
35527
|
broadcast("remove", { id, decision: "deny" });
|
|
34409
35528
|
}
|
|
34410
|
-
}
|
|
35529
|
+
})
|
|
34411
35530
|
};
|
|
34412
35531
|
pending.set(id, entry);
|
|
34413
35532
|
if (!fromCLI) {
|
|
@@ -34469,6 +35588,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34469
35588
|
clearTimeout(e.timer);
|
|
34470
35589
|
const decision = result.approved ? "allow" : "deny";
|
|
34471
35590
|
appendAuditLog({ toolName: e.toolName, args: e.args, decision });
|
|
35591
|
+
e.decisionSource = backgroundPassSource(result);
|
|
34472
35592
|
if (e.waiter) {
|
|
34473
35593
|
e.waiter(decision, result.reason);
|
|
34474
35594
|
pending.delete(id);
|
|
@@ -34583,8 +35703,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34583
35703
|
insightCounts.delete(entry.toolName);
|
|
34584
35704
|
saveInsightCounts();
|
|
34585
35705
|
}
|
|
34586
|
-
|
|
34587
|
-
if (source && VALID_SOURCES.has(source)) entry.decisionSource = source;
|
|
35706
|
+
if (source && DECISION_ROUTE_SOURCES.has(source)) entry.decisionSource = source;
|
|
34588
35707
|
if (entry.waiter) {
|
|
34589
35708
|
entry.waiter(resolvedDecision, reason);
|
|
34590
35709
|
pending.delete(id);
|
|
@@ -34722,8 +35841,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34722
35841
|
insightCounts.delete(entry.toolName);
|
|
34723
35842
|
saveInsightCounts();
|
|
34724
35843
|
}
|
|
34725
|
-
|
|
34726
|
-
if (source && VALID_RESOLVE_SOURCES.has(source)) entry.decisionSource = source;
|
|
35844
|
+
if (source && HUMAN_DECISION_SOURCES.has(source)) entry.decisionSource = source;
|
|
34727
35845
|
if (entry.waiter) entry.waiter(resolvedResolveDecision);
|
|
34728
35846
|
else entry.earlyDecision = resolvedResolveDecision;
|
|
34729
35847
|
pending.delete(id);
|
|
@@ -35387,6 +36505,7 @@ var init_server = __esm({
|
|
|
35387
36505
|
import_os50 = __toESM(require("os"));
|
|
35388
36506
|
import_crypto18 = require("crypto");
|
|
35389
36507
|
init_trusted_recipients();
|
|
36508
|
+
init_decision_sources();
|
|
35390
36509
|
import_child_process5 = require("child_process");
|
|
35391
36510
|
import_chalk7 = __toESM(require("chalk"));
|
|
35392
36511
|
init_core();
|
|
@@ -35645,14 +36764,14 @@ async function onboardMachine(apiKey, opts = {}) {
|
|
|
35645
36764
|
detail: `${sync.rules} cloud rule${sync.rules === 1 ? "" : "s"} active`
|
|
35646
36765
|
} : { name: "policy-sync", ok: false, detail: sync.reason }
|
|
35647
36766
|
);
|
|
35648
|
-
const
|
|
36767
|
+
const push2 = await runPolicyPush();
|
|
35649
36768
|
steps.push(
|
|
35650
|
-
|
|
36769
|
+
push2.ok ? { name: "register", ok: true, detail: "machine visible in the dashboard" } : { name: "register", ok: false, detail: push2.reason }
|
|
35651
36770
|
);
|
|
35652
36771
|
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
35653
36772
|
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)";
|
|
35654
36773
|
let daemonOk = true;
|
|
35655
|
-
if (sync.ok &&
|
|
36774
|
+
if (sync.ok && push2.ok && isInteractive() && mayChangeService({ stdoutTTY: !!process.stdout.isTTY, ci: isCI() }) && autostartState() === "absent") {
|
|
35656
36775
|
try {
|
|
35657
36776
|
if (await (0, import_prompts2.confirm)({
|
|
35658
36777
|
message: "Run node9 in the background when this computer starts?",
|
|
@@ -63931,101 +65050,7 @@ var import_child_process6 = require("child_process");
|
|
|
63931
65050
|
var import_execa = require("execa");
|
|
63932
65051
|
var import_execa2 = require("execa");
|
|
63933
65052
|
init_orchestrator();
|
|
63934
|
-
|
|
63935
|
-
// src/policy/negotiation.ts
|
|
63936
|
-
function buildNegotiationMessage(blockedByLabel, isHumanDecision, humanReason, recoveryCommand, context) {
|
|
63937
|
-
if (isHumanDecision) {
|
|
63938
|
-
return `NODE9: The human user rejected this action.
|
|
63939
|
-
REASON: ${humanReason || "No specific reason provided."}
|
|
63940
|
-
INSTRUCTIONS:
|
|
63941
|
-
- Do NOT retry this exact command.
|
|
63942
|
-
- Acknowledge the block to the user and ask if there is an alternative approach.
|
|
63943
|
-
- If you believe this action is critical, explain your reasoning and ask them to run "node9 pause 15m" to proceed.`;
|
|
63944
|
-
}
|
|
63945
|
-
if (context?.kind === "protected-path") {
|
|
63946
|
-
const subject = context.path ? `${context.path} is a protected file` : "This file is protected";
|
|
63947
|
-
return `NODE9: Blocked. ${subject}, and node9 stopped this call to it. The call did not run, so nothing was exposed.
|
|
63948
|
-
INSTRUCTIONS:
|
|
63949
|
-
- Do NOT retry, and do NOT reach the file another way (cat, grep, a copy, a script, a redirect).
|
|
63950
|
-
- If this step needs that file, ask the user to provide what you need or to do the step themselves.
|
|
63951
|
-
- Tell the user node9 protected this file, then ask how to proceed.`;
|
|
63952
|
-
}
|
|
63953
|
-
const label3 = blockedByLabel.toLowerCase();
|
|
63954
|
-
if (label3.includes("dlp") || label3.includes("secret detected") || label3.includes("credential review")) {
|
|
63955
|
-
return `NODE9 SECURITY ALERT: A sensitive credential (API key, token, or private key) was found in your tool call arguments.
|
|
63956
|
-
CRITICAL INSTRUCTION: Do NOT retry this action.
|
|
63957
|
-
REQUIRED ACTIONS:
|
|
63958
|
-
1. Remove the hardcoded credential from your command or code.
|
|
63959
|
-
2. Use an environment variable or a dedicated secrets manager instead.
|
|
63960
|
-
3. Treat the leaked credential as compromised and rotate it immediately.
|
|
63961
|
-
Do NOT attempt to bypass this check or pass the credential through another tool.
|
|
63962
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63963
|
-
}
|
|
63964
|
-
if (label3.includes("package check")) {
|
|
63965
|
-
return `NODE9: Blocked \u2014 ${humanReason || "a package in this command is known to be malicious."}
|
|
63966
|
-
INSTRUCTIONS:
|
|
63967
|
-
- Do NOT install this package, and do NOT fetch it another way (a different package manager, a tarball URL, a git URL, a mirror).
|
|
63968
|
-
- If a dependency is needed, suggest a well-known alternative and let the user choose.
|
|
63969
|
-
- Tell the user node9 blocked this install and name the advisory, then ask how to proceed.`;
|
|
63970
|
-
}
|
|
63971
|
-
if (label3.includes("sql safety") && label3.includes("delete without where")) {
|
|
63972
|
-
return `NODE9: Blocked \u2014 DELETE without WHERE clause would wipe the entire table.
|
|
63973
|
-
INSTRUCTION: Add a WHERE clause to scope the deletion (e.g. WHERE id = <value>).
|
|
63974
|
-
Do NOT retry without a WHERE clause.
|
|
63975
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63976
|
-
}
|
|
63977
|
-
if (label3.includes("sql safety") && label3.includes("update without where")) {
|
|
63978
|
-
return `NODE9: Blocked \u2014 UPDATE without WHERE clause would update every row.
|
|
63979
|
-
INSTRUCTION: Add a WHERE clause to scope the update (e.g. WHERE id = <value>).
|
|
63980
|
-
Do NOT retry without a WHERE clause.
|
|
63981
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63982
|
-
}
|
|
63983
|
-
if (label3.includes("dangerous word")) {
|
|
63984
|
-
const match = blockedByLabel.match(/dangerous word: "([^"]+)"/i);
|
|
63985
|
-
const word = match?.[1] ?? "a dangerous keyword";
|
|
63986
|
-
return `NODE9: Blocked \u2014 command contains forbidden keyword "${word}".
|
|
63987
|
-
INSTRUCTION: Do NOT use "${word}". Use a non-destructive alternative.
|
|
63988
|
-
Do NOT attempt to bypass this with shell tricks or aliases \u2014 it will be blocked again.
|
|
63989
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63990
|
-
}
|
|
63991
|
-
if (label3.includes("path blocked") || label3.includes("sandbox")) {
|
|
63992
|
-
return `NODE9: Blocked \u2014 operation targets a path outside the allowed sandbox.
|
|
63993
|
-
INSTRUCTION: Move your output to an allowed directory such as /tmp/ or the project directory.
|
|
63994
|
-
Do NOT retry on the same path.
|
|
63995
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
63996
|
-
}
|
|
63997
|
-
if (label3.includes("inline execution")) {
|
|
63998
|
-
return `NODE9: Blocked \u2014 inline code execution (e.g. bash -c "...") is not allowed.
|
|
63999
|
-
INSTRUCTION: Use individual tool calls instead of embedding code in a shell string.
|
|
64000
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
64001
|
-
}
|
|
64002
|
-
if (label3.includes("strict mode")) {
|
|
64003
|
-
return `NODE9: Blocked \u2014 strict mode is active. All tool calls require explicit human approval.
|
|
64004
|
-
INSTRUCTION: Inform the user this action is pending approval. Wait for them to approve via the dashboard or run "node9 pause".`;
|
|
64005
|
-
}
|
|
64006
|
-
if (label3.includes("rule") && label3.includes("default block")) {
|
|
64007
|
-
const match = blockedByLabel.match(/rule "([^"]+)"/i);
|
|
64008
|
-
const rule = match?.[1] ?? "a policy rule";
|
|
64009
|
-
return `NODE9: Blocked \u2014 action "${rule}" is forbidden by security policy.
|
|
64010
|
-
INSTRUCTION: Do NOT use "${rule}". Find a read-only or non-destructive alternative.
|
|
64011
|
-
Do NOT attempt to bypass this rule.
|
|
64012
|
-
- Tell the user node9 blocked this and why, then ask how to proceed.`;
|
|
64013
|
-
}
|
|
64014
|
-
const recovery = recoveryCommand ? `
|
|
64015
|
-
REQUIRED ACTION: Run \`${recoveryCommand}\` first, then retry your original command.` : "\n- Pivot to a non-destructive or read-only alternative.";
|
|
64016
|
-
return `NODE9: Action blocked by security policy [${blockedByLabel}].
|
|
64017
|
-
INSTRUCTIONS:
|
|
64018
|
-
- Do NOT retry this exact command or attempt to bypass the rule.${recovery}
|
|
64019
|
-
- Inform the user which security rule was triggered and ask how to proceed.`;
|
|
64020
|
-
}
|
|
64021
|
-
function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
64022
|
-
const why = ruleDescription || (reason && reason !== blockedByLabel ? reason : void 0) || blockedByLabel || "this action needs your review";
|
|
64023
|
-
const trimmed = why.trimEnd();
|
|
64024
|
-
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
64025
|
-
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
64026
|
-
}
|
|
64027
|
-
|
|
64028
|
-
// src/proxy/index.ts
|
|
65053
|
+
init_negotiation();
|
|
64029
65054
|
init_platform_shell();
|
|
64030
65055
|
init_safe_text();
|
|
64031
65056
|
async function runProxy(targetCommand) {
|
|
@@ -64073,7 +65098,7 @@ async function runProxy(targetCommand) {
|
|
|
64073
65098
|
console.error(import_chalk9.default.gray(` Reason: ${result.reason || "Security Policy"}
|
|
64074
65099
|
`));
|
|
64075
65100
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
64076
|
-
const isHuman =
|
|
65101
|
+
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
64077
65102
|
const aiInstruction = buildNegotiationMessage(
|
|
64078
65103
|
blockedByLabel,
|
|
64079
65104
|
isHuman,
|
|
@@ -64209,6 +65234,7 @@ init_daemon();
|
|
|
64209
65234
|
init_dist();
|
|
64210
65235
|
init_startup_log();
|
|
64211
65236
|
init_config();
|
|
65237
|
+
init_negotiation();
|
|
64212
65238
|
init_daemon_starter();
|
|
64213
65239
|
|
|
64214
65240
|
// src/skill-pin.ts
|
|
@@ -64754,7 +65780,7 @@ RAW: ${raw}
|
|
|
64754
65780
|
const mcpServer = mcpMatch?.[1];
|
|
64755
65781
|
const sendBlock = (msg, result2) => {
|
|
64756
65782
|
const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || (isKeyedForPolicy() ? "Workspace Policy" : "Local Security Policy");
|
|
64757
|
-
const
|
|
65783
|
+
const humanDecision = isHumanDecision(blockedByContext, result2?.blockedBy);
|
|
64758
65784
|
let ttyFd = null;
|
|
64759
65785
|
try {
|
|
64760
65786
|
ttyFd = import_fs66.default.openSync("/dev/tty", "w");
|
|
@@ -64791,7 +65817,7 @@ RAW: ${raw}
|
|
|
64791
65817
|
}
|
|
64792
65818
|
const aiFeedbackMessage = buildNegotiationMessage(
|
|
64793
65819
|
blockedByContext,
|
|
64794
|
-
|
|
65820
|
+
humanDecision,
|
|
64795
65821
|
msg,
|
|
64796
65822
|
result2?.recoveryCommand,
|
|
64797
65823
|
{ kind: result2?.blockKind, path: result2?.blockedPath }
|
|
@@ -65199,7 +66225,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65199
66225
|
let size = 0;
|
|
65200
66226
|
let fields = 0;
|
|
65201
66227
|
let truncated = false;
|
|
65202
|
-
const
|
|
66228
|
+
const push2 = (s) => {
|
|
65203
66229
|
if (s.length === 0) return true;
|
|
65204
66230
|
const room = maxBytes - size;
|
|
65205
66231
|
if (s.length > room) {
|
|
@@ -65217,7 +66243,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65217
66243
|
return true;
|
|
65218
66244
|
};
|
|
65219
66245
|
const walk2 = (v, depth) => {
|
|
65220
|
-
if (typeof v === "string") return
|
|
66246
|
+
if (typeof v === "string") return push2(v);
|
|
65221
66247
|
if (depth >= MAX_DEPTH5 || v === null || typeof v !== "object") return true;
|
|
65222
66248
|
const children = Array.isArray(v) ? v : Object.values(v);
|
|
65223
66249
|
for (const child of children) if (!walk2(child, depth + 1)) return false;
|
|
@@ -68384,6 +69410,7 @@ init_orchestrator();
|
|
|
68384
69410
|
init_config();
|
|
68385
69411
|
init_cloud();
|
|
68386
69412
|
init_config();
|
|
69413
|
+
init_negotiation();
|
|
68387
69414
|
init_provenance();
|
|
68388
69415
|
init_mcp_pin();
|
|
68389
69416
|
init_mcp_cmd();
|
|
@@ -68753,10 +69780,10 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68753
69780
|
console.error(import_chalk24.default.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
68754
69781
|
`));
|
|
68755
69782
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
68756
|
-
const
|
|
69783
|
+
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
68757
69784
|
const aiInstruction = buildNegotiationMessage(
|
|
68758
69785
|
blockedByLabel,
|
|
68759
|
-
|
|
69786
|
+
humanDecision,
|
|
68760
69787
|
result.reason,
|
|
68761
69788
|
void 0,
|
|
68762
69789
|
{ kind: result.blockKind, path: result.blockedPath }
|
|
@@ -75843,7 +76870,7 @@ function addHost(list, host2) {
|
|
|
75843
76870
|
if (!cliGuardPolicyWrite(`egress ${list} ${host2}`)) return false;
|
|
75844
76871
|
return guard(() => addEgressHost(list, host2));
|
|
75845
76872
|
}
|
|
75846
|
-
function
|
|
76873
|
+
function exempt2(address) {
|
|
75847
76874
|
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
75848
76875
|
return guard(() => addSsrfExemption(address));
|
|
75849
76876
|
}
|
|
@@ -76026,7 +77053,7 @@ function registerEgressCommand(program2) {
|
|
|
76026
77053
|
process.exitCode = 1;
|
|
76027
77054
|
return;
|
|
76028
77055
|
}
|
|
76029
|
-
if (!
|
|
77056
|
+
if (!exempt2(a)) return;
|
|
76030
77057
|
console.log(import_chalk34.default.green(`
|
|
76031
77058
|
\u2713 ${a} is exempt from the floor.`));
|
|
76032
77059
|
if (!m)
|
|
@@ -77815,17 +78842,18 @@ function registerSessionExposureCommand(program2) {
|
|
|
77815
78842
|
var import_chalk42 = __toESM(require("chalk"));
|
|
77816
78843
|
init_osv_index();
|
|
77817
78844
|
init_osv_sync();
|
|
78845
|
+
var ECO_WIDTH = Math.max(...ECOSYSTEMS2.map((e) => e.length));
|
|
77818
78846
|
function registerPackageIndexCommand(program2) {
|
|
77819
78847
|
const cmd = program2.command("package-index").description("Local OSV malicious-package index used to check installs before they run");
|
|
77820
78848
|
cmd.command("status").description("Show when each ecosystem was last synced and how many records it holds").action(() => {
|
|
77821
78849
|
for (const eco of ECOSYSTEMS2) {
|
|
77822
78850
|
const meta = readMeta(eco);
|
|
77823
78851
|
if (!meta) {
|
|
77824
|
-
console.log(`${eco.padEnd(
|
|
78852
|
+
console.log(`${eco.padEnd(ECO_WIDTH)} ${import_chalk42.default.yellow("not synced")}`);
|
|
77825
78853
|
continue;
|
|
77826
78854
|
}
|
|
77827
78855
|
console.log(
|
|
77828
|
-
`${eco.padEnd(
|
|
78856
|
+
`${eco.padEnd(ECO_WIDTH)} ${meta.records} records synced ${meta.syncedAt} (${meta.mode})`
|
|
77829
78857
|
);
|
|
77830
78858
|
}
|
|
77831
78859
|
});
|
|
@@ -77835,9 +78863,9 @@ function registerPackageIndexCommand(program2) {
|
|
|
77835
78863
|
for (const r of results) {
|
|
77836
78864
|
if (r.error) {
|
|
77837
78865
|
failed = true;
|
|
77838
|
-
console.error(`${r.ecosystem.padEnd(
|
|
78866
|
+
console.error(`${r.ecosystem.padEnd(ECO_WIDTH)} ${import_chalk42.default.red("failed")}: ${r.error}`);
|
|
77839
78867
|
} else {
|
|
77840
|
-
console.log(`${r.ecosystem.padEnd(
|
|
78868
|
+
console.log(`${r.ecosystem.padEnd(ECO_WIDTH)} ${r.mode}: ${r.records} records`);
|
|
77841
78869
|
}
|
|
77842
78870
|
}
|
|
77843
78871
|
process.exitCode = failed ? 1 : 0;
|