@node9/proxy 2.33.2 → 2.35.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 +2010 -603
- package/dist/cli.mjs +1985 -578
- package/dist/dashboard.mjs +266 -19
- package/dist/index.js +1380 -216
- package/dist/index.mjs +1380 -216
- package/dist/scan-ink.mjs +134 -11
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1173,6 +1173,120 @@ function expandPlainParam(p) {
|
|
|
1173
1173
|
if (recorded !== void 0) return recorded;
|
|
1174
1174
|
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
1175
1175
|
}
|
|
1176
|
+
function functionBodies(f) {
|
|
1177
|
+
const out = /* @__PURE__ */ new Map();
|
|
1178
|
+
syntax.Walk(f, (node) => {
|
|
1179
|
+
const n = node;
|
|
1180
|
+
if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1181
|
+
const list = out.get(n.Name.Value) ?? [];
|
|
1182
|
+
list.push(n.Body);
|
|
1183
|
+
out.set(n.Name.Value, list);
|
|
1184
|
+
}
|
|
1185
|
+
return true;
|
|
1186
|
+
});
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
function rebindsPositionals(body) {
|
|
1190
|
+
let found = false;
|
|
1191
|
+
syntax.Walk(body, (node) => {
|
|
1192
|
+
const n = node;
|
|
1193
|
+
if (found || !n) return false;
|
|
1194
|
+
if (syntax.NodeType(n) === "CallExpr") {
|
|
1195
|
+
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1196
|
+
if (words[0] === "shift") found = true;
|
|
1197
|
+
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
1198
|
+
found = true;
|
|
1199
|
+
}
|
|
1200
|
+
return !found;
|
|
1201
|
+
});
|
|
1202
|
+
return found;
|
|
1203
|
+
}
|
|
1204
|
+
function tableBindings() {
|
|
1205
|
+
if (!assignmentTable) return "";
|
|
1206
|
+
const out = [];
|
|
1207
|
+
for (const [name, rec] of assignmentTable)
|
|
1208
|
+
if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
|
|
1209
|
+
return out.sort().join("");
|
|
1210
|
+
}
|
|
1211
|
+
function walkWithCalls(f, visit) {
|
|
1212
|
+
const bodies = functionBodies(f);
|
|
1213
|
+
if (bodies.size === 0) {
|
|
1214
|
+
syntax.Walk(f, visit);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
1218
|
+
const inline = (call) => {
|
|
1219
|
+
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1220
|
+
const list = words[0] ? bodies.get(words[0]) : void 0;
|
|
1221
|
+
if (!list || callDepth >= MAX_CALL_DEPTH) return;
|
|
1222
|
+
const args = words.slice(1);
|
|
1223
|
+
const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
|
|
1224
|
+
if (seenCalls.has(key)) return;
|
|
1225
|
+
seenCalls.add(key);
|
|
1226
|
+
const outerTable = assignmentTable;
|
|
1227
|
+
const outerOffset = currentStmtOffset;
|
|
1228
|
+
for (const body of list) {
|
|
1229
|
+
if (callWalks >= MAX_CALL_WALKS) return;
|
|
1230
|
+
callWalks++;
|
|
1231
|
+
const table = /* @__PURE__ */ new Map();
|
|
1232
|
+
for (const [name, rec] of outerTable ?? [])
|
|
1233
|
+
if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
|
|
1234
|
+
for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
|
|
1235
|
+
table.delete(name);
|
|
1236
|
+
if (!rebindsPositionals(body)) {
|
|
1237
|
+
args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
|
|
1238
|
+
if (args.length === 1) {
|
|
1239
|
+
table.set("@", { value: args[0], at: -1 });
|
|
1240
|
+
table.set("*", { value: args[0], at: -1 });
|
|
1241
|
+
}
|
|
1242
|
+
table.set("#", { value: String(args.length), at: -1 });
|
|
1243
|
+
}
|
|
1244
|
+
assignmentTable = table;
|
|
1245
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1246
|
+
callDepth++;
|
|
1247
|
+
try {
|
|
1248
|
+
if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
|
|
1249
|
+
syntax.Walk(body, step);
|
|
1250
|
+
} finally {
|
|
1251
|
+
callDepth--;
|
|
1252
|
+
assignmentTable = outerTable;
|
|
1253
|
+
currentStmtOffset = outerOffset;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
const step = (node) => {
|
|
1258
|
+
if (!node) return false;
|
|
1259
|
+
const go = visit(node);
|
|
1260
|
+
if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
|
|
1261
|
+
return go;
|
|
1262
|
+
};
|
|
1263
|
+
syntax.Walk(f, step);
|
|
1264
|
+
}
|
|
1265
|
+
function withAssignments(f, body) {
|
|
1266
|
+
const outerTable = assignmentTable;
|
|
1267
|
+
const outerOffset = currentStmtOffset;
|
|
1268
|
+
const outerWalks = callWalks;
|
|
1269
|
+
callWalks = 0;
|
|
1270
|
+
assignmentTable = new Map(
|
|
1271
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
1272
|
+
);
|
|
1273
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1274
|
+
try {
|
|
1275
|
+
recordTopLevelAssignments(f);
|
|
1276
|
+
return body();
|
|
1277
|
+
} finally {
|
|
1278
|
+
assignmentTable = outerTable;
|
|
1279
|
+
currentStmtOffset = outerOffset;
|
|
1280
|
+
callWalks = outerWalks;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function trackStatement(n) {
|
|
1284
|
+
try {
|
|
1285
|
+
currentStmtOffset = n.Pos().Offset();
|
|
1286
|
+
} catch {
|
|
1287
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1176
1290
|
function resolveWordLiteral(w) {
|
|
1177
1291
|
const parts = w?.Parts || [];
|
|
1178
1292
|
let s = "";
|
|
@@ -1293,27 +1407,31 @@ function extractShellDestinations(command) {
|
|
|
1293
1407
|
const out = [];
|
|
1294
1408
|
const seen = /* @__PURE__ */ new Set();
|
|
1295
1409
|
try {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1410
|
+
withAssignments(
|
|
1411
|
+
f,
|
|
1412
|
+
() => walkWithCalls(f, (node) => {
|
|
1413
|
+
if (!node) return false;
|
|
1414
|
+
const n = node;
|
|
1415
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1416
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1417
|
+
const callArgs = n.Args || [];
|
|
1418
|
+
if (callArgs.length === 0) return true;
|
|
1419
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1420
|
+
const at = unwrapCommandHead(words);
|
|
1421
|
+
const name = (words[at] || "").toLowerCase().split("/").pop() ?? "";
|
|
1422
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1423
|
+
const rest = words.slice(at + 1);
|
|
1424
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1425
|
+
const host2 = parseDestHost(raw);
|
|
1426
|
+
if (!host2) continue;
|
|
1427
|
+
const key = `${name}:${host2}`;
|
|
1428
|
+
if (seen.has(key)) continue;
|
|
1429
|
+
seen.add(key);
|
|
1430
|
+
out.push({ host: host2, binary: name, raw });
|
|
1431
|
+
}
|
|
1432
|
+
return true;
|
|
1433
|
+
})
|
|
1434
|
+
);
|
|
1317
1435
|
} catch {
|
|
1318
1436
|
return out;
|
|
1319
1437
|
}
|
|
@@ -1325,39 +1443,45 @@ function extractShellDestTokens(command) {
|
|
|
1325
1443
|
const out = [];
|
|
1326
1444
|
const seen = /* @__PURE__ */ new Set();
|
|
1327
1445
|
try {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
tok = tok.
|
|
1446
|
+
withAssignments(
|
|
1447
|
+
f,
|
|
1448
|
+
() => walkWithCalls(f, (node) => {
|
|
1449
|
+
if (!node) return false;
|
|
1450
|
+
const n = node;
|
|
1451
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1452
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1453
|
+
const callArgs = n.Args || [];
|
|
1454
|
+
if (callArgs.length === 0) return true;
|
|
1455
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1456
|
+
const head = unwrapCommandHead(words);
|
|
1457
|
+
const name = (words[head] || "").toLowerCase().split("/").pop() ?? "";
|
|
1458
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1459
|
+
const rest = words.slice(head + 1);
|
|
1460
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1461
|
+
if (!raw) continue;
|
|
1462
|
+
let tok = raw.trim();
|
|
1463
|
+
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(tok);
|
|
1464
|
+
const hasScheme = scheme !== null;
|
|
1465
|
+
if (hasScheme) tok = tok.slice(scheme[0].length);
|
|
1466
|
+
tok = tok.split(/[/?#]/)[0];
|
|
1467
|
+
const at = tok.lastIndexOf("@");
|
|
1468
|
+
if (at >= 0) tok = tok.slice(at + 1);
|
|
1469
|
+
if (tok.startsWith("[")) {
|
|
1470
|
+
const close = tok.indexOf("]");
|
|
1471
|
+
if (close > 0) tok = tok.slice(0, close + 1);
|
|
1472
|
+
} else {
|
|
1473
|
+
tok = tok.split(":")[0];
|
|
1474
|
+
}
|
|
1475
|
+
if (!tok) continue;
|
|
1476
|
+
if (!hasScheme && /^\d+$/.test(tok) && Number(tok) < 16777216) continue;
|
|
1477
|
+
const key = `${name}:${tok}`;
|
|
1478
|
+
if (seen.has(key)) continue;
|
|
1479
|
+
seen.add(key);
|
|
1480
|
+
out.push({ token: tok, binary: name });
|
|
1351
1481
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (seen.has(key)) continue;
|
|
1356
|
-
seen.add(key);
|
|
1357
|
-
out.push({ token: tok, binary: name });
|
|
1358
|
-
}
|
|
1359
|
-
return true;
|
|
1360
|
-
});
|
|
1482
|
+
return true;
|
|
1483
|
+
})
|
|
1484
|
+
);
|
|
1361
1485
|
} catch {
|
|
1362
1486
|
return out;
|
|
1363
1487
|
}
|
|
@@ -1476,6 +1600,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1476
1600
|
if (depth === 0) {
|
|
1477
1601
|
seenPayloads = /* @__PURE__ */ new Set();
|
|
1478
1602
|
payloadBudget = PAYLOAD_BUDGET;
|
|
1603
|
+
if (callDepth === 0) callWalks = 0;
|
|
1479
1604
|
}
|
|
1480
1605
|
assignmentTable = new Map(
|
|
1481
1606
|
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
@@ -1483,7 +1608,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1483
1608
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1484
1609
|
try {
|
|
1485
1610
|
recordTopLevelAssignments(f);
|
|
1486
|
-
|
|
1611
|
+
walkWithCalls(f, (node) => {
|
|
1487
1612
|
if (!node || result?.verdict === "block") return false;
|
|
1488
1613
|
const n = node;
|
|
1489
1614
|
const nodeType = syntax.NodeType(n);
|
|
@@ -2272,6 +2397,19 @@ function runnablePrefix(command) {
|
|
|
2272
2397
|
}
|
|
2273
2398
|
return null;
|
|
2274
2399
|
}
|
|
2400
|
+
function uploadedFile(tool, flag, value) {
|
|
2401
|
+
const v = value.value;
|
|
2402
|
+
const file2 = (f) => f && f !== "-" && f !== "." ? { value: f } : null;
|
|
2403
|
+
if (tool === "curl") {
|
|
2404
|
+
if (["-d", "--data", "--data-ascii", "--data-binary", "--json"].includes(flag))
|
|
2405
|
+
return v.startsWith("@") ? file2(v.slice(1)) : null;
|
|
2406
|
+
if (flag === "--data-urlencode") return file2(/^[^=@]*@(.+)$/.exec(v)?.[1]);
|
|
2407
|
+
if (flag === "-F" || flag === "--form") return file2(/^[^=]*=[@<]([^;]+)/.exec(v)?.[1]);
|
|
2408
|
+
if (flag === "-T" || flag === "--upload-file") return file2(v);
|
|
2409
|
+
}
|
|
2410
|
+
if (tool === "wget" && ["--post-file", "--body-file"].includes(flag)) return file2(v);
|
|
2411
|
+
return null;
|
|
2412
|
+
}
|
|
2275
2413
|
function merge(a, b) {
|
|
2276
2414
|
a.cwd = unique([...a.cwd, ...b.cwd]);
|
|
2277
2415
|
for (const key of /* @__PURE__ */ new Set([...a.vars.keys(), ...b.vars.keys()])) {
|
|
@@ -2345,6 +2483,9 @@ function wordValues(word, state) {
|
|
|
2345
2483
|
function collectShellFileAccesses(command, context = {}) {
|
|
2346
2484
|
const out = [];
|
|
2347
2485
|
let payloads = 256;
|
|
2486
|
+
const functions = /* @__PURE__ */ new Map();
|
|
2487
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
2488
|
+
let callWalks2 = MAX_CALL_WALKS2;
|
|
2348
2489
|
const initial = {
|
|
2349
2490
|
cwd: [context.cwd ?? ""],
|
|
2350
2491
|
vars: /* @__PURE__ */ new Map([["HOME", [context.homeDirs?.[0] ?? "~"]]])
|
|
@@ -2602,8 +2743,98 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2602
2743
|
}
|
|
2603
2744
|
return;
|
|
2604
2745
|
}
|
|
2746
|
+
if (name === "wget" || name === "curl" || name === "aria2c") {
|
|
2747
|
+
downloadOutputs(name, args, state);
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
if (["http", "https", "xh", "xhs"].includes(name)) {
|
|
2751
|
+
for (const token of args) {
|
|
2752
|
+
if (!token || token.dynamic || /^[a-z][a-z0-9+.-]*:\/\//i.test(token.value)) continue;
|
|
2753
|
+
const m = /^(?:@|[^=:@\s]+@|[^=:@\s]*=@)(.+)$/.exec(token.value);
|
|
2754
|
+
if (m && m[1] !== "-") add("read", { value: m[1] }, state, { upload: true });
|
|
2755
|
+
}
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
if (name === "scp" || name === "rsync") {
|
|
2759
|
+
const operands2 = strings.filter((s) => s && !s.startsWith("-"));
|
|
2760
|
+
const remote = operands2.slice(1).some((o) => /^[^/\s]+:/.test(o));
|
|
2761
|
+
for (const p of copySourcePaths(words))
|
|
2762
|
+
add("read", locate(p), state, remote ? { upload: true } : {});
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2605
2765
|
for (const p of copySourcePaths(words)) add("read", locate(p), state);
|
|
2606
2766
|
}
|
|
2767
|
+
function downloadOutputs(name, args, state) {
|
|
2768
|
+
const valueShort = DOWNLOAD_SHORT_VALUE[name];
|
|
2769
|
+
const outputs = [];
|
|
2770
|
+
const urls = [];
|
|
2771
|
+
let dir2 = null;
|
|
2772
|
+
let remoteName = false;
|
|
2773
|
+
let document = false;
|
|
2774
|
+
let serverNamed = false;
|
|
2775
|
+
const take = (flag, value) => {
|
|
2776
|
+
if (!value) return;
|
|
2777
|
+
const sent = uploadedFile(name, flag, value);
|
|
2778
|
+
if (sent) add("read", sent, state, { upload: true });
|
|
2779
|
+
if (DOWNLOAD_OUTPUT_FLAGS[name].includes(flag)) {
|
|
2780
|
+
if (["-O", "--output-document", "--out"].includes(flag) || name === "aria2c" && flag === "-o")
|
|
2781
|
+
document = true;
|
|
2782
|
+
if (value.value !== "-" || value.pattern || value.dynamic) outputs.push(value);
|
|
2783
|
+
} else if (DOWNLOAD_DIR_FLAGS[name].includes(flag)) dir2 = value;
|
|
2784
|
+
else if (name === "wget" && (flag === "-i" || flag === "--input-file")) serverNamed = true;
|
|
2785
|
+
};
|
|
2786
|
+
for (let i = 0; i < args.length; i++) {
|
|
2787
|
+
const token = args[i];
|
|
2788
|
+
if (!token) continue;
|
|
2789
|
+
const v = token.value;
|
|
2790
|
+
if (v.startsWith("--")) {
|
|
2791
|
+
const eq = v.indexOf("=");
|
|
2792
|
+
const flag = eq < 0 ? v : v.slice(0, eq);
|
|
2793
|
+
if (["--remote-name", "--remote-name-all"].includes(flag)) remoteName = true;
|
|
2794
|
+
if (["--remote-header-name", "--recursive", "--mirror", "--page-requisites"].includes(flag))
|
|
2795
|
+
serverNamed = true;
|
|
2796
|
+
if (DOWNLOAD_LONG_VALUE[name].includes(flag))
|
|
2797
|
+
take(flag, eq < 0 ? args[++i] ?? null : { ...token, value: v.slice(eq + 1) });
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
if (v.startsWith("-") && v.length > 1 && !token.dynamic) {
|
|
2801
|
+
for (let j = 1; j < v.length; j++) {
|
|
2802
|
+
const c = v[j];
|
|
2803
|
+
if (name === "wget" && c === "n") {
|
|
2804
|
+
j++;
|
|
2805
|
+
continue;
|
|
2806
|
+
}
|
|
2807
|
+
if (name === "curl" && c === "O") remoteName = true;
|
|
2808
|
+
if (name === "curl" && c === "J") serverNamed = true;
|
|
2809
|
+
if (name === "wget" && (c === "r" || c === "m" || c === "p")) serverNamed = true;
|
|
2810
|
+
if (valueShort.includes(c)) {
|
|
2811
|
+
const rest = v.slice(j + 1);
|
|
2812
|
+
take(`-${c}`, rest ? { value: rest } : args[++i] ?? null);
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
if (/^(?:https?|ftps?):\/\//i.test(v)) urls.push(v);
|
|
2819
|
+
}
|
|
2820
|
+
for (const out2 of outputs) {
|
|
2821
|
+
const spelled = name === "aria2c" && dir2?.value && !out2.value.startsWith("/") ? { ...out2, value: `${dir2.value.replace(/\/+$/, "")}/${out2.value}` } : out2;
|
|
2822
|
+
if (name === "curl" && /#\d/.test(spelled.value))
|
|
2823
|
+
add("write", { value: "", pattern: spelled.value.replace(/#\d+/g, "*"), dot: true }, state);
|
|
2824
|
+
else add("write", spelled, state);
|
|
2825
|
+
}
|
|
2826
|
+
const remote = name === "curl" ? remoteName || serverNamed : !document || serverNamed;
|
|
2827
|
+
if (!remote) return;
|
|
2828
|
+
const into = dir2?.value ? dir2.value.replace(/\/+$/, "") : ".";
|
|
2829
|
+
if (serverNamed || urls.length === 0) {
|
|
2830
|
+
add("write", { value: "", pattern: `${quoteGlob(into)}/*`, dot: true }, state);
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
for (const url of urls) {
|
|
2834
|
+
const file2 = url.replace(/[?#].*$/, "").replace(/^[a-z]+:\/\/[^/]*/i, "").split("/").pop();
|
|
2835
|
+
add("write", { value: `${into}/${file2 || "index.html"}` }, state);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2607
2838
|
function findActions2(tokens, words, head, state, nested, depth) {
|
|
2608
2839
|
const starts = findStartPoints(words, head).starts;
|
|
2609
2840
|
const roots = (starts.length ? starts : ["."]).slice(0, MAX_ALTERNATIVES);
|
|
@@ -2675,7 +2906,35 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2675
2906
|
const words = wordValues(node, state);
|
|
2676
2907
|
alternatives = alternatives.flatMap((prefix) => (words.length ? words : [null]).map((w) => [...prefix, w])).slice(0, MAX_ALTERNATIVES);
|
|
2677
2908
|
}
|
|
2678
|
-
for (const tokens of alternatives)
|
|
2909
|
+
for (const tokens of alternatives) {
|
|
2910
|
+
fromWords(tokens, state, nested, depth);
|
|
2911
|
+
inlineCall(tokens, state, depth);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
function inlineCall(tokens, state, depth) {
|
|
2915
|
+
const head = tokens[0];
|
|
2916
|
+
if (!head || head.dynamic || head.pattern) return;
|
|
2917
|
+
const bodies = functions.get(head.value);
|
|
2918
|
+
if (!bodies || (state.callDepth ?? 0) >= MAX_CALL_DEPTH2) return;
|
|
2919
|
+
const args = tokens.slice(1).map((a) => a && !a.dynamic ? a.value : null);
|
|
2920
|
+
const vars = JSON.stringify([...state.vars].sort(([a], [b]) => a < b ? -1 : 1));
|
|
2921
|
+
const key = `${head.value}\0${JSON.stringify(args)}\0${state.cwd.join("")}\0${vars}`;
|
|
2922
|
+
if (seenCalls.has(key)) return;
|
|
2923
|
+
seenCalls.add(key);
|
|
2924
|
+
for (const body of bodies) {
|
|
2925
|
+
if (callWalks2-- <= 0) return;
|
|
2926
|
+
const local = clone(state);
|
|
2927
|
+
local.callDepth = (state.callDepth ?? 0) + 1;
|
|
2928
|
+
for (const name of POSITIONAL) local.vars.delete(name);
|
|
2929
|
+
if (!rebindsPositionals(body)) {
|
|
2930
|
+
args.slice(0, 9).forEach((v, i) => local.vars.set(String(i + 1), v === null ? null : [v]));
|
|
2931
|
+
const all = args.every((v) => v !== null) && args.length ? unique(args) : null;
|
|
2932
|
+
local.vars.set("@", all);
|
|
2933
|
+
local.vars.set("*", all);
|
|
2934
|
+
local.vars.set("#", [String(args.length)]);
|
|
2935
|
+
}
|
|
2936
|
+
statement(body, local, depth + 1);
|
|
2937
|
+
}
|
|
2679
2938
|
}
|
|
2680
2939
|
function readsScriptFromStdin(stmt) {
|
|
2681
2940
|
const cmd = stmt?.Cmd;
|
|
@@ -2712,11 +2971,13 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2712
2971
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
2713
2972
|
state.onSuccess = void 0;
|
|
2714
2973
|
if (stmt.Background) state = clone(state);
|
|
2974
|
+
const headWord = stmt.Cmd && syntax2.NodeType(stmt.Cmd) === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
2975
|
+
const sends = SENDING_STDIN.has(headWord);
|
|
2715
2976
|
for (const redir of stmt.Redirs ?? []) {
|
|
2716
2977
|
substitutions(redir.Word, state, depth);
|
|
2717
2978
|
for (const word of wordValues(redir.Word, state)) {
|
|
2718
2979
|
if (WRITES.has(redir.Op)) add("write", word, state);
|
|
2719
|
-
if (READS.has(redir.Op)) add("read", word, state);
|
|
2980
|
+
if (READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
|
|
2720
2981
|
if (redir.Op === DUP_OUT && word.value && !/^\d+$/.test(word.value))
|
|
2721
2982
|
add("write", word, state);
|
|
2722
2983
|
}
|
|
@@ -2825,7 +3086,10 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2825
3086
|
}
|
|
2826
3087
|
const parsed = parseShared(command);
|
|
2827
3088
|
const program = typeof parsed !== "symbol" ? parsed : runnablePrefix(command);
|
|
2828
|
-
if (program)
|
|
3089
|
+
if (program) {
|
|
3090
|
+
for (const [name, bodies] of functionBodies(program)) functions.set(name, bodies);
|
|
3091
|
+
statements(program.Stmts ?? [], initial, 0);
|
|
3092
|
+
}
|
|
2829
3093
|
return out;
|
|
2830
3094
|
}
|
|
2831
3095
|
function isAgentInstructionPath(absPath, root) {
|
|
@@ -2941,6 +3205,33 @@ function hits(access, paths, directories, context) {
|
|
|
2941
3205
|
}
|
|
2942
3206
|
return false;
|
|
2943
3207
|
}
|
|
3208
|
+
function hitsSystemDir(access, context, dirs = SYSTEM_DIRS) {
|
|
3209
|
+
const home = context.homeDirs?.[0] ?? "~";
|
|
3210
|
+
const candidates = access.candidates ?? [
|
|
3211
|
+
normalizeFilePath(access.path, access.cwd ?? context.cwd, home)
|
|
3212
|
+
];
|
|
3213
|
+
for (const p of candidates) {
|
|
3214
|
+
if (dirs.some((d) => within(p, d)) && !exempt(p)) return true;
|
|
3215
|
+
if (access.recursive && dirs.some((d) => within(d, p))) return true;
|
|
3216
|
+
}
|
|
3217
|
+
if (!access.glob) return false;
|
|
3218
|
+
const pattern = normalizeFilePath(access.glob, access.cwd ?? context.cwd, home);
|
|
3219
|
+
const magic = pattern.search(/[*?[{]/);
|
|
3220
|
+
const literal = magic < 0 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", magic) + 1);
|
|
3221
|
+
if (literal && exempt(literal.replace(/\/$/, "") || "/")) return false;
|
|
3222
|
+
const options = { dot: access.dotGlob === true, nonegate: true };
|
|
3223
|
+
const components = pattern.split("/");
|
|
3224
|
+
for (const root of dirs) {
|
|
3225
|
+
const count = root.split("/").length;
|
|
3226
|
+
if (components.length > count && picomatch(components.slice(0, count).join("/"), options)(root))
|
|
3227
|
+
return true;
|
|
3228
|
+
if (access.recursive && picomatch(pattern, options)(root)) return true;
|
|
3229
|
+
}
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
3232
|
+
function uploadDirs(context) {
|
|
3233
|
+
return context.homeDirs?.[0] === "/root" ? UPLOAD_SYSTEM_DIRS.filter((d) => d !== "/root") : UPLOAD_SYSTEM_DIRS;
|
|
3234
|
+
}
|
|
2944
3235
|
function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
2945
3236
|
const homes = [.../* @__PURE__ */ new Set([...context.homeDirs ?? ["~"], "/root"])];
|
|
2946
3237
|
const startup = homes.flatMap(
|
|
@@ -2962,6 +3253,14 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2962
3253
|
"files.shell-startup",
|
|
2963
3254
|
"review-shell-startup-mutation",
|
|
2964
3255
|
"Changing shell startup files requires approval"
|
|
3256
|
+
] : mutation && hitsSystemDir(access, context) ? [
|
|
3257
|
+
"files.system-dirs",
|
|
3258
|
+
"review-system-dir-mutation",
|
|
3259
|
+
"Changing files in a system directory requires approval"
|
|
3260
|
+
] : !mutation && access.upload && hitsSystemDir(access, context, uploadDirs(context)) ? [
|
|
3261
|
+
"files.system-dirs",
|
|
3262
|
+
"review-system-file-upload",
|
|
3263
|
+
"Uploading a system file requires approval"
|
|
2965
3264
|
] : !mutation && hits(access, PASSWORD_HASH_FILES, [], context) ? [
|
|
2966
3265
|
"files.password-hashes",
|
|
2967
3266
|
"review-password-hash-read",
|
|
@@ -3222,16 +3521,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
|
|
|
3222
3521
|
if (EMAIL_VALUE_RE.test(value)) return false;
|
|
3223
3522
|
if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
|
|
3224
3523
|
if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
|
|
3524
|
+
if (CONCATENATION_RE.test(value)) return false;
|
|
3225
3525
|
if (rejectedByStructuredValidator(value)) return false;
|
|
3226
3526
|
return shannonEntropy(value) >= minEntropy;
|
|
3227
3527
|
}
|
|
3228
3528
|
function nameHoldsCredential(name) {
|
|
3229
3529
|
return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
|
|
3230
3530
|
}
|
|
3531
|
+
function isOtpDocSecret(value) {
|
|
3532
|
+
return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
|
|
3533
|
+
}
|
|
3231
3534
|
function validateQueryCredential(raw) {
|
|
3232
3535
|
const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
|
|
3233
3536
|
if (value === null) return false;
|
|
3234
|
-
if (OTP_SECRET_RE.test(value))
|
|
3537
|
+
if (OTP_SECRET_RE.test(value))
|
|
3538
|
+
return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
|
|
3235
3539
|
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
|
|
3236
3540
|
return looksGenerated(value, 12, 3);
|
|
3237
3541
|
}
|
|
@@ -3245,13 +3549,40 @@ function keywordEndsAPart(name) {
|
|
|
3245
3549
|
}
|
|
3246
3550
|
function validateAssignedSecret(raw, before = "") {
|
|
3247
3551
|
const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
|
|
3248
|
-
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
|
|
3552
|
+
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
|
|
3249
3553
|
if (value === null || name === null) return false;
|
|
3250
|
-
if (!keywordEndsAPart(name)
|
|
3251
|
-
|
|
3554
|
+
if (!keywordEndsAPart(name)) return false;
|
|
3555
|
+
if (ARN_CONTEXT_RE.test(before)) return false;
|
|
3556
|
+
const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
|
|
3252
3557
|
if (!nameHoldsCredential(prefix + name)) return false;
|
|
3253
3558
|
return looksGenerated(value, 12, 3.5);
|
|
3254
3559
|
}
|
|
3560
|
+
function validateBasicCredential(raw) {
|
|
3561
|
+
const b64 = capturedValue(raw, BASIC_VALUE_RE);
|
|
3562
|
+
if (b64 === null) return false;
|
|
3563
|
+
const bare = b64.replace(/=+$/, "");
|
|
3564
|
+
if (bare.length % 4 === 1) return false;
|
|
3565
|
+
const bytes = Buffer.from(b64, "base64");
|
|
3566
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
|
|
3567
|
+
const text = bytes.toString("utf8");
|
|
3568
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) return false;
|
|
3569
|
+
for (let i = 0; i < text.length; i++) {
|
|
3570
|
+
const c = text.charCodeAt(i);
|
|
3571
|
+
if (c < 32 || c === 127) return false;
|
|
3572
|
+
}
|
|
3573
|
+
const pair = BASIC_PAIR_RE.exec(text);
|
|
3574
|
+
if (!pair) return false;
|
|
3575
|
+
const password = pair[2];
|
|
3576
|
+
if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
|
|
3577
|
+
const lower2 = password.toLowerCase();
|
|
3578
|
+
if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
|
|
3579
|
+
return shannonEntropy(password) >= 3;
|
|
3580
|
+
}
|
|
3581
|
+
function validateOtpSecret(raw) {
|
|
3582
|
+
const secret = capturedValue(raw, OTP_CAPTURE_RE);
|
|
3583
|
+
if (secret === null || isOtpDocSecret(secret)) return false;
|
|
3584
|
+
return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
|
|
3585
|
+
}
|
|
3255
3586
|
function matchSensitivePath2(resolvedPath, originalPath, options = {}) {
|
|
3256
3587
|
const normalised = resolvedPath.replace(/\\/g, "/");
|
|
3257
3588
|
if (!options.systemAccessHandled && isLegacySystemPath(normalised))
|
|
@@ -3522,6 +3853,156 @@ function parsePySpec(raw) {
|
|
|
3522
3853
|
const exact = PY_EXACT_RE.exec(m[2].trim());
|
|
3523
3854
|
return { name, version: exact ? exact[1] : void 0 };
|
|
3524
3855
|
}
|
|
3856
|
+
function exactVersion(v) {
|
|
3857
|
+
if (!v) return void 0;
|
|
3858
|
+
const m = EXACT_VERSION_RE.exec(v.trim());
|
|
3859
|
+
return m ? m[1] : void 0;
|
|
3860
|
+
}
|
|
3861
|
+
function push(out, ecosystem, manager, name, version, raw) {
|
|
3862
|
+
out.push({ ecosystem, manager, name, ...version ? { version } : {}, raw });
|
|
3863
|
+
}
|
|
3864
|
+
function gemInstall(words, from, out) {
|
|
3865
|
+
const dash = words.indexOf("--", from);
|
|
3866
|
+
const own = dash < 0 ? words : words.slice(0, dash);
|
|
3867
|
+
const pinned = exactVersion(flagValues(own, from, ["-v", "--version"])[0]);
|
|
3868
|
+
for (const raw of operands(own, from, GEM_VALUE_FLAGS)) {
|
|
3869
|
+
if (NON_REGISTRY_RE.test(raw) || /\.gem$/i.test(raw)) continue;
|
|
3870
|
+
const colon = raw.indexOf(":");
|
|
3871
|
+
const name = colon < 0 ? raw : raw.slice(0, colon);
|
|
3872
|
+
if (!GEM_NAME_RE.test(name)) continue;
|
|
3873
|
+
push(
|
|
3874
|
+
out,
|
|
3875
|
+
"RubyGems",
|
|
3876
|
+
"gem",
|
|
3877
|
+
name,
|
|
3878
|
+
colon < 0 ? pinned : exactVersion(raw.slice(colon + 1)),
|
|
3879
|
+
raw
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
function cargoCrates(words, from, manager, out) {
|
|
3884
|
+
const flags = manager === "cargo add" ? CARGO_ADD_VALUE_FLAGS : CARGO_INSTALL_VALUE_FLAGS;
|
|
3885
|
+
if (flagValues(words, from, ["--git", "--path", "--registry", "--index"]).length > 0) return;
|
|
3886
|
+
if (words.some((w) => w === "--git" || w === "--path")) return;
|
|
3887
|
+
const flagVersion = manager === "cargo install" ? exactVersion(flagValues(words, from, ["--version", "--vers"])[0]) : void 0;
|
|
3888
|
+
for (const raw of operands(words, from, flags)) {
|
|
3889
|
+
const at = raw.indexOf("@");
|
|
3890
|
+
const name = at < 0 ? raw : raw.slice(0, at);
|
|
3891
|
+
if (!CRATE_NAME_RE.test(name)) continue;
|
|
3892
|
+
push(
|
|
3893
|
+
out,
|
|
3894
|
+
"crates.io",
|
|
3895
|
+
manager,
|
|
3896
|
+
name,
|
|
3897
|
+
at < 0 ? flagVersion : exactVersion(raw.slice(at + 1)),
|
|
3898
|
+
raw
|
|
3899
|
+
);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
function goModules(words, from, manager, out) {
|
|
3903
|
+
let specs = operands(words, from, GO_VALUE_FLAGS);
|
|
3904
|
+
if (manager === "go run") specs = specs.slice(0, 1).filter((s) => s.includes("@"));
|
|
3905
|
+
for (const raw of specs) {
|
|
3906
|
+
const at = raw.indexOf("@");
|
|
3907
|
+
const pathPart = (at < 0 ? raw : raw.slice(0, at)).replace(/\/\.\.\.$/, "");
|
|
3908
|
+
const ver = at < 0 ? void 0 : raw.slice(at + 1);
|
|
3909
|
+
if (ver === "none") continue;
|
|
3910
|
+
if (!GO_PATH_RE.test(pathPart) || /\.go$/i.test(pathPart)) continue;
|
|
3911
|
+
const exact = ver && /^v\d/.test(ver) ? exactVersion(ver) : void 0;
|
|
3912
|
+
push(out, "Go", manager, pathPart, exact, raw);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function dotnetAdd(words, from, out) {
|
|
3916
|
+
let k = -1;
|
|
3917
|
+
for (let i = from; i < Math.min(words.length, from + 2); i++) {
|
|
3918
|
+
if (lower(words[i]) === "package") {
|
|
3919
|
+
k = i;
|
|
3920
|
+
break;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
if (k < 0) return;
|
|
3924
|
+
const name = operands(words, k + 1, DOTNET_ADD_VALUE_FLAGS)[0];
|
|
3925
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
3926
|
+
const version = exactVersion(flagValues(words, k + 1, ["-v", "--version"])[0]);
|
|
3927
|
+
push(out, "NuGet", "dotnet add package", name, version, name);
|
|
3928
|
+
}
|
|
3929
|
+
function dotnetTool(words, from, out) {
|
|
3930
|
+
const name = operands(words, from, DOTNET_TOOL_VALUE_FLAGS)[0];
|
|
3931
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
3932
|
+
const version = exactVersion(flagValues(words, from, ["--version"])[0]);
|
|
3933
|
+
push(out, "NuGet", "dotnet tool", name, version, name);
|
|
3934
|
+
}
|
|
3935
|
+
function nugetInstall(words, from, out) {
|
|
3936
|
+
const lowered = words.map((w) => w !== null && w.startsWith("-") ? w.toLowerCase() : w);
|
|
3937
|
+
const name = operands(lowered, from, NUGET_VALUE_FLAGS)[0];
|
|
3938
|
+
if (!name || !NUGET_NAME_RE.test(name) || /\.config$/i.test(name)) return;
|
|
3939
|
+
const version = exactVersion(flagValues(lowered, from, ["-version"])[0]);
|
|
3940
|
+
push(out, "NuGet", "nuget install", name, version, name);
|
|
3941
|
+
}
|
|
3942
|
+
function mavenGet(words, from, out) {
|
|
3943
|
+
const goal = words.slice(from).some(
|
|
3944
|
+
(w) => w !== null && /(?:^|:)dependency:get$|^org\.apache\.maven\.plugins:maven-dependency-plugin:(?:[\w.-]+:)?get$/.test(
|
|
3945
|
+
w
|
|
3946
|
+
)
|
|
3947
|
+
);
|
|
3948
|
+
if (!goal) return;
|
|
3949
|
+
const prop = (key) => {
|
|
3950
|
+
for (let i = from; i < words.length; i++) {
|
|
3951
|
+
const w = words[i];
|
|
3952
|
+
if (w === null) continue;
|
|
3953
|
+
if (w.startsWith(`-D${key}=`)) return w.slice(key.length + 3);
|
|
3954
|
+
if (w === "-D" && words[i + 1]?.startsWith(`${key}=`))
|
|
3955
|
+
return words[i + 1].slice(key.length + 1);
|
|
3956
|
+
}
|
|
3957
|
+
return void 0;
|
|
3958
|
+
};
|
|
3959
|
+
const artifact = prop("artifact");
|
|
3960
|
+
let coord;
|
|
3961
|
+
let ver;
|
|
3962
|
+
if (artifact) {
|
|
3963
|
+
const parts = artifact.split(":");
|
|
3964
|
+
if (parts.length < 2) return;
|
|
3965
|
+
coord = `${parts[0]}:${parts[1]}`;
|
|
3966
|
+
ver = parts[2];
|
|
3967
|
+
} else {
|
|
3968
|
+
const g = prop("groupId");
|
|
3969
|
+
const a = prop("artifactId");
|
|
3970
|
+
if (!g || !a) return;
|
|
3971
|
+
coord = `${g}:${a}`;
|
|
3972
|
+
ver = prop("version");
|
|
3973
|
+
}
|
|
3974
|
+
if (!MAVEN_COORD_RE.test(coord)) return;
|
|
3975
|
+
const exact = ver && !/^(?:LATEST|RELEASE)$/i.test(ver) ? exactVersion(ver) : void 0;
|
|
3976
|
+
push(out, "Maven", "mvn dependency:get", coord, exact, artifact ?? coord);
|
|
3977
|
+
}
|
|
3978
|
+
function composerPackages(words, from, manager, out) {
|
|
3979
|
+
const ops = operands(words, from, COMPOSER_VALUE_FLAGS);
|
|
3980
|
+
const specs = manager === "composer create-project" ? ops.slice(0, 1) : ops;
|
|
3981
|
+
for (const raw of specs) {
|
|
3982
|
+
const sep = raw.search(/[:=]/);
|
|
3983
|
+
const name = sep < 0 ? raw : raw.slice(0, sep);
|
|
3984
|
+
if (!COMPOSER_NAME_RE.test(name)) continue;
|
|
3985
|
+
let ver = sep < 0 ? void 0 : raw.slice(sep + 1);
|
|
3986
|
+
if (manager === "composer create-project" && ver === void 0) ver = ops[2] ?? void 0;
|
|
3987
|
+
push(out, "Packagist", manager, name.toLowerCase(), exactVersion(ver), raw);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
function editorExtensions(words, from, out) {
|
|
3991
|
+
for (const raw of flagValues(words, from, ["--install-extension"])) {
|
|
3992
|
+
if (/\.vsix$/i.test(raw) || NON_REGISTRY_RE.test(raw)) continue;
|
|
3993
|
+
const at = raw.lastIndexOf("@");
|
|
3994
|
+
const name = at <= 0 ? raw : raw.slice(0, at);
|
|
3995
|
+
if (!EXTENSION_ID_RE.test(name)) continue;
|
|
3996
|
+
push(
|
|
3997
|
+
out,
|
|
3998
|
+
"VSCode",
|
|
3999
|
+
"code --install-extension",
|
|
4000
|
+
name,
|
|
4001
|
+
at <= 0 ? void 0 : exactVersion(raw.slice(at + 1)),
|
|
4002
|
+
raw
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
3525
4006
|
function basename(w) {
|
|
3526
4007
|
return (w ?? "").toLowerCase().split("/").pop() ?? "";
|
|
3527
4008
|
}
|
|
@@ -3753,7 +4234,63 @@ function fromCall(words, out, depth) {
|
|
|
3753
4234
|
pushPy(out, "pipenv", operands(words, v + 1, PIPENV_VALUE_FLAGS));
|
|
3754
4235
|
return;
|
|
3755
4236
|
}
|
|
4237
|
+
case "gem": {
|
|
4238
|
+
const v = verbIndex(words, start + 1, GEM_VALUE_FLAGS);
|
|
4239
|
+
if (v >= 0 && ["install", "i"].includes(lower(words[v]))) gemInstall(words, v + 1, out);
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
4242
|
+
case "bundle": {
|
|
4243
|
+
const v = verbIndex(words, start + 1, BUNDLE_VALUE_FLAGS);
|
|
4244
|
+
if (v < 0 || lower(words[v]) !== "add") return;
|
|
4245
|
+
if (flagValues(words, v + 1, ["--git", "--github", "--path"]).length > 0) return;
|
|
4246
|
+
const version = exactVersion(flagValues(words, v + 1, ["-v", "--version"])[0]);
|
|
4247
|
+
for (const raw of operands(words, v + 1, BUNDLE_VALUE_FLAGS))
|
|
4248
|
+
if (GEM_NAME_RE.test(raw)) push(out, "RubyGems", "bundle add", raw, version, raw);
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
4251
|
+
case "cargo": {
|
|
4252
|
+
const from = words[start + 1]?.startsWith("+") ? start + 2 : start + 1;
|
|
4253
|
+
const v = verbIndex(words, from, CARGO_ADD_VALUE_FLAGS);
|
|
4254
|
+
const verb = lower(words[v]);
|
|
4255
|
+
if (verb === "add") cargoCrates(words, v + 1, "cargo add", out);
|
|
4256
|
+
else if (verb === "install") cargoCrates(words, v + 1, "cargo install", out);
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
case "go": {
|
|
4260
|
+
const v = verbIndex(words, start + 1, GO_VALUE_FLAGS);
|
|
4261
|
+
const verb = lower(words[v]);
|
|
4262
|
+
if (verb === "get" || verb === "install" || verb === "run")
|
|
4263
|
+
goModules(words, v + 1, `go ${verb}`, out);
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
case "dotnet": {
|
|
4267
|
+
const v = verbIndex(words, start + 1, DOTNET_ADD_VALUE_FLAGS);
|
|
4268
|
+
const verb = lower(words[v]);
|
|
4269
|
+
if (verb === "add") dotnetAdd(words, v + 1, out);
|
|
4270
|
+
else if (verb === "tool" && ["install", "update"].includes(lower(words[v + 1])))
|
|
4271
|
+
dotnetTool(words, v + 2, out);
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
case "nuget": {
|
|
4275
|
+
if (lower(words[start + 1]) === "install") nugetInstall(words, start + 2, out);
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4278
|
+
case "mvn":
|
|
4279
|
+
case "mvnw":
|
|
4280
|
+
mavenGet(words, start + 1, out);
|
|
4281
|
+
return;
|
|
4282
|
+
case "composer": {
|
|
4283
|
+
let v = verbIndex(words, start + 1, COMPOSER_VALUE_FLAGS);
|
|
4284
|
+
if (v >= 0 && lower(words[v]) === "global") v = verbIndex(words, v + 1, COMPOSER_VALUE_FLAGS);
|
|
4285
|
+
const verb = lower(words[v]);
|
|
4286
|
+
if (verb === "require" || verb === "req" || verb === "r")
|
|
4287
|
+
composerPackages(words, v + 1, "composer require", out);
|
|
4288
|
+
else if (verb === "create-project")
|
|
4289
|
+
composerPackages(words, v + 1, "composer create-project", out);
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
3756
4292
|
default:
|
|
4293
|
+
if (EDITOR_HEADS.includes(family)) editorExtensions(words, start + 1, out);
|
|
3757
4294
|
return;
|
|
3758
4295
|
}
|
|
3759
4296
|
}
|
|
@@ -5258,11 +5795,11 @@ function isDecimalFraction(text, index, unseparated) {
|
|
|
5258
5795
|
const a = text[index - 2];
|
|
5259
5796
|
if (sep === ".") {
|
|
5260
5797
|
if (a !== void 0 && a >= "0" && a <= "9") return true;
|
|
5261
|
-
return a === void 0 ||
|
|
5798
|
+
return a === void 0 || (a === "-" || a === "+") && index === 2;
|
|
5262
5799
|
}
|
|
5263
5800
|
if (sep === "," && a === "0") {
|
|
5264
5801
|
const b = text[index - 3];
|
|
5265
|
-
return b === void 0 ||
|
|
5802
|
+
return b === void 0 || b === ";";
|
|
5266
5803
|
}
|
|
5267
5804
|
return false;
|
|
5268
5805
|
}
|
|
@@ -5936,7 +6473,7 @@ function matchCanaryArgs(args, values) {
|
|
|
5936
6473
|
return null;
|
|
5937
6474
|
}
|
|
5938
6475
|
}
|
|
5939
|
-
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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, 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, 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,
|
|
6476
|
+
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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, 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, 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, LOOP_MAX_RECORDS, 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;
|
|
5940
6477
|
var init_dist = __esm({
|
|
5941
6478
|
"packages/policy-engine/dist/index.mjs"() {
|
|
5942
6479
|
"use strict";
|
|
@@ -6737,6 +7274,11 @@ var init_dist = __esm({
|
|
|
6737
7274
|
example: "cat /etc/shadow",
|
|
6738
7275
|
advice: "Keep Review, or choose Block when agents must never read system password hashes."
|
|
6739
7276
|
},
|
|
7277
|
+
"files.system-dirs": {
|
|
7278
|
+
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.",
|
|
7279
|
+
example: "wget -O /usr/bin/tool https://example.com/tool",
|
|
7280
|
+
advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
|
|
7281
|
+
},
|
|
6740
7282
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
6741
7283
|
"commands.inline-exec": {
|
|
6742
7284
|
plain: "The agent runs a small program written inside the command itself.",
|
|
@@ -6933,7 +7475,7 @@ var init_dist = __esm({
|
|
|
6933
7475
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
6934
7476
|
},
|
|
6935
7477
|
"loading.malicious-package": {
|
|
6936
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
|
|
7478
|
+
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.",
|
|
6937
7479
|
example: "npm install of a package flagged as malware",
|
|
6938
7480
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
6939
7481
|
}
|
|
@@ -6971,6 +7513,15 @@ var init_dist = __esm({
|
|
|
6971
7513
|
defaultValue: "review",
|
|
6972
7514
|
legacy: ["review-password-hash-read"]
|
|
6973
7515
|
},
|
|
7516
|
+
{
|
|
7517
|
+
id: "files.system-dirs",
|
|
7518
|
+
group: "files",
|
|
7519
|
+
title: "System directories",
|
|
7520
|
+
catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
|
|
7521
|
+
values: ALL,
|
|
7522
|
+
defaultValue: "review",
|
|
7523
|
+
legacy: ["review-system-dir-mutation", "review-system-file-upload"]
|
|
7524
|
+
},
|
|
6974
7525
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
6975
7526
|
{
|
|
6976
7527
|
id: "commands.inline-exec",
|
|
@@ -8268,6 +8819,10 @@ var init_dist = __esm({
|
|
|
8268
8819
|
AND_OP = probeBinOp("a && b");
|
|
8269
8820
|
OR_OP = probeBinOp("a || b");
|
|
8270
8821
|
AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
8822
|
+
MAX_CALL_DEPTH = 4;
|
|
8823
|
+
MAX_CALL_WALKS = 256;
|
|
8824
|
+
callDepth = 0;
|
|
8825
|
+
callWalks = 0;
|
|
8271
8826
|
FS_OP_CACHE_MAX = 5e3;
|
|
8272
8827
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
8273
8828
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
@@ -8320,10 +8875,79 @@ var init_dist = __esm({
|
|
|
8320
8875
|
MAX_ARGV_CHARACTERS = 262144;
|
|
8321
8876
|
({ syntax: syntax2 } = mvdanSh2);
|
|
8322
8877
|
MAX_ALTERNATIVES = 64;
|
|
8878
|
+
MAX_CALL_DEPTH2 = 4;
|
|
8879
|
+
MAX_CALL_WALKS2 = 256;
|
|
8880
|
+
POSITIONAL = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"];
|
|
8323
8881
|
MAX_DEPTH = 64;
|
|
8324
8882
|
MAX_PAYLOAD_DEPTH = 24;
|
|
8325
8883
|
unique = (a) => [...new Set(a)].slice(0, MAX_ALTERNATIVES);
|
|
8326
|
-
clone = (s) => ({
|
|
8884
|
+
clone = (s) => ({
|
|
8885
|
+
cwd: [...s.cwd],
|
|
8886
|
+
vars: new Map(s.vars),
|
|
8887
|
+
...s.callDepth ? { callDepth: s.callDepth } : {}
|
|
8888
|
+
});
|
|
8889
|
+
SENDING_STDIN = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "socat", "telnet", "curl", "wget"]);
|
|
8890
|
+
DOWNLOAD_SHORT_VALUE = {
|
|
8891
|
+
curl: "odDeEFHKmPrtTuUwxXyYzAbcCQ",
|
|
8892
|
+
wget: "OoaPiBetTwQlARDXIUY",
|
|
8893
|
+
aria2c: "odisx"
|
|
8894
|
+
};
|
|
8895
|
+
DOWNLOAD_LONG_VALUE = {
|
|
8896
|
+
curl: [
|
|
8897
|
+
"--output",
|
|
8898
|
+
"--output-dir",
|
|
8899
|
+
"--dump-header",
|
|
8900
|
+
"--cookie-jar",
|
|
8901
|
+
"--data",
|
|
8902
|
+
"--data-binary",
|
|
8903
|
+
"--data-raw",
|
|
8904
|
+
"--data-ascii",
|
|
8905
|
+
"--data-urlencode",
|
|
8906
|
+
"--form",
|
|
8907
|
+
"--header",
|
|
8908
|
+
"--request",
|
|
8909
|
+
"--user",
|
|
8910
|
+
"--user-agent",
|
|
8911
|
+
"--referer",
|
|
8912
|
+
"--cookie",
|
|
8913
|
+
"--config",
|
|
8914
|
+
"--json",
|
|
8915
|
+
"--upload-file",
|
|
8916
|
+
"--max-time",
|
|
8917
|
+
"--proxy",
|
|
8918
|
+
"--write-out",
|
|
8919
|
+
"--url"
|
|
8920
|
+
],
|
|
8921
|
+
wget: [
|
|
8922
|
+
"--output-document",
|
|
8923
|
+
"--output-file",
|
|
8924
|
+
"--append-output",
|
|
8925
|
+
"--directory-prefix",
|
|
8926
|
+
"--input-file",
|
|
8927
|
+
"--post-data",
|
|
8928
|
+
"--post-file",
|
|
8929
|
+
"--body-data",
|
|
8930
|
+
"--body-file",
|
|
8931
|
+
"--method",
|
|
8932
|
+
"--header",
|
|
8933
|
+
"--user-agent",
|
|
8934
|
+
"--user",
|
|
8935
|
+
"--password",
|
|
8936
|
+
"--tries",
|
|
8937
|
+
"--timeout"
|
|
8938
|
+
],
|
|
8939
|
+
aria2c: ["--out", "--dir", "--input-file"]
|
|
8940
|
+
};
|
|
8941
|
+
DOWNLOAD_OUTPUT_FLAGS = {
|
|
8942
|
+
curl: ["-o", "--output", "-D", "--dump-header", "-c", "--cookie-jar"],
|
|
8943
|
+
wget: ["-O", "--output-document", "-o", "--output-file", "-a", "--append-output"],
|
|
8944
|
+
aria2c: ["-o", "--out"]
|
|
8945
|
+
};
|
|
8946
|
+
DOWNLOAD_DIR_FLAGS = {
|
|
8947
|
+
curl: ["--output-dir"],
|
|
8948
|
+
wget: ["-P", "--directory-prefix"],
|
|
8949
|
+
aria2c: ["-d", "--dir"]
|
|
8950
|
+
};
|
|
8327
8951
|
base = (s) => (s ?? "").replace(/\/+$/, "").split("/").pop() ?? "";
|
|
8328
8952
|
quoteGlob = (s) => s.replace(/[\\*?\[\]{}()!+@]/g, "\\$&");
|
|
8329
8953
|
asWord = (p) => ({ value: p.value, ...p.glob ? { pattern: p.pattern } : {} });
|
|
@@ -8348,6 +8972,18 @@ var init_dist = __esm({
|
|
|
8348
8972
|
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
8349
8973
|
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
8350
8974
|
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
8975
|
+
SYSTEM_DIRS = [
|
|
8976
|
+
"/usr",
|
|
8977
|
+
"/etc",
|
|
8978
|
+
"/boot",
|
|
8979
|
+
"/bin",
|
|
8980
|
+
"/sbin",
|
|
8981
|
+
"/lib",
|
|
8982
|
+
"/lib64",
|
|
8983
|
+
"/var/log"
|
|
8984
|
+
];
|
|
8985
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
8986
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
8351
8987
|
AGENT_INSTRUCTION_FILES = [
|
|
8352
8988
|
".gitlab-ci.yml",
|
|
8353
8989
|
"CLAUDE.md",
|
|
@@ -8365,6 +9001,7 @@ var init_dist = __esm({
|
|
|
8365
9001
|
".husky"
|
|
8366
9002
|
];
|
|
8367
9003
|
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9004
|
+
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
8368
9005
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
8369
9006
|
BIP39_ENGLISH = `abandon
|
|
8370
9007
|
ability
|
|
@@ -10650,15 +11287,20 @@ zoo`.split("\n");
|
|
|
10650
11287
|
IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
|
|
10651
11288
|
PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
|
|
10652
11289
|
DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
|
|
11290
|
+
CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
|
|
10653
11291
|
EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
|
|
10654
11292
|
FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
|
|
10655
|
-
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;
|
|
11293
|
+
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;
|
|
10656
11294
|
QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
|
|
10657
11295
|
OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
|
|
10658
|
-
|
|
11296
|
+
OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
|
|
11297
|
+
ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
|
|
10659
11298
|
ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
|
|
10660
|
-
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
|
|
11299
|
+
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;
|
|
10661
11300
|
ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
|
|
11301
|
+
BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
|
|
11302
|
+
BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
|
|
11303
|
+
OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
|
|
10662
11304
|
DLP_PATTERNS = [
|
|
10663
11305
|
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
10664
11306
|
{
|
|
@@ -10996,6 +11638,22 @@ zoo`.split("\n");
|
|
|
10996
11638
|
contextBoost: true,
|
|
10997
11639
|
minEntropy: 3
|
|
10998
11640
|
},
|
|
11641
|
+
// ── HTTP Basic credential ─────────────────────────────────────────────────
|
|
11642
|
+
// `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
|
|
11643
|
+
// config list. The validator decodes the value and accepts only a
|
|
11644
|
+
// `user:password` whose password looks generated (letters and digits, 8+
|
|
11645
|
+
// characters, the query-credential rule), so prose ("Basic authentication")
|
|
11646
|
+
// and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
|
|
11647
|
+
// review: the same weight as a bare Bearer token.
|
|
11648
|
+
{
|
|
11649
|
+
name: "HTTP Basic Credential",
|
|
11650
|
+
regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
|
|
11651
|
+
severity: "review",
|
|
11652
|
+
keywords: ["basic"],
|
|
11653
|
+
validate: validateBasicCredential,
|
|
11654
|
+
redactGroup: 1,
|
|
11655
|
+
argumentsOnly: true
|
|
11656
|
+
},
|
|
10999
11657
|
// ── Resend ────────────────────────────────────────────────────────────────
|
|
11000
11658
|
{
|
|
11001
11659
|
name: "Resend API Key",
|
|
@@ -11137,6 +11795,19 @@ zoo`.split("\n");
|
|
|
11137
11795
|
validate: validateQueryCredential,
|
|
11138
11796
|
argumentsOnly: true
|
|
11139
11797
|
},
|
|
11798
|
+
// ── OTP secret in a constructor ───────────────────────────────────────────
|
|
11799
|
+
// `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
|
|
11800
|
+
// the shared secret of a one-time-password generator written as a literal.
|
|
11801
|
+
// The otpauth URI form is `Credential in URL Query`. review.
|
|
11802
|
+
{
|
|
11803
|
+
name: "OTP Secret",
|
|
11804
|
+
regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
|
|
11805
|
+
severity: "review",
|
|
11806
|
+
keywords: ["otp"],
|
|
11807
|
+
validate: validateOtpSecret,
|
|
11808
|
+
redactGroup: 1,
|
|
11809
|
+
argumentsOnly: true
|
|
11810
|
+
},
|
|
11140
11811
|
// ── Secret assigned to a telling name ─────────────────────────────────────
|
|
11141
11812
|
// `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
|
|
11142
11813
|
// not a finding: most such lines hold a reference (`process.env.X`), a
|
|
@@ -11144,12 +11815,14 @@ zoo`.split("\n");
|
|
|
11144
11815
|
// generated: 12+ characters, not an identifier, Shannon entropy of 3.5
|
|
11145
11816
|
// bits per character or more. review, never block (node9Firewall#348,
|
|
11146
11817
|
// item 4). Tuned on CredData's public split; its look-alike lines are the
|
|
11147
|
-
// negative control.
|
|
11818
|
+
// negative control. A quote may be escaped (`\"access_token\":\"...\"`):
|
|
11819
|
+
// JSON written inside a string literal. The two optional quotes after the
|
|
11820
|
+
// name are written out, not as a counted group, to keep star height 1.
|
|
11148
11821
|
{
|
|
11149
11822
|
name: "Assigned Secret",
|
|
11150
|
-
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]
|
|
11823
|
+
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,
|
|
11151
11824
|
severity: "review",
|
|
11152
|
-
keywords: ["
|
|
11825
|
+
keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
|
|
11153
11826
|
validate: validateAssignedSecret,
|
|
11154
11827
|
argumentsOnly: true
|
|
11155
11828
|
}
|
|
@@ -11410,7 +12083,169 @@ zoo`.split("\n");
|
|
|
11410
12083
|
]);
|
|
11411
12084
|
PY_SPEC_RE = /^([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?:\[[^\]]*\])?\s*(.*)$/;
|
|
11412
12085
|
PY_EXACT_RE = /^===?\s*v?(\d+(?:\.\d+)*(?:[a-zA-Z0-9.+!-]*))$/;
|
|
12086
|
+
EXACT_VERSION_RE = /^=?v?(\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?)$/;
|
|
12087
|
+
GEM_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12088
|
+
GEM_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12089
|
+
"-v",
|
|
12090
|
+
"--version",
|
|
12091
|
+
"-i",
|
|
12092
|
+
"--install-dir",
|
|
12093
|
+
"-n",
|
|
12094
|
+
"--bindir",
|
|
12095
|
+
"--document",
|
|
12096
|
+
"--build-root",
|
|
12097
|
+
"-P",
|
|
12098
|
+
"--trust-policy",
|
|
12099
|
+
"-s",
|
|
12100
|
+
"--source",
|
|
12101
|
+
"-p",
|
|
12102
|
+
"--http-proxy",
|
|
12103
|
+
"--platform",
|
|
12104
|
+
"-g",
|
|
12105
|
+
"--file",
|
|
12106
|
+
"--without"
|
|
12107
|
+
]);
|
|
12108
|
+
BUNDLE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12109
|
+
"-v",
|
|
12110
|
+
"--version",
|
|
12111
|
+
"-g",
|
|
12112
|
+
"--group",
|
|
12113
|
+
"-s",
|
|
12114
|
+
"--source",
|
|
12115
|
+
"-r",
|
|
12116
|
+
"--require",
|
|
12117
|
+
"--path",
|
|
12118
|
+
"--git",
|
|
12119
|
+
"--github",
|
|
12120
|
+
"--branch",
|
|
12121
|
+
"--ref",
|
|
12122
|
+
"--glob"
|
|
12123
|
+
]);
|
|
12124
|
+
CRATE_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*$/u;
|
|
12125
|
+
CARGO_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12126
|
+
"-F",
|
|
12127
|
+
"--features",
|
|
12128
|
+
"--rename",
|
|
12129
|
+
"--registry",
|
|
12130
|
+
"--manifest-path",
|
|
12131
|
+
"-p",
|
|
12132
|
+
"--package",
|
|
12133
|
+
"--git",
|
|
12134
|
+
"--branch",
|
|
12135
|
+
"--tag",
|
|
12136
|
+
"--rev",
|
|
12137
|
+
"--path",
|
|
12138
|
+
"--base",
|
|
12139
|
+
"--target",
|
|
12140
|
+
"--color",
|
|
12141
|
+
"--config",
|
|
12142
|
+
"-Z",
|
|
12143
|
+
"--lockfile-path"
|
|
12144
|
+
]);
|
|
12145
|
+
CARGO_INSTALL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12146
|
+
"--version",
|
|
12147
|
+
"--vers",
|
|
12148
|
+
"--git",
|
|
12149
|
+
"--branch",
|
|
12150
|
+
"--tag",
|
|
12151
|
+
"--rev",
|
|
12152
|
+
"--path",
|
|
12153
|
+
"--root",
|
|
12154
|
+
"--index",
|
|
12155
|
+
"--registry",
|
|
12156
|
+
"-F",
|
|
12157
|
+
"--features",
|
|
12158
|
+
"--profile",
|
|
12159
|
+
"--target",
|
|
12160
|
+
"--target-dir",
|
|
12161
|
+
"-j",
|
|
12162
|
+
"--jobs",
|
|
12163
|
+
"--bin",
|
|
12164
|
+
"--example",
|
|
12165
|
+
"--color",
|
|
12166
|
+
"--config",
|
|
12167
|
+
"-Z"
|
|
12168
|
+
]);
|
|
12169
|
+
GO_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12170
|
+
"-C",
|
|
12171
|
+
"-tags",
|
|
12172
|
+
"-ldflags",
|
|
12173
|
+
"-gcflags",
|
|
12174
|
+
"-asmflags",
|
|
12175
|
+
"-mod",
|
|
12176
|
+
"-modfile",
|
|
12177
|
+
"-o",
|
|
12178
|
+
"-p",
|
|
12179
|
+
"-pkgdir",
|
|
12180
|
+
"-toolexec",
|
|
12181
|
+
"-exec",
|
|
12182
|
+
"-overlay",
|
|
12183
|
+
"-pgo",
|
|
12184
|
+
"-buildmode",
|
|
12185
|
+
"-compiler",
|
|
12186
|
+
"-gccgoflags",
|
|
12187
|
+
"-installsuffix",
|
|
12188
|
+
"-covermode",
|
|
12189
|
+
"-coverpkg"
|
|
12190
|
+
]);
|
|
12191
|
+
GO_PATH_RE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+(?:\/[\p{L}\p{N}_.~+-]+)*$/u;
|
|
12192
|
+
NUGET_NAME_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u;
|
|
12193
|
+
DOTNET_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12194
|
+
"-v",
|
|
12195
|
+
"--version",
|
|
12196
|
+
"-s",
|
|
12197
|
+
"--source",
|
|
12198
|
+
"-f",
|
|
12199
|
+
"--framework",
|
|
12200
|
+
"--package-directory"
|
|
12201
|
+
]);
|
|
12202
|
+
DOTNET_TOOL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12203
|
+
"--version",
|
|
12204
|
+
"--add-source",
|
|
12205
|
+
"--configfile",
|
|
12206
|
+
"--framework",
|
|
12207
|
+
"--tool-path",
|
|
12208
|
+
"-a",
|
|
12209
|
+
"--arch",
|
|
12210
|
+
"--tool-manifest",
|
|
12211
|
+
"-v",
|
|
12212
|
+
"--verbosity"
|
|
12213
|
+
]);
|
|
12214
|
+
NUGET_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12215
|
+
"-version",
|
|
12216
|
+
"-outputdirectory",
|
|
12217
|
+
"-o",
|
|
12218
|
+
"-source",
|
|
12219
|
+
"-configfile",
|
|
12220
|
+
"-fallbacksource",
|
|
12221
|
+
"-framework",
|
|
12222
|
+
"-packagesavemode",
|
|
12223
|
+
"-dependencyversion",
|
|
12224
|
+
"-solutiondirectory",
|
|
12225
|
+
"-verbosity"
|
|
12226
|
+
]);
|
|
12227
|
+
MAVEN_COORD_RE = /^[\p{L}\p{N}_.-]+:[\p{L}\p{N}_.-]+$/u;
|
|
12228
|
+
COMPOSER_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*\/[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12229
|
+
COMPOSER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12230
|
+
"-d",
|
|
12231
|
+
"--working-dir",
|
|
12232
|
+
"--stability",
|
|
12233
|
+
"--repository",
|
|
12234
|
+
"--add-repository"
|
|
12235
|
+
]);
|
|
12236
|
+
EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
|
|
12237
|
+
EXTENSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*\.[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
11413
12238
|
MANAGER_HEADS = /* @__PURE__ */ new Set([
|
|
12239
|
+
"gem",
|
|
12240
|
+
"bundle",
|
|
12241
|
+
"cargo",
|
|
12242
|
+
"go",
|
|
12243
|
+
"dotnet",
|
|
12244
|
+
"nuget",
|
|
12245
|
+
"mvn",
|
|
12246
|
+
"mvnw",
|
|
12247
|
+
"composer",
|
|
12248
|
+
...EDITOR_HEADS,
|
|
11414
12249
|
"npm",
|
|
11415
12250
|
"npx",
|
|
11416
12251
|
"pnpm",
|
|
@@ -11489,6 +12324,7 @@ zoo`.split("\n");
|
|
|
11489
12324
|
MAP_GOVERNED = /* @__PURE__ */ new Set([
|
|
11490
12325
|
"files.shell-startup",
|
|
11491
12326
|
"files.password-hashes",
|
|
12327
|
+
"files.system-dirs",
|
|
11492
12328
|
"commands.inline-exec",
|
|
11493
12329
|
"commands.eval-dynamic",
|
|
11494
12330
|
"commands.curl-pipe-shell",
|
|
@@ -11757,7 +12593,6 @@ zoo`.split("\n");
|
|
|
11757
12593
|
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/;
|
|
11758
12594
|
PII_CC15_RE = /\b3[47]\d{2}[-\s]?\d{6}[-\s]?\d{5}\b/;
|
|
11759
12595
|
PII_IBAN_RE = /\b[A-Z]{2}\d{2}(?:[A-Z0-9]|[ -][A-Z0-9]){11,30}\b/;
|
|
11760
|
-
NUMBER_LEAD_RE = /[\s:=;([{"'+-]/;
|
|
11761
12596
|
REALTIME_PII_PATTERNS = ["SSN", "Credit Card", "IBAN"];
|
|
11762
12597
|
MAX_PII_SCAN_BYTES = 1e5;
|
|
11763
12598
|
MASKED_KINDS = /* @__PURE__ */ new Set(["email", "iban", "account"]);
|
|
@@ -12099,6 +12934,9 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12099
12934
|
...transcriptPathField,
|
|
12100
12935
|
...taintFields,
|
|
12101
12936
|
...exposureFields,
|
|
12937
|
+
...meta?.decidedBy && { decidedBy: meta.decidedBy },
|
|
12938
|
+
...meta?.channel && { channel: meta.channel },
|
|
12939
|
+
...meta?.cardId && { cardId: meta.cardId },
|
|
12102
12940
|
...testRun,
|
|
12103
12941
|
agent: meta?.agent,
|
|
12104
12942
|
mcpServer: meta?.mcpServer,
|
|
@@ -14475,9 +15313,159 @@ function getPersistentDecision(toolName) {
|
|
|
14475
15313
|
|
|
14476
15314
|
// src/auth/daemon.ts
|
|
14477
15315
|
import fs10 from "fs";
|
|
15316
|
+
import http from "http";
|
|
14478
15317
|
import net from "net";
|
|
14479
15318
|
import path10 from "path";
|
|
14480
15319
|
import os8 from "os";
|
|
15320
|
+
|
|
15321
|
+
// src/auth/decision-record.ts
|
|
15322
|
+
var CHANNELS = ["terminal", "monitor", "native", "cloud"];
|
|
15323
|
+
var PERSON_CHANNELS = /* @__PURE__ */ new Set([
|
|
15324
|
+
"terminal",
|
|
15325
|
+
"monitor",
|
|
15326
|
+
"native"
|
|
15327
|
+
]);
|
|
15328
|
+
var CARD_PROTOCOL = 2;
|
|
15329
|
+
var PERSON_LABELS = {
|
|
15330
|
+
terminal: "User Decision (Terminal (node9 tail))",
|
|
15331
|
+
monitor: "User Decision (node9 monitor)",
|
|
15332
|
+
native: "User Decision (Native)"
|
|
15333
|
+
};
|
|
15334
|
+
var REDIRECT_LABEL = "Steered Redirect (Terminal)";
|
|
15335
|
+
var TIMER_LABEL = "Approval Timeout";
|
|
15336
|
+
var CLOUD_LABEL = "Organization Policy (SaaS)";
|
|
15337
|
+
var ABANDONED_LABEL = "No Approver (node9 tail closed)";
|
|
15338
|
+
function personRecord(channel, decision, extra = {}, now = Date.now()) {
|
|
15339
|
+
return {
|
|
15340
|
+
decision,
|
|
15341
|
+
decidedBy: "person",
|
|
15342
|
+
channel,
|
|
15343
|
+
label: extra.redirect ? REDIRECT_LABEL : PERSON_LABELS[channel],
|
|
15344
|
+
...extra,
|
|
15345
|
+
decidedAt: now
|
|
15346
|
+
};
|
|
15347
|
+
}
|
|
15348
|
+
function timerRecord(timeoutMs, now = Date.now()) {
|
|
15349
|
+
return {
|
|
15350
|
+
decision: "deny",
|
|
15351
|
+
decidedBy: "timer",
|
|
15352
|
+
label: TIMER_LABEL,
|
|
15353
|
+
reason: `No response within ${Math.round(timeoutMs / 1e3)}s: auto-denied by the approval timeout.`,
|
|
15354
|
+
decidedAt: now
|
|
15355
|
+
};
|
|
15356
|
+
}
|
|
15357
|
+
function abandonedRecord(now = Date.now()) {
|
|
15358
|
+
return {
|
|
15359
|
+
decision: "deny",
|
|
15360
|
+
decidedBy: "abandoned",
|
|
15361
|
+
label: ABANDONED_LABEL,
|
|
15362
|
+
reason: "No approver was left to answer: node9 tail was closed.",
|
|
15363
|
+
decidedAt: now
|
|
15364
|
+
};
|
|
15365
|
+
}
|
|
15366
|
+
var DECIDED_BY = /* @__PURE__ */ new Set([
|
|
15367
|
+
"person",
|
|
15368
|
+
"timer",
|
|
15369
|
+
"cloud",
|
|
15370
|
+
"policy",
|
|
15371
|
+
"abandoned"
|
|
15372
|
+
]);
|
|
15373
|
+
var STANDING = /* @__PURE__ */ new Set([
|
|
15374
|
+
"always-allow",
|
|
15375
|
+
"always-deny",
|
|
15376
|
+
"trust",
|
|
15377
|
+
"trust-recipients"
|
|
15378
|
+
]);
|
|
15379
|
+
function boundedString(v, max) {
|
|
15380
|
+
return typeof v === "string" && v.length > 0 ? v.slice(0, max) : void 0;
|
|
15381
|
+
}
|
|
15382
|
+
function parseDecisionRecord(x) {
|
|
15383
|
+
if (!x || typeof x !== "object") return null;
|
|
15384
|
+
const r = x;
|
|
15385
|
+
if (r.decision !== "allow" && r.decision !== "deny") return null;
|
|
15386
|
+
if (typeof r.decidedBy !== "string" || !DECIDED_BY.has(r.decidedBy)) return null;
|
|
15387
|
+
const label2 = boundedString(r.label, 200);
|
|
15388
|
+
if (!label2) return null;
|
|
15389
|
+
const channel = typeof r.channel === "string" && CHANNELS.includes(r.channel) ? r.channel : void 0;
|
|
15390
|
+
if (r.decidedBy === "person" && (!channel || !PERSON_CHANNELS.has(channel))) return null;
|
|
15391
|
+
const out = {
|
|
15392
|
+
decision: r.decision,
|
|
15393
|
+
decidedBy: r.decidedBy,
|
|
15394
|
+
label: label2,
|
|
15395
|
+
decidedAt: typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt) ? r.decidedAt : 0
|
|
15396
|
+
};
|
|
15397
|
+
if (channel) out.channel = channel;
|
|
15398
|
+
const redirect = boundedString(r.redirect, 2e3);
|
|
15399
|
+
if (redirect) out.redirect = redirect;
|
|
15400
|
+
const reason = boundedString(r.reason, 2e3);
|
|
15401
|
+
if (reason) out.reason = reason;
|
|
15402
|
+
const ruleName = boundedString(r.ruleName, 256);
|
|
15403
|
+
if (ruleName) out.ruleName = ruleName;
|
|
15404
|
+
if (typeof r.standing === "string" && STANDING.has(r.standing))
|
|
15405
|
+
out.standing = r.standing;
|
|
15406
|
+
const trustDuration = boundedString(r.trustDuration, 8);
|
|
15407
|
+
if (trustDuration) out.trustDuration = trustDuration;
|
|
15408
|
+
return out;
|
|
15409
|
+
}
|
|
15410
|
+
function legacyWaitFields(record) {
|
|
15411
|
+
switch (record.decidedBy) {
|
|
15412
|
+
case "person": {
|
|
15413
|
+
const source = record.redirect ? "terminal-redirect" : record.channel === "native" ? "native" : "terminal";
|
|
15414
|
+
const reason = record.redirect ?? record.reason;
|
|
15415
|
+
return { decision: record.decision, source, ...reason && { reason } };
|
|
15416
|
+
}
|
|
15417
|
+
case "timer":
|
|
15418
|
+
return {
|
|
15419
|
+
decision: record.decision,
|
|
15420
|
+
source: "timeout",
|
|
15421
|
+
...record.reason && { reason: record.reason }
|
|
15422
|
+
};
|
|
15423
|
+
case "cloud":
|
|
15424
|
+
case "policy":
|
|
15425
|
+
return {
|
|
15426
|
+
decision: record.decision,
|
|
15427
|
+
source: record.decidedBy,
|
|
15428
|
+
...record.reason && { reason: record.reason }
|
|
15429
|
+
};
|
|
15430
|
+
case "abandoned":
|
|
15431
|
+
return { decision: "abandoned" };
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
function recordFromLegacyWait(body, now = Date.now()) {
|
|
15435
|
+
if (body.decision === "abandoned") return abandonedRecord(now);
|
|
15436
|
+
const decision = body.decision;
|
|
15437
|
+
switch (body.source) {
|
|
15438
|
+
case "terminal":
|
|
15439
|
+
case "browser":
|
|
15440
|
+
return personRecord("terminal", decision, body.reason ? { reason: body.reason } : {}, now);
|
|
15441
|
+
case "native":
|
|
15442
|
+
return personRecord("native", decision, {}, now);
|
|
15443
|
+
case "terminal-redirect":
|
|
15444
|
+
return personRecord("terminal", decision, body.reason ? { redirect: body.reason } : {}, now);
|
|
15445
|
+
case "timeout":
|
|
15446
|
+
return { ...timerRecord(0, now), ...body.reason && { reason: body.reason } };
|
|
15447
|
+
case "cloud":
|
|
15448
|
+
return {
|
|
15449
|
+
decision,
|
|
15450
|
+
decidedBy: "cloud",
|
|
15451
|
+
label: CLOUD_LABEL,
|
|
15452
|
+
...body.reason && { reason: body.reason },
|
|
15453
|
+
decidedAt: now
|
|
15454
|
+
};
|
|
15455
|
+
case "policy":
|
|
15456
|
+
return {
|
|
15457
|
+
decision,
|
|
15458
|
+
decidedBy: "policy",
|
|
15459
|
+
label: "Policy",
|
|
15460
|
+
...body.reason && { reason: body.reason },
|
|
15461
|
+
decidedAt: now
|
|
15462
|
+
};
|
|
15463
|
+
default:
|
|
15464
|
+
return null;
|
|
15465
|
+
}
|
|
15466
|
+
}
|
|
15467
|
+
|
|
15468
|
+
// src/auth/daemon.ts
|
|
14481
15469
|
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path10.join(os8.tmpdir(), "node9-activity.sock");
|
|
14482
15470
|
function notifyActivitySocket(data) {
|
|
14483
15471
|
return new Promise((resolve) => {
|
|
@@ -14575,7 +15563,7 @@ async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
|
14575
15563
|
return false;
|
|
14576
15564
|
}
|
|
14577
15565
|
}
|
|
14578
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId) {
|
|
15566
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId, card) {
|
|
14579
15567
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14580
15568
|
const ctrl = new AbortController();
|
|
14581
15569
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -14601,19 +15589,43 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
14601
15589
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
14602
15590
|
...viewOnly && { viewOnly: true },
|
|
14603
15591
|
...localSmartRuleMatched && { localSmartRuleMatched: true },
|
|
14604
|
-
...sessionId && { sessionId }
|
|
15592
|
+
...sessionId && { sessionId },
|
|
15593
|
+
...cardFields(card)
|
|
14605
15594
|
}),
|
|
14606
15595
|
signal: ctrl.signal
|
|
14607
15596
|
});
|
|
14608
15597
|
if (!res.ok) throw new Error("Daemon fail");
|
|
14609
|
-
|
|
14610
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
15598
|
+
return registeredCard(await res.json());
|
|
14611
15599
|
} finally {
|
|
14612
15600
|
clearTimeout(timer);
|
|
14613
15601
|
}
|
|
14614
15602
|
}
|
|
15603
|
+
function cardFields(card) {
|
|
15604
|
+
return {
|
|
15605
|
+
// A review card: the hook already decided this call needs a person; the
|
|
15606
|
+
// daemon never checks it again (approval-card design, defect 1).
|
|
15607
|
+
kind: "review",
|
|
15608
|
+
// Protocol 2 makes the card's /wait require the internal token: claim it
|
|
15609
|
+
// only when this process can read the token (a daemon under another uid
|
|
15610
|
+
// is running but its pid file probe fails with EPERM), or every wait
|
|
15611
|
+
// would get 403 and the review would be denied unseen.
|
|
15612
|
+
...getInternalToken() && { protocol: CARD_PROTOCOL },
|
|
15613
|
+
...card?.channels && { channels: card.channels },
|
|
15614
|
+
...typeof card?.timeoutMs === "number" && { timeoutMs: card.timeoutMs }
|
|
15615
|
+
};
|
|
15616
|
+
}
|
|
15617
|
+
function registeredCard(body) {
|
|
15618
|
+
const b = body;
|
|
15619
|
+
return {
|
|
15620
|
+
id: b.id,
|
|
15621
|
+
allowCount: b.allowCount ?? 1,
|
|
15622
|
+
...b.protocol === CARD_PROTOCOL && {
|
|
15623
|
+
protocol: CARD_PROTOCOL,
|
|
15624
|
+
deadline: typeof b.deadline === "number" ? b.deadline : null
|
|
15625
|
+
}
|
|
15626
|
+
};
|
|
15627
|
+
}
|
|
14615
15628
|
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
14616
|
-
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14617
15629
|
const waitCtrl = new AbortController();
|
|
14618
15630
|
let capped = false;
|
|
14619
15631
|
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
@@ -14623,14 +15635,18 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
14623
15635
|
const onAbort = () => waitCtrl.abort();
|
|
14624
15636
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
14625
15637
|
try {
|
|
14626
|
-
const
|
|
15638
|
+
const { status, body } = await daemonGet(`/wait/${id}`, waitCtrl.signal).catch(
|
|
14627
15639
|
(err) => {
|
|
14628
15640
|
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
14629
15641
|
throw err;
|
|
14630
15642
|
}
|
|
14631
15643
|
);
|
|
14632
|
-
if (
|
|
14633
|
-
|
|
15644
|
+
if (status === 409) throw new Error("node9 card was withdrawn");
|
|
15645
|
+
if (status !== 200) return { decision: "deny" };
|
|
15646
|
+
const parsed = JSON.parse(body);
|
|
15647
|
+
const record = parseDecisionRecord(parsed.record);
|
|
15648
|
+
if (record) return { ...legacyWaitFields(record), record };
|
|
15649
|
+
const { decision, source, reason } = parsed;
|
|
14634
15650
|
if (decision === "allow") return { decision: "allow", source };
|
|
14635
15651
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
14636
15652
|
return { decision: "deny", source, reason };
|
|
@@ -14639,7 +15655,29 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
14639
15655
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
14640
15656
|
}
|
|
14641
15657
|
}
|
|
14642
|
-
|
|
15658
|
+
function daemonGet(urlPath, signal, token = getInternalToken()) {
|
|
15659
|
+
return new Promise((resolve, reject) => {
|
|
15660
|
+
const req = http.get(
|
|
15661
|
+
{
|
|
15662
|
+
host: DAEMON_HOST,
|
|
15663
|
+
port: DAEMON_PORT,
|
|
15664
|
+
path: urlPath,
|
|
15665
|
+
signal,
|
|
15666
|
+
agent: false,
|
|
15667
|
+
headers: token ? { "X-Node9-Internal": token } : {}
|
|
15668
|
+
},
|
|
15669
|
+
(res) => {
|
|
15670
|
+
let body = "";
|
|
15671
|
+
res.setEncoding("utf8");
|
|
15672
|
+
res.on("data", (chunk) => body += chunk);
|
|
15673
|
+
res.on("end", () => resolve({ status: res.statusCode ?? 0, body }));
|
|
15674
|
+
res.on("error", reject);
|
|
15675
|
+
}
|
|
15676
|
+
);
|
|
15677
|
+
req.on("error", reject);
|
|
15678
|
+
});
|
|
15679
|
+
}
|
|
15680
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent, extra) {
|
|
14643
15681
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14644
15682
|
const res = await fetch(`${base2}/check`, {
|
|
14645
15683
|
method: "POST",
|
|
@@ -14655,13 +15693,16 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId
|
|
|
14655
15693
|
// event via socket. Same contract as registerDaemonEntry — without
|
|
14656
15694
|
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
14657
15695
|
fromCLI: socketActivitySent !== false,
|
|
14658
|
-
activityId
|
|
15696
|
+
activityId,
|
|
15697
|
+
...extra?.cwd && { cwd: extra.cwd },
|
|
15698
|
+
...extra?.recoveryCommand && { recoveryCommand: extra.recoveryCommand },
|
|
15699
|
+
...extra?.sessionId && { sessionId: extra.sessionId },
|
|
15700
|
+
...cardFields(extra?.card)
|
|
14659
15701
|
}),
|
|
14660
15702
|
signal: AbortSignal.timeout(3e3)
|
|
14661
15703
|
});
|
|
14662
15704
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
14663
|
-
|
|
14664
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
15705
|
+
return registeredCard(await res.json());
|
|
14665
15706
|
}
|
|
14666
15707
|
async function notifyTaint(filePath, source, fromEid) {
|
|
14667
15708
|
if (!isDaemonRunning()) return;
|
|
@@ -14754,14 +15795,18 @@ async function approveSessionExposure(sessionId, keys) {
|
|
|
14754
15795
|
} catch {
|
|
14755
15796
|
}
|
|
14756
15797
|
}
|
|
14757
|
-
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
15798
|
+
async function resolveViaDaemon(id, decision, internalToken, source, record) {
|
|
14758
15799
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14759
|
-
await fetch(`${base2}/resolve/${id}`, {
|
|
15800
|
+
const res = await fetch(`${base2}/resolve/${id}`, {
|
|
14760
15801
|
method: "POST",
|
|
14761
15802
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
14762
|
-
body: JSON.stringify({ decision, ...source && { source } }),
|
|
15803
|
+
body: JSON.stringify({ decision, ...source && { source }, ...record && { record } }),
|
|
14763
15804
|
signal: AbortSignal.timeout(3e3)
|
|
14764
15805
|
});
|
|
15806
|
+
if (!res.ok) return null;
|
|
15807
|
+
const body = await res.json().catch(() => null);
|
|
15808
|
+
const winner = parseDecisionRecord(body?.record);
|
|
15809
|
+
return winner && typeof body?.won === "boolean" ? { won: body.won, record: winner } : null;
|
|
14765
15810
|
}
|
|
14766
15811
|
|
|
14767
15812
|
// src/auth/orchestrator.ts
|
|
@@ -15878,7 +16923,17 @@ function ecoDir(eco) {
|
|
|
15878
16923
|
return path23.join(osvRoot(), eco);
|
|
15879
16924
|
}
|
|
15880
16925
|
function indexKey(eco, name) {
|
|
15881
|
-
|
|
16926
|
+
switch (eco) {
|
|
16927
|
+
case "PyPI":
|
|
16928
|
+
return normalizePyPiName(name);
|
|
16929
|
+
case "crates.io":
|
|
16930
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
16931
|
+
case "Go":
|
|
16932
|
+
case "Maven":
|
|
16933
|
+
return name;
|
|
16934
|
+
default:
|
|
16935
|
+
return name.toLowerCase();
|
|
16936
|
+
}
|
|
15882
16937
|
}
|
|
15883
16938
|
function shardOf(key) {
|
|
15884
16939
|
return createHash4("sha1").update(key).digest("hex").slice(0, 2);
|
|
@@ -16089,8 +17144,11 @@ async function pypiInfo(name, version) {
|
|
|
16089
17144
|
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0
|
|
16090
17145
|
};
|
|
16091
17146
|
}
|
|
17147
|
+
var HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
16092
17148
|
async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
16093
|
-
|
|
17149
|
+
if (eco === "npm") return npmInfo(name, version, freshWindowMs, raw);
|
|
17150
|
+
if (eco === "PyPI") return pypiInfo(name, version);
|
|
17151
|
+
return null;
|
|
16094
17152
|
}
|
|
16095
17153
|
|
|
16096
17154
|
// src/supply-chain/local-resolve.ts
|
|
@@ -16129,26 +17187,51 @@ function commandOf(toolName, args) {
|
|
|
16129
17187
|
const a = args;
|
|
16130
17188
|
return typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : null;
|
|
16131
17189
|
}
|
|
17190
|
+
var KIND = {
|
|
17191
|
+
npm: "npm package",
|
|
17192
|
+
PyPI: "PyPI package",
|
|
17193
|
+
RubyGems: "RubyGems package",
|
|
17194
|
+
"crates.io": "crates.io package",
|
|
17195
|
+
Go: "Go module",
|
|
17196
|
+
NuGet: "NuGet package",
|
|
17197
|
+
Maven: "Maven artifact",
|
|
17198
|
+
Packagist: "Packagist package",
|
|
17199
|
+
VSCode: "VS Code extension"
|
|
17200
|
+
};
|
|
16132
17201
|
function label(p, version) {
|
|
16133
|
-
return `${p.ecosystem
|
|
17202
|
+
return `${KIND[p.ecosystem]} "${p.name}${version ? `@${version}` : ""}"`;
|
|
17203
|
+
}
|
|
17204
|
+
function osvNames(p) {
|
|
17205
|
+
if (p.ecosystem !== "Go") return [p.name];
|
|
17206
|
+
const parts = p.name.split("/");
|
|
17207
|
+
const out = [];
|
|
17208
|
+
for (let n = parts.length; n >= 2 && out.length < 6; n--) out.push(parts.slice(0, n).join("/"));
|
|
17209
|
+
return out;
|
|
16134
17210
|
}
|
|
16135
17211
|
async function maliciousFor(p, version, cfg, misses, networkCapped) {
|
|
16136
|
-
const
|
|
16137
|
-
const
|
|
16138
|
-
|
|
17212
|
+
const names = osvNames(p);
|
|
17213
|
+
const local = names.map((n) => lookupIndex(p.ecosystem, n));
|
|
17214
|
+
const hits2 = local.flatMap((l) => l.status === "hit" ? l.entries : []);
|
|
17215
|
+
const entries = hits2.length > 0 ? hits2 : null;
|
|
17216
|
+
const freshClean = local.every((l) => l.status === "clean" && l.fresh);
|
|
17217
|
+
const status = local.some((l) => l.status === "unavailable") ? "unavailable" : local[0].status;
|
|
17218
|
+
if (!entries && !freshClean) {
|
|
16139
17219
|
if (!cfg.onlineFallback || networkCapped) {
|
|
16140
17220
|
misses.push(
|
|
16141
|
-
`${label(p, version)}: local index ${
|
|
17221
|
+
`${label(p, version)}: local index ${status}, no online lookup (${networkCapped ? "over the per-command network cap" : "online fallback off"})`
|
|
16142
17222
|
);
|
|
16143
17223
|
return null;
|
|
16144
17224
|
}
|
|
16145
|
-
let
|
|
17225
|
+
let perName = null;
|
|
16146
17226
|
try {
|
|
16147
|
-
|
|
17227
|
+
perName = await queryOsvMalicious(
|
|
17228
|
+
names.map((name) => ({ ecosystem: p.ecosystem, name, version }))
|
|
17229
|
+
);
|
|
16148
17230
|
} catch (err) {
|
|
16149
17231
|
misses.push(`${label(p, version)}: OSV online ${err.message}`);
|
|
16150
17232
|
return null;
|
|
16151
17233
|
}
|
|
17234
|
+
const ids = perName ? [[...new Set(perName.flat())]] : null;
|
|
16152
17235
|
if (!ids) {
|
|
16153
17236
|
misses.push(`${label(p, version)}: OSV online unavailable`);
|
|
16154
17237
|
return null;
|
|
@@ -16218,7 +17301,7 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
16218
17301
|
}
|
|
16219
17302
|
const findings = [];
|
|
16220
17303
|
let info = null;
|
|
16221
|
-
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
17304
|
+
const wantsRegistry = (cfg.newPackage !== "off" || cfg.installScript !== "off") && HAS_REGISTRY_CLIENT.has(p.ecosystem);
|
|
16222
17305
|
if (wantsRegistry && !networkCapped) {
|
|
16223
17306
|
try {
|
|
16224
17307
|
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
@@ -16317,17 +17400,98 @@ function asApprovalRequest(description) {
|
|
|
16317
17400
|
return description;
|
|
16318
17401
|
}
|
|
16319
17402
|
|
|
16320
|
-
// src/auth/
|
|
16321
|
-
var
|
|
16322
|
-
|
|
16323
|
-
"
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
...
|
|
16329
|
-
|
|
16330
|
-
|
|
17403
|
+
// src/auth/card-result.ts
|
|
17404
|
+
var CHANNEL_NAMES = {
|
|
17405
|
+
terminal: "Terminal (node9 tail)",
|
|
17406
|
+
monitor: "node9 monitor",
|
|
17407
|
+
native: "system dialog window",
|
|
17408
|
+
cloud: "organization"
|
|
17409
|
+
};
|
|
17410
|
+
function authResultFromRecord(r, ctx = {}) {
|
|
17411
|
+
const who = { decidedBy: r.decidedBy, ...r.channel && { channel: r.channel } };
|
|
17412
|
+
switch (r.decidedBy) {
|
|
17413
|
+
case "person": {
|
|
17414
|
+
const channel = r.channel ?? "terminal";
|
|
17415
|
+
const decisionSource = channel === "native" ? "native" : "terminal";
|
|
17416
|
+
if (r.decision === "allow") {
|
|
17417
|
+
return { approved: true, checkedBy: "daemon", decisionSource, ...who };
|
|
17418
|
+
}
|
|
17419
|
+
return {
|
|
17420
|
+
approved: false,
|
|
17421
|
+
reason: r.redirect || `The human user rejected this action via the Node9 ${CHANNEL_NAMES[channel]}.`,
|
|
17422
|
+
blockedBy: "local-decision",
|
|
17423
|
+
blockedByLabel: r.label,
|
|
17424
|
+
decisionSource,
|
|
17425
|
+
...who,
|
|
17426
|
+
...r.redirect && { redirect: r.redirect }
|
|
17427
|
+
};
|
|
17428
|
+
}
|
|
17429
|
+
case "timer":
|
|
17430
|
+
if (r.decision === "deny" && ctx.downgradedBlock)
|
|
17431
|
+
return { ...ctx.downgradedBlock, blockedBy: "timeout", ...who };
|
|
17432
|
+
return r.decision === "allow" ? { approved: true, ...who } : {
|
|
17433
|
+
approved: false,
|
|
17434
|
+
reason: ctx.timeoutReason || r.reason || "No response: auto-denied after the approval timeout.",
|
|
17435
|
+
blockedBy: "timeout",
|
|
17436
|
+
blockedByLabel: TIMER_LABEL,
|
|
17437
|
+
...who
|
|
17438
|
+
};
|
|
17439
|
+
case "cloud":
|
|
17440
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "cloud", decisionSource: "cloud", ...who } : {
|
|
17441
|
+
approved: false,
|
|
17442
|
+
reason: r.reason || "Action rejected by organization administrator via Slack.",
|
|
17443
|
+
blockedBy: "team-policy",
|
|
17444
|
+
blockedByLabel: CLOUD_LABEL,
|
|
17445
|
+
decisionSource: "cloud",
|
|
17446
|
+
...who
|
|
17447
|
+
};
|
|
17448
|
+
case "policy":
|
|
17449
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "local-policy", ...who } : {
|
|
17450
|
+
approved: false,
|
|
17451
|
+
reason: r.reason || "Action blocked by policy.",
|
|
17452
|
+
blockedBy: "local-config",
|
|
17453
|
+
blockedByLabel: r.label,
|
|
17454
|
+
...r.ruleName && { ruleHit: r.ruleName },
|
|
17455
|
+
...who
|
|
17456
|
+
};
|
|
17457
|
+
case "abandoned":
|
|
17458
|
+
return {
|
|
17459
|
+
approved: false,
|
|
17460
|
+
reason: r.reason || "No approver was left to answer.",
|
|
17461
|
+
blockedBy: "no-approval-mechanism",
|
|
17462
|
+
blockedByLabel: ABANDONED_LABEL,
|
|
17463
|
+
...who
|
|
17464
|
+
};
|
|
17465
|
+
}
|
|
17466
|
+
}
|
|
17467
|
+
function approvalUnavailable(reason) {
|
|
17468
|
+
return {
|
|
17469
|
+
approved: false,
|
|
17470
|
+
reason: reason || "node9 could not get an approval decision for this call.",
|
|
17471
|
+
blockedBy: "no-approval-mechanism",
|
|
17472
|
+
blockedByLabel: "Approval Unavailable"
|
|
17473
|
+
};
|
|
17474
|
+
}
|
|
17475
|
+
function decidedByOf(res) {
|
|
17476
|
+
if (res.decidedBy) return res.decidedBy;
|
|
17477
|
+
if (res.blockedBy === "timeout") return "timer";
|
|
17478
|
+
if (res.checkedBy === "cloud" || res.blockedBy === "team-policy") return "cloud";
|
|
17479
|
+
return "policy";
|
|
17480
|
+
}
|
|
17481
|
+
function recordFromAuthResult(res, now = Date.now()) {
|
|
17482
|
+
const decidedBy = decidedByOf(res);
|
|
17483
|
+
const channel = res.channel ?? (decidedBy === "cloud" ? "cloud" : decidedBy === "person" ? "native" : void 0);
|
|
17484
|
+
return {
|
|
17485
|
+
decision: res.approved ? "allow" : "deny",
|
|
17486
|
+
decidedBy,
|
|
17487
|
+
...channel && { channel },
|
|
17488
|
+
label: res.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : decidedBy === "timer" ? TIMER_LABEL : res.approved ? "Allowed" : "Blocked"),
|
|
17489
|
+
...res.reason && { reason: res.reason.slice(0, 2e3) },
|
|
17490
|
+
...res.ruleHit && { ruleName: res.ruleHit },
|
|
17491
|
+
...res.redirect && { redirect: res.redirect },
|
|
17492
|
+
decidedAt: now
|
|
17493
|
+
};
|
|
17494
|
+
}
|
|
16331
17495
|
|
|
16332
17496
|
// src/auth/orchestrator.ts
|
|
16333
17497
|
var SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
@@ -17367,28 +18531,22 @@ ${packageReview}`;
|
|
|
17367
18531
|
const { signal } = abortController;
|
|
17368
18532
|
const racePromises = [];
|
|
17369
18533
|
const approvalTimeoutMs = config.settings.approvalTimeoutMs ?? 0;
|
|
17370
|
-
if (approvalTimeoutMs > 0) {
|
|
17371
|
-
racePromises.push(
|
|
17372
|
-
new Promise((resolve, reject) => {
|
|
17373
|
-
const timer = setTimeout(() => {
|
|
17374
|
-
resolve({
|
|
17375
|
-
approved: false,
|
|
17376
|
-
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
17377
|
-
blockedBy: "timeout",
|
|
17378
|
-
blockedByLabel: "Approval Timeout"
|
|
17379
|
-
});
|
|
17380
|
-
}, approvalTimeoutMs);
|
|
17381
|
-
signal.addEventListener("abort", () => {
|
|
17382
|
-
clearTimeout(timer);
|
|
17383
|
-
reject(new Error("Aborted"));
|
|
17384
|
-
});
|
|
17385
|
-
})
|
|
17386
|
-
);
|
|
17387
|
-
}
|
|
17388
18534
|
let viewerId = null;
|
|
17389
18535
|
const internalToken = getInternalToken();
|
|
17390
18536
|
let daemonEntryId = null;
|
|
17391
18537
|
let daemonAllowCount = 1;
|
|
18538
|
+
let cardDeadline;
|
|
18539
|
+
const cardOptions = {
|
|
18540
|
+
channels: [
|
|
18541
|
+
"terminal",
|
|
18542
|
+
"monitor",
|
|
18543
|
+
// Only when the popup can show: a card listing a channel nobody can
|
|
18544
|
+
// use is never abandoned when node9 tail closes, and waits unanswered.
|
|
18545
|
+
...approvers.native && !isManual && !options?.calledFromDaemon && (process.platform === "darwin" || process.platform === "win32" || !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY)) ? ["native"] : [],
|
|
18546
|
+
...cloudEnforced && cloudRequestId && cloudMayVote ? ["cloud"] : []
|
|
18547
|
+
],
|
|
18548
|
+
timeoutMs: approvalTimeoutMs
|
|
18549
|
+
};
|
|
17392
18550
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
17393
18551
|
if (cloudEnforced && cloudRequestId) {
|
|
17394
18552
|
const viewer = await notifyDaemonViewer(
|
|
@@ -17397,11 +18555,20 @@ ${packageReview}`;
|
|
|
17397
18555
|
meta,
|
|
17398
18556
|
riskMetadata,
|
|
17399
18557
|
options?.activityId,
|
|
17400
|
-
options?.socketActivitySent
|
|
18558
|
+
options?.socketActivitySent,
|
|
18559
|
+
{
|
|
18560
|
+
cwd: options?.cwd,
|
|
18561
|
+
recoveryCommand: statefulRecoveryCommand,
|
|
18562
|
+
sessionId: meta?.sessionId,
|
|
18563
|
+
card: cardOptions
|
|
18564
|
+
}
|
|
17401
18565
|
).catch(() => null);
|
|
17402
18566
|
viewerId = viewer?.id ?? null;
|
|
17403
18567
|
daemonEntryId = viewerId;
|
|
17404
|
-
if (viewer)
|
|
18568
|
+
if (viewer) {
|
|
18569
|
+
daemonAllowCount = viewer.allowCount;
|
|
18570
|
+
cardDeadline = viewer.deadline;
|
|
18571
|
+
}
|
|
17405
18572
|
} else {
|
|
17406
18573
|
try {
|
|
17407
18574
|
const entry = await registerDaemonEntry(
|
|
@@ -17425,14 +18592,35 @@ ${packageReview}`;
|
|
|
17425
18592
|
void 0,
|
|
17426
18593
|
localSmartRuleMatched || options?.localSmartRuleMatched,
|
|
17427
18594
|
options?.socketActivitySent,
|
|
17428
|
-
meta?.sessionId
|
|
18595
|
+
meta?.sessionId,
|
|
18596
|
+
cardOptions
|
|
17429
18597
|
);
|
|
17430
18598
|
daemonEntryId = entry.id;
|
|
17431
18599
|
daemonAllowCount = entry.allowCount;
|
|
18600
|
+
cardDeadline = entry.deadline;
|
|
17432
18601
|
} catch {
|
|
17433
18602
|
}
|
|
17434
18603
|
}
|
|
17435
18604
|
}
|
|
18605
|
+
if (approvalTimeoutMs > 0 && cardDeadline === void 0) {
|
|
18606
|
+
racePromises.push(
|
|
18607
|
+
new Promise((resolve, reject) => {
|
|
18608
|
+
const timer = setTimeout(() => {
|
|
18609
|
+
resolve({
|
|
18610
|
+
approved: false,
|
|
18611
|
+
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
18612
|
+
blockedBy: "timeout",
|
|
18613
|
+
blockedByLabel: "Approval Timeout",
|
|
18614
|
+
decidedBy: "timer"
|
|
18615
|
+
});
|
|
18616
|
+
}, approvalTimeoutMs);
|
|
18617
|
+
signal.addEventListener("abort", () => {
|
|
18618
|
+
clearTimeout(timer);
|
|
18619
|
+
reject(new Error("Aborted"));
|
|
18620
|
+
});
|
|
18621
|
+
})
|
|
18622
|
+
);
|
|
18623
|
+
}
|
|
17436
18624
|
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
17437
18625
|
racePromises.push(
|
|
17438
18626
|
(async () => {
|
|
@@ -17443,7 +18631,9 @@ ${packageReview}`;
|
|
|
17443
18631
|
reason: cloudResult.approved ? void 0 : cloudResult.reason || "Action rejected by organization administrator via Slack.",
|
|
17444
18632
|
checkedBy: cloudResult.approved ? "cloud" : void 0,
|
|
17445
18633
|
blockedBy: cloudResult.approved ? void 0 : "team-policy",
|
|
17446
|
-
blockedByLabel: "Organization Policy (SaaS)"
|
|
18634
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
18635
|
+
decidedBy: "cloud",
|
|
18636
|
+
channel: "cloud"
|
|
17447
18637
|
};
|
|
17448
18638
|
} catch (err) {
|
|
17449
18639
|
const error = err;
|
|
@@ -17473,14 +18663,31 @@ ${packageReview}`;
|
|
|
17473
18663
|
if (config.policySource !== "workspace") {
|
|
17474
18664
|
for (const d of exposureDestinations) addTrustedRecipient(d, "approval");
|
|
17475
18665
|
}
|
|
17476
|
-
return {
|
|
18666
|
+
return {
|
|
18667
|
+
approved: true,
|
|
18668
|
+
checkedBy: "daemon",
|
|
18669
|
+
decisionSource: "native",
|
|
18670
|
+
decidedBy: "person",
|
|
18671
|
+
channel: "native"
|
|
18672
|
+
};
|
|
17477
18673
|
}
|
|
17478
18674
|
if (decision === "always_allow") {
|
|
17479
18675
|
if (standingTrustApplies()) {
|
|
17480
18676
|
writeTrustSession(toolName, 36e5, args);
|
|
17481
|
-
return {
|
|
18677
|
+
return {
|
|
18678
|
+
approved: true,
|
|
18679
|
+
checkedBy: "trust",
|
|
18680
|
+
decidedBy: "person",
|
|
18681
|
+
channel: "native"
|
|
18682
|
+
};
|
|
17482
18683
|
}
|
|
17483
|
-
return {
|
|
18684
|
+
return {
|
|
18685
|
+
approved: true,
|
|
18686
|
+
checkedBy: "daemon",
|
|
18687
|
+
decisionSource: "native",
|
|
18688
|
+
decidedBy: "person",
|
|
18689
|
+
channel: "native"
|
|
18690
|
+
};
|
|
17484
18691
|
}
|
|
17485
18692
|
const isApproved = decision === "allow";
|
|
17486
18693
|
return {
|
|
@@ -17489,75 +18696,32 @@ ${packageReview}`;
|
|
|
17489
18696
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
17490
18697
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
17491
18698
|
blockedByLabel: "User Decision (Native)",
|
|
17492
|
-
decisionSource: "native"
|
|
18699
|
+
decisionSource: "native",
|
|
18700
|
+
decidedBy: "person",
|
|
18701
|
+
channel: "native"
|
|
17493
18702
|
};
|
|
17494
18703
|
})()
|
|
17495
18704
|
);
|
|
17496
18705
|
}
|
|
18706
|
+
const fromCard = /* @__PURE__ */ new WeakSet();
|
|
18707
|
+
const timeoutReason = approvalTimeoutMs > 0 ? approvalTimeoutReason(approvalTimeoutMs) : void 0;
|
|
17497
18708
|
if (daemonEntryId && approvers.terminal) {
|
|
17498
18709
|
racePromises.push(
|
|
17499
18710
|
(async () => {
|
|
17500
|
-
const
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
);
|
|
17509
|
-
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
17510
|
-
const isApproved = daemonDecision === "allow";
|
|
17511
|
-
const isRedirect = decisionSource === REDIRECT_SOURCE;
|
|
17512
|
-
if (!isApproved && !isRedirect && !(decisionSource && HUMAN_DECISION_SOURCES.has(decisionSource))) {
|
|
17513
|
-
if (decisionSource === "cloud")
|
|
17514
|
-
return {
|
|
17515
|
-
approved: false,
|
|
17516
|
-
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
17517
|
-
blockedBy: "team-policy",
|
|
17518
|
-
blockedByLabel: "Organization Policy (SaaS)",
|
|
17519
|
-
decisionSource: "cloud"
|
|
17520
|
-
};
|
|
17521
|
-
const timedOut = decisionSource === "timeout";
|
|
17522
|
-
if (downgradedBlock)
|
|
17523
|
-
return {
|
|
17524
|
-
...downgradedBlock,
|
|
17525
|
-
blockedBy: timedOut ? "timeout" : "local-config"
|
|
17526
|
-
};
|
|
17527
|
-
if (timedOut)
|
|
17528
|
-
return {
|
|
17529
|
-
approved: false,
|
|
17530
|
-
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
17531
|
-
blockedBy: "timeout",
|
|
17532
|
-
blockedByLabel: "Approval Timeout"
|
|
17533
|
-
};
|
|
17534
|
-
if (decisionSource === "policy")
|
|
17535
|
-
return {
|
|
17536
|
-
approved: false,
|
|
17537
|
-
reason: daemonReason || "Action blocked by policy.",
|
|
17538
|
-
blockedBy: "local-config",
|
|
17539
|
-
blockedByLabel: explainableLabel
|
|
17540
|
-
};
|
|
17541
|
-
return {
|
|
17542
|
-
approved: false,
|
|
17543
|
-
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
17544
|
-
blockedBy: "no-approval-mechanism",
|
|
17545
|
-
blockedByLabel: "Approval Unavailable"
|
|
17546
|
-
};
|
|
18711
|
+
const cap = cardDeadline === void 0 ? daemonWaitCapMs(config.settings.approvalTimeoutMs) : cardDeadline === null ? 0 : Math.max(cardDeadline - Date.now(), 0) + 5e3;
|
|
18712
|
+
const outcome = await waitForDaemonDecision(daemonEntryId, signal, cap);
|
|
18713
|
+
const legacy = !outcome.record;
|
|
18714
|
+
const record = outcome.record ?? recordFromLegacyWait(outcome);
|
|
18715
|
+
if (downgradedBlock && (!record || legacy && record.decidedBy === "policy" && record.decision === "deny")) {
|
|
18716
|
+
const softened = { ...downgradedBlock, blockedBy: "local-config" };
|
|
18717
|
+
fromCard.add(softened);
|
|
18718
|
+
return softened;
|
|
17547
18719
|
}
|
|
17548
|
-
if (
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
return {
|
|
17554
|
-
approved: isApproved,
|
|
17555
|
-
reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
|
|
17556
|
-
checkedBy: isApproved ? "daemon" : void 0,
|
|
17557
|
-
blockedBy: isApproved ? void 0 : "local-decision",
|
|
17558
|
-
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
17559
|
-
decisionSource: "terminal"
|
|
17560
|
-
};
|
|
18720
|
+
if (!record) return approvalUnavailable(outcome.reason);
|
|
18721
|
+
if (legacy && record.decidedBy === "policy") record.label = explainableLabel;
|
|
18722
|
+
const result = authResultFromRecord(record, { downgradedBlock, timeoutReason });
|
|
18723
|
+
fromCard.add(result);
|
|
18724
|
+
return result;
|
|
17561
18725
|
})()
|
|
17562
18726
|
);
|
|
17563
18727
|
}
|
|
@@ -17589,7 +18753,7 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17589
18753
|
blockedByLabel: explainableLabel
|
|
17590
18754
|
};
|
|
17591
18755
|
}
|
|
17592
|
-
const
|
|
18756
|
+
const raceResult = await new Promise((resolve) => {
|
|
17593
18757
|
let resolved = false;
|
|
17594
18758
|
let failures = 0;
|
|
17595
18759
|
const total = racePromises.length;
|
|
@@ -17597,14 +18761,6 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17597
18761
|
if (!resolved) {
|
|
17598
18762
|
resolved = true;
|
|
17599
18763
|
abortController.abort();
|
|
17600
|
-
if (daemonEntryId && internalToken) {
|
|
17601
|
-
resolveViaDaemon(
|
|
17602
|
-
daemonEntryId,
|
|
17603
|
-
res.approved ? "allow" : "deny",
|
|
17604
|
-
internalToken,
|
|
17605
|
-
res.decisionSource
|
|
17606
|
-
).catch(() => null);
|
|
17607
|
-
}
|
|
17608
18764
|
resolve(res);
|
|
17609
18765
|
}
|
|
17610
18766
|
};
|
|
@@ -17612,26 +18768,29 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17612
18768
|
p.then(finish).catch((err) => {
|
|
17613
18769
|
if (err.name === "AbortError" || err.message?.includes("canceled") || err.message?.includes("Aborted"))
|
|
17614
18770
|
return;
|
|
17615
|
-
if (err.message === "Abandoned") {
|
|
17616
|
-
finish({
|
|
17617
|
-
approved: false,
|
|
17618
|
-
reason: "Browser dashboard closed without making a decision.",
|
|
17619
|
-
blockedBy: "local-decision",
|
|
17620
|
-
blockedByLabel: "Browser Dashboard (Abandoned)"
|
|
17621
|
-
});
|
|
17622
|
-
return;
|
|
17623
|
-
}
|
|
17624
18771
|
failures++;
|
|
17625
18772
|
if (failures === total && !resolved) {
|
|
17626
|
-
finish(
|
|
18773
|
+
finish(approvalUnavailable("All approval channels failed or disconnected."));
|
|
17627
18774
|
}
|
|
17628
18775
|
});
|
|
17629
18776
|
}
|
|
17630
18777
|
});
|
|
18778
|
+
let finalResult = raceResult;
|
|
18779
|
+
if (daemonEntryId && internalToken && !fromCard.has(raceResult)) {
|
|
18780
|
+
const reply = await resolveViaDaemon(
|
|
18781
|
+
daemonEntryId,
|
|
18782
|
+
raceResult.approved ? "allow" : "deny",
|
|
18783
|
+
internalToken,
|
|
18784
|
+
raceResult.decisionSource,
|
|
18785
|
+
recordFromAuthResult(raceResult)
|
|
18786
|
+
).catch(() => null);
|
|
18787
|
+
if (reply && !reply.won)
|
|
18788
|
+
finalResult = authResultFromRecord(reply.record, { downgradedBlock, timeoutReason });
|
|
18789
|
+
}
|
|
17631
18790
|
if (finalResult.approved && exposureKeys.length > 0 && meta?.sessionId && ucPolicy?.afterExposure === "ask-once") {
|
|
17632
18791
|
await approveSessionExposure(meta.sessionId, exposureKeys);
|
|
17633
18792
|
}
|
|
17634
|
-
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
18793
|
+
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud" && finalResult.decidedBy !== "cloud") {
|
|
17635
18794
|
await resolveNode9SaaS(
|
|
17636
18795
|
cloudRequestId,
|
|
17637
18796
|
creds,
|
|
@@ -17655,7 +18814,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17655
18814
|
{
|
|
17656
18815
|
...ownRowMeta(finalResult.approved),
|
|
17657
18816
|
...cloudRequestId && { cloudRequestId },
|
|
17658
|
-
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` }
|
|
18817
|
+
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` },
|
|
18818
|
+
// Who decided, from the decision record, and the card: the daemon
|
|
18819
|
+
// writes its own row for the same card, and the report counts one.
|
|
18820
|
+
...finalResult.decidedBy && { decidedBy: finalResult.decidedBy },
|
|
18821
|
+
...finalResult.channel && { channel: finalResult.channel },
|
|
18822
|
+
...daemonEntryId && { cardId: daemonEntryId }
|
|
17659
18823
|
},
|
|
17660
18824
|
hashAuditArgs
|
|
17661
18825
|
);
|