@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.js
CHANGED
|
@@ -1183,6 +1183,120 @@ function expandPlainParam(p) {
|
|
|
1183
1183
|
if (recorded !== void 0) return recorded;
|
|
1184
1184
|
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
1185
1185
|
}
|
|
1186
|
+
function functionBodies(f) {
|
|
1187
|
+
const out = /* @__PURE__ */ new Map();
|
|
1188
|
+
syntax.Walk(f, (node) => {
|
|
1189
|
+
const n = node;
|
|
1190
|
+
if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1191
|
+
const list = out.get(n.Name.Value) ?? [];
|
|
1192
|
+
list.push(n.Body);
|
|
1193
|
+
out.set(n.Name.Value, list);
|
|
1194
|
+
}
|
|
1195
|
+
return true;
|
|
1196
|
+
});
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
1199
|
+
function rebindsPositionals(body) {
|
|
1200
|
+
let found = false;
|
|
1201
|
+
syntax.Walk(body, (node) => {
|
|
1202
|
+
const n = node;
|
|
1203
|
+
if (found || !n) return false;
|
|
1204
|
+
if (syntax.NodeType(n) === "CallExpr") {
|
|
1205
|
+
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1206
|
+
if (words[0] === "shift") found = true;
|
|
1207
|
+
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
1208
|
+
found = true;
|
|
1209
|
+
}
|
|
1210
|
+
return !found;
|
|
1211
|
+
});
|
|
1212
|
+
return found;
|
|
1213
|
+
}
|
|
1214
|
+
function tableBindings() {
|
|
1215
|
+
if (!assignmentTable) return "";
|
|
1216
|
+
const out = [];
|
|
1217
|
+
for (const [name, rec] of assignmentTable)
|
|
1218
|
+
if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
|
|
1219
|
+
return out.sort().join("");
|
|
1220
|
+
}
|
|
1221
|
+
function walkWithCalls(f, visit) {
|
|
1222
|
+
const bodies = functionBodies(f);
|
|
1223
|
+
if (bodies.size === 0) {
|
|
1224
|
+
syntax.Walk(f, visit);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
1228
|
+
const inline = (call) => {
|
|
1229
|
+
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1230
|
+
const list = words[0] ? bodies.get(words[0]) : void 0;
|
|
1231
|
+
if (!list || callDepth >= MAX_CALL_DEPTH) return;
|
|
1232
|
+
const args = words.slice(1);
|
|
1233
|
+
const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
|
|
1234
|
+
if (seenCalls.has(key)) return;
|
|
1235
|
+
seenCalls.add(key);
|
|
1236
|
+
const outerTable = assignmentTable;
|
|
1237
|
+
const outerOffset = currentStmtOffset;
|
|
1238
|
+
for (const body of list) {
|
|
1239
|
+
if (callWalks >= MAX_CALL_WALKS) return;
|
|
1240
|
+
callWalks++;
|
|
1241
|
+
const table = /* @__PURE__ */ new Map();
|
|
1242
|
+
for (const [name, rec] of outerTable ?? [])
|
|
1243
|
+
if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
|
|
1244
|
+
for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
|
|
1245
|
+
table.delete(name);
|
|
1246
|
+
if (!rebindsPositionals(body)) {
|
|
1247
|
+
args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
|
|
1248
|
+
if (args.length === 1) {
|
|
1249
|
+
table.set("@", { value: args[0], at: -1 });
|
|
1250
|
+
table.set("*", { value: args[0], at: -1 });
|
|
1251
|
+
}
|
|
1252
|
+
table.set("#", { value: String(args.length), at: -1 });
|
|
1253
|
+
}
|
|
1254
|
+
assignmentTable = table;
|
|
1255
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1256
|
+
callDepth++;
|
|
1257
|
+
try {
|
|
1258
|
+
if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
|
|
1259
|
+
syntax.Walk(body, step);
|
|
1260
|
+
} finally {
|
|
1261
|
+
callDepth--;
|
|
1262
|
+
assignmentTable = outerTable;
|
|
1263
|
+
currentStmtOffset = outerOffset;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
const step = (node) => {
|
|
1268
|
+
if (!node) return false;
|
|
1269
|
+
const go = visit(node);
|
|
1270
|
+
if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
|
|
1271
|
+
return go;
|
|
1272
|
+
};
|
|
1273
|
+
syntax.Walk(f, step);
|
|
1274
|
+
}
|
|
1275
|
+
function withAssignments(f, body) {
|
|
1276
|
+
const outerTable = assignmentTable;
|
|
1277
|
+
const outerOffset = currentStmtOffset;
|
|
1278
|
+
const outerWalks = callWalks;
|
|
1279
|
+
callWalks = 0;
|
|
1280
|
+
assignmentTable = new Map(
|
|
1281
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
1282
|
+
);
|
|
1283
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1284
|
+
try {
|
|
1285
|
+
recordTopLevelAssignments(f);
|
|
1286
|
+
return body();
|
|
1287
|
+
} finally {
|
|
1288
|
+
assignmentTable = outerTable;
|
|
1289
|
+
currentStmtOffset = outerOffset;
|
|
1290
|
+
callWalks = outerWalks;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function trackStatement(n) {
|
|
1294
|
+
try {
|
|
1295
|
+
currentStmtOffset = n.Pos().Offset();
|
|
1296
|
+
} catch {
|
|
1297
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1186
1300
|
function resolveWordLiteral(w) {
|
|
1187
1301
|
const parts = w?.Parts || [];
|
|
1188
1302
|
let s = "";
|
|
@@ -1303,27 +1417,31 @@ function extractShellDestinations(command) {
|
|
|
1303
1417
|
const out = [];
|
|
1304
1418
|
const seen = /* @__PURE__ */ new Set();
|
|
1305
1419
|
try {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1420
|
+
withAssignments(
|
|
1421
|
+
f,
|
|
1422
|
+
() => walkWithCalls(f, (node) => {
|
|
1423
|
+
if (!node) return false;
|
|
1424
|
+
const n = node;
|
|
1425
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1426
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1427
|
+
const callArgs = n.Args || [];
|
|
1428
|
+
if (callArgs.length === 0) return true;
|
|
1429
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1430
|
+
const at = unwrapCommandHead(words);
|
|
1431
|
+
const name = (words[at] || "").toLowerCase().split("/").pop() ?? "";
|
|
1432
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1433
|
+
const rest = words.slice(at + 1);
|
|
1434
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1435
|
+
const host2 = parseDestHost(raw);
|
|
1436
|
+
if (!host2) continue;
|
|
1437
|
+
const key = `${name}:${host2}`;
|
|
1438
|
+
if (seen.has(key)) continue;
|
|
1439
|
+
seen.add(key);
|
|
1440
|
+
out.push({ host: host2, binary: name, raw });
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1443
|
+
})
|
|
1444
|
+
);
|
|
1327
1445
|
} catch {
|
|
1328
1446
|
return out;
|
|
1329
1447
|
}
|
|
@@ -1335,39 +1453,45 @@ function extractShellDestTokens(command) {
|
|
|
1335
1453
|
const out = [];
|
|
1336
1454
|
const seen = /* @__PURE__ */ new Set();
|
|
1337
1455
|
try {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
tok = tok.
|
|
1456
|
+
withAssignments(
|
|
1457
|
+
f,
|
|
1458
|
+
() => walkWithCalls(f, (node) => {
|
|
1459
|
+
if (!node) return false;
|
|
1460
|
+
const n = node;
|
|
1461
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1462
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1463
|
+
const callArgs = n.Args || [];
|
|
1464
|
+
if (callArgs.length === 0) return true;
|
|
1465
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1466
|
+
const head = unwrapCommandHead(words);
|
|
1467
|
+
const name = (words[head] || "").toLowerCase().split("/").pop() ?? "";
|
|
1468
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1469
|
+
const rest = words.slice(head + 1);
|
|
1470
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1471
|
+
if (!raw) continue;
|
|
1472
|
+
let tok = raw.trim();
|
|
1473
|
+
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(tok);
|
|
1474
|
+
const hasScheme = scheme !== null;
|
|
1475
|
+
if (hasScheme) tok = tok.slice(scheme[0].length);
|
|
1476
|
+
tok = tok.split(/[/?#]/)[0];
|
|
1477
|
+
const at = tok.lastIndexOf("@");
|
|
1478
|
+
if (at >= 0) tok = tok.slice(at + 1);
|
|
1479
|
+
if (tok.startsWith("[")) {
|
|
1480
|
+
const close = tok.indexOf("]");
|
|
1481
|
+
if (close > 0) tok = tok.slice(0, close + 1);
|
|
1482
|
+
} else {
|
|
1483
|
+
tok = tok.split(":")[0];
|
|
1484
|
+
}
|
|
1485
|
+
if (!tok) continue;
|
|
1486
|
+
if (!hasScheme && /^\d+$/.test(tok) && Number(tok) < 16777216) continue;
|
|
1487
|
+
const key = `${name}:${tok}`;
|
|
1488
|
+
if (seen.has(key)) continue;
|
|
1489
|
+
seen.add(key);
|
|
1490
|
+
out.push({ token: tok, binary: name });
|
|
1361
1491
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
if (seen.has(key)) continue;
|
|
1366
|
-
seen.add(key);
|
|
1367
|
-
out.push({ token: tok, binary: name });
|
|
1368
|
-
}
|
|
1369
|
-
return true;
|
|
1370
|
-
});
|
|
1492
|
+
return true;
|
|
1493
|
+
})
|
|
1494
|
+
);
|
|
1371
1495
|
} catch {
|
|
1372
1496
|
return out;
|
|
1373
1497
|
}
|
|
@@ -1486,6 +1610,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1486
1610
|
if (depth === 0) {
|
|
1487
1611
|
seenPayloads = /* @__PURE__ */ new Set();
|
|
1488
1612
|
payloadBudget = PAYLOAD_BUDGET;
|
|
1613
|
+
if (callDepth === 0) callWalks = 0;
|
|
1489
1614
|
}
|
|
1490
1615
|
assignmentTable = new Map(
|
|
1491
1616
|
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
@@ -1493,7 +1618,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1493
1618
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1494
1619
|
try {
|
|
1495
1620
|
recordTopLevelAssignments(f);
|
|
1496
|
-
|
|
1621
|
+
walkWithCalls(f, (node) => {
|
|
1497
1622
|
if (!node || result?.verdict === "block") return false;
|
|
1498
1623
|
const n = node;
|
|
1499
1624
|
const nodeType = syntax.NodeType(n);
|
|
@@ -2282,6 +2407,19 @@ function runnablePrefix(command) {
|
|
|
2282
2407
|
}
|
|
2283
2408
|
return null;
|
|
2284
2409
|
}
|
|
2410
|
+
function uploadedFile(tool, flag, value) {
|
|
2411
|
+
const v = value.value;
|
|
2412
|
+
const file2 = (f) => f && f !== "-" && f !== "." ? { value: f } : null;
|
|
2413
|
+
if (tool === "curl") {
|
|
2414
|
+
if (["-d", "--data", "--data-ascii", "--data-binary", "--json"].includes(flag))
|
|
2415
|
+
return v.startsWith("@") ? file2(v.slice(1)) : null;
|
|
2416
|
+
if (flag === "--data-urlencode") return file2(/^[^=@]*@(.+)$/.exec(v)?.[1]);
|
|
2417
|
+
if (flag === "-F" || flag === "--form") return file2(/^[^=]*=[@<]([^;]+)/.exec(v)?.[1]);
|
|
2418
|
+
if (flag === "-T" || flag === "--upload-file") return file2(v);
|
|
2419
|
+
}
|
|
2420
|
+
if (tool === "wget" && ["--post-file", "--body-file"].includes(flag)) return file2(v);
|
|
2421
|
+
return null;
|
|
2422
|
+
}
|
|
2285
2423
|
function merge(a, b) {
|
|
2286
2424
|
a.cwd = unique([...a.cwd, ...b.cwd]);
|
|
2287
2425
|
for (const key of /* @__PURE__ */ new Set([...a.vars.keys(), ...b.vars.keys()])) {
|
|
@@ -2355,6 +2493,9 @@ function wordValues(word, state) {
|
|
|
2355
2493
|
function collectShellFileAccesses(command, context = {}) {
|
|
2356
2494
|
const out = [];
|
|
2357
2495
|
let payloads = 256;
|
|
2496
|
+
const functions = /* @__PURE__ */ new Map();
|
|
2497
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
2498
|
+
let callWalks2 = MAX_CALL_WALKS2;
|
|
2358
2499
|
const initial = {
|
|
2359
2500
|
cwd: [context.cwd ?? ""],
|
|
2360
2501
|
vars: /* @__PURE__ */ new Map([["HOME", [context.homeDirs?.[0] ?? "~"]]])
|
|
@@ -2612,8 +2753,98 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2612
2753
|
}
|
|
2613
2754
|
return;
|
|
2614
2755
|
}
|
|
2756
|
+
if (name === "wget" || name === "curl" || name === "aria2c") {
|
|
2757
|
+
downloadOutputs(name, args, state);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (["http", "https", "xh", "xhs"].includes(name)) {
|
|
2761
|
+
for (const token of args) {
|
|
2762
|
+
if (!token || token.dynamic || /^[a-z][a-z0-9+.-]*:\/\//i.test(token.value)) continue;
|
|
2763
|
+
const m = /^(?:@|[^=:@\s]+@|[^=:@\s]*=@)(.+)$/.exec(token.value);
|
|
2764
|
+
if (m && m[1] !== "-") add("read", { value: m[1] }, state, { upload: true });
|
|
2765
|
+
}
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
if (name === "scp" || name === "rsync") {
|
|
2769
|
+
const operands2 = strings.filter((s) => s && !s.startsWith("-"));
|
|
2770
|
+
const remote = operands2.slice(1).some((o) => /^[^/\s]+:/.test(o));
|
|
2771
|
+
for (const p of copySourcePaths(words))
|
|
2772
|
+
add("read", locate(p), state, remote ? { upload: true } : {});
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2615
2775
|
for (const p of copySourcePaths(words)) add("read", locate(p), state);
|
|
2616
2776
|
}
|
|
2777
|
+
function downloadOutputs(name, args, state) {
|
|
2778
|
+
const valueShort = DOWNLOAD_SHORT_VALUE[name];
|
|
2779
|
+
const outputs = [];
|
|
2780
|
+
const urls = [];
|
|
2781
|
+
let dir2 = null;
|
|
2782
|
+
let remoteName = false;
|
|
2783
|
+
let document = false;
|
|
2784
|
+
let serverNamed = false;
|
|
2785
|
+
const take = (flag, value) => {
|
|
2786
|
+
if (!value) return;
|
|
2787
|
+
const sent = uploadedFile(name, flag, value);
|
|
2788
|
+
if (sent) add("read", sent, state, { upload: true });
|
|
2789
|
+
if (DOWNLOAD_OUTPUT_FLAGS[name].includes(flag)) {
|
|
2790
|
+
if (["-O", "--output-document", "--out"].includes(flag) || name === "aria2c" && flag === "-o")
|
|
2791
|
+
document = true;
|
|
2792
|
+
if (value.value !== "-" || value.pattern || value.dynamic) outputs.push(value);
|
|
2793
|
+
} else if (DOWNLOAD_DIR_FLAGS[name].includes(flag)) dir2 = value;
|
|
2794
|
+
else if (name === "wget" && (flag === "-i" || flag === "--input-file")) serverNamed = true;
|
|
2795
|
+
};
|
|
2796
|
+
for (let i = 0; i < args.length; i++) {
|
|
2797
|
+
const token = args[i];
|
|
2798
|
+
if (!token) continue;
|
|
2799
|
+
const v = token.value;
|
|
2800
|
+
if (v.startsWith("--")) {
|
|
2801
|
+
const eq = v.indexOf("=");
|
|
2802
|
+
const flag = eq < 0 ? v : v.slice(0, eq);
|
|
2803
|
+
if (["--remote-name", "--remote-name-all"].includes(flag)) remoteName = true;
|
|
2804
|
+
if (["--remote-header-name", "--recursive", "--mirror", "--page-requisites"].includes(flag))
|
|
2805
|
+
serverNamed = true;
|
|
2806
|
+
if (DOWNLOAD_LONG_VALUE[name].includes(flag))
|
|
2807
|
+
take(flag, eq < 0 ? args[++i] ?? null : { ...token, value: v.slice(eq + 1) });
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
if (v.startsWith("-") && v.length > 1 && !token.dynamic) {
|
|
2811
|
+
for (let j = 1; j < v.length; j++) {
|
|
2812
|
+
const c = v[j];
|
|
2813
|
+
if (name === "wget" && c === "n") {
|
|
2814
|
+
j++;
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
if (name === "curl" && c === "O") remoteName = true;
|
|
2818
|
+
if (name === "curl" && c === "J") serverNamed = true;
|
|
2819
|
+
if (name === "wget" && (c === "r" || c === "m" || c === "p")) serverNamed = true;
|
|
2820
|
+
if (valueShort.includes(c)) {
|
|
2821
|
+
const rest = v.slice(j + 1);
|
|
2822
|
+
take(`-${c}`, rest ? { value: rest } : args[++i] ?? null);
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
continue;
|
|
2827
|
+
}
|
|
2828
|
+
if (/^(?:https?|ftps?):\/\//i.test(v)) urls.push(v);
|
|
2829
|
+
}
|
|
2830
|
+
for (const out2 of outputs) {
|
|
2831
|
+
const spelled = name === "aria2c" && dir2?.value && !out2.value.startsWith("/") ? { ...out2, value: `${dir2.value.replace(/\/+$/, "")}/${out2.value}` } : out2;
|
|
2832
|
+
if (name === "curl" && /#\d/.test(spelled.value))
|
|
2833
|
+
add("write", { value: "", pattern: spelled.value.replace(/#\d+/g, "*"), dot: true }, state);
|
|
2834
|
+
else add("write", spelled, state);
|
|
2835
|
+
}
|
|
2836
|
+
const remote = name === "curl" ? remoteName || serverNamed : !document || serverNamed;
|
|
2837
|
+
if (!remote) return;
|
|
2838
|
+
const into = dir2?.value ? dir2.value.replace(/\/+$/, "") : ".";
|
|
2839
|
+
if (serverNamed || urls.length === 0) {
|
|
2840
|
+
add("write", { value: "", pattern: `${quoteGlob(into)}/*`, dot: true }, state);
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
for (const url of urls) {
|
|
2844
|
+
const file2 = url.replace(/[?#].*$/, "").replace(/^[a-z]+:\/\/[^/]*/i, "").split("/").pop();
|
|
2845
|
+
add("write", { value: `${into}/${file2 || "index.html"}` }, state);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2617
2848
|
function findActions2(tokens, words, head, state, nested, depth) {
|
|
2618
2849
|
const starts = findStartPoints(words, head).starts;
|
|
2619
2850
|
const roots = (starts.length ? starts : ["."]).slice(0, MAX_ALTERNATIVES);
|
|
@@ -2685,7 +2916,35 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2685
2916
|
const words = wordValues(node, state);
|
|
2686
2917
|
alternatives = alternatives.flatMap((prefix) => (words.length ? words : [null]).map((w) => [...prefix, w])).slice(0, MAX_ALTERNATIVES);
|
|
2687
2918
|
}
|
|
2688
|
-
for (const tokens of alternatives)
|
|
2919
|
+
for (const tokens of alternatives) {
|
|
2920
|
+
fromWords(tokens, state, nested, depth);
|
|
2921
|
+
inlineCall(tokens, state, depth);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
function inlineCall(tokens, state, depth) {
|
|
2925
|
+
const head = tokens[0];
|
|
2926
|
+
if (!head || head.dynamic || head.pattern) return;
|
|
2927
|
+
const bodies = functions.get(head.value);
|
|
2928
|
+
if (!bodies || (state.callDepth ?? 0) >= MAX_CALL_DEPTH2) return;
|
|
2929
|
+
const args = tokens.slice(1).map((a) => a && !a.dynamic ? a.value : null);
|
|
2930
|
+
const vars = JSON.stringify([...state.vars].sort(([a], [b]) => a < b ? -1 : 1));
|
|
2931
|
+
const key = `${head.value}\0${JSON.stringify(args)}\0${state.cwd.join("")}\0${vars}`;
|
|
2932
|
+
if (seenCalls.has(key)) return;
|
|
2933
|
+
seenCalls.add(key);
|
|
2934
|
+
for (const body of bodies) {
|
|
2935
|
+
if (callWalks2-- <= 0) return;
|
|
2936
|
+
const local = clone(state);
|
|
2937
|
+
local.callDepth = (state.callDepth ?? 0) + 1;
|
|
2938
|
+
for (const name of POSITIONAL) local.vars.delete(name);
|
|
2939
|
+
if (!rebindsPositionals(body)) {
|
|
2940
|
+
args.slice(0, 9).forEach((v, i) => local.vars.set(String(i + 1), v === null ? null : [v]));
|
|
2941
|
+
const all = args.every((v) => v !== null) && args.length ? unique(args) : null;
|
|
2942
|
+
local.vars.set("@", all);
|
|
2943
|
+
local.vars.set("*", all);
|
|
2944
|
+
local.vars.set("#", [String(args.length)]);
|
|
2945
|
+
}
|
|
2946
|
+
statement(body, local, depth + 1);
|
|
2947
|
+
}
|
|
2689
2948
|
}
|
|
2690
2949
|
function readsScriptFromStdin(stmt) {
|
|
2691
2950
|
const cmd = stmt?.Cmd;
|
|
@@ -2722,11 +2981,13 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2722
2981
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
2723
2982
|
state.onSuccess = void 0;
|
|
2724
2983
|
if (stmt.Background) state = clone(state);
|
|
2984
|
+
const headWord = stmt.Cmd && syntax2.NodeType(stmt.Cmd) === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
2985
|
+
const sends = SENDING_STDIN.has(headWord);
|
|
2725
2986
|
for (const redir of stmt.Redirs ?? []) {
|
|
2726
2987
|
substitutions(redir.Word, state, depth);
|
|
2727
2988
|
for (const word of wordValues(redir.Word, state)) {
|
|
2728
2989
|
if (WRITES.has(redir.Op)) add("write", word, state);
|
|
2729
|
-
if (READS.has(redir.Op)) add("read", word, state);
|
|
2990
|
+
if (READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
|
|
2730
2991
|
if (redir.Op === DUP_OUT && word.value && !/^\d+$/.test(word.value))
|
|
2731
2992
|
add("write", word, state);
|
|
2732
2993
|
}
|
|
@@ -2835,7 +3096,10 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2835
3096
|
}
|
|
2836
3097
|
const parsed = parseShared(command);
|
|
2837
3098
|
const program = typeof parsed !== "symbol" ? parsed : runnablePrefix(command);
|
|
2838
|
-
if (program)
|
|
3099
|
+
if (program) {
|
|
3100
|
+
for (const [name, bodies] of functionBodies(program)) functions.set(name, bodies);
|
|
3101
|
+
statements(program.Stmts ?? [], initial, 0);
|
|
3102
|
+
}
|
|
2839
3103
|
return out;
|
|
2840
3104
|
}
|
|
2841
3105
|
function isAgentInstructionPath(absPath, root) {
|
|
@@ -2951,6 +3215,33 @@ function hits(access, paths, directories, context) {
|
|
|
2951
3215
|
}
|
|
2952
3216
|
return false;
|
|
2953
3217
|
}
|
|
3218
|
+
function hitsSystemDir(access, context, dirs = SYSTEM_DIRS) {
|
|
3219
|
+
const home = context.homeDirs?.[0] ?? "~";
|
|
3220
|
+
const candidates = access.candidates ?? [
|
|
3221
|
+
normalizeFilePath(access.path, access.cwd ?? context.cwd, home)
|
|
3222
|
+
];
|
|
3223
|
+
for (const p of candidates) {
|
|
3224
|
+
if (dirs.some((d) => within(p, d)) && !exempt(p)) return true;
|
|
3225
|
+
if (access.recursive && dirs.some((d) => within(d, p))) return true;
|
|
3226
|
+
}
|
|
3227
|
+
if (!access.glob) return false;
|
|
3228
|
+
const pattern = normalizeFilePath(access.glob, access.cwd ?? context.cwd, home);
|
|
3229
|
+
const magic = pattern.search(/[*?[{]/);
|
|
3230
|
+
const literal = magic < 0 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", magic) + 1);
|
|
3231
|
+
if (literal && exempt(literal.replace(/\/$/, "") || "/")) return false;
|
|
3232
|
+
const options = { dot: access.dotGlob === true, nonegate: true };
|
|
3233
|
+
const components = pattern.split("/");
|
|
3234
|
+
for (const root of dirs) {
|
|
3235
|
+
const count = root.split("/").length;
|
|
3236
|
+
if (components.length > count && (0, import_picomatch.default)(components.slice(0, count).join("/"), options)(root))
|
|
3237
|
+
return true;
|
|
3238
|
+
if (access.recursive && (0, import_picomatch.default)(pattern, options)(root)) return true;
|
|
3239
|
+
}
|
|
3240
|
+
return false;
|
|
3241
|
+
}
|
|
3242
|
+
function uploadDirs(context) {
|
|
3243
|
+
return context.homeDirs?.[0] === "/root" ? UPLOAD_SYSTEM_DIRS.filter((d) => d !== "/root") : UPLOAD_SYSTEM_DIRS;
|
|
3244
|
+
}
|
|
2954
3245
|
function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
2955
3246
|
const homes = [.../* @__PURE__ */ new Set([...context.homeDirs ?? ["~"], "/root"])];
|
|
2956
3247
|
const startup = homes.flatMap(
|
|
@@ -2972,6 +3263,14 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2972
3263
|
"files.shell-startup",
|
|
2973
3264
|
"review-shell-startup-mutation",
|
|
2974
3265
|
"Changing shell startup files requires approval"
|
|
3266
|
+
] : mutation && hitsSystemDir(access, context) ? [
|
|
3267
|
+
"files.system-dirs",
|
|
3268
|
+
"review-system-dir-mutation",
|
|
3269
|
+
"Changing files in a system directory requires approval"
|
|
3270
|
+
] : !mutation && access.upload && hitsSystemDir(access, context, uploadDirs(context)) ? [
|
|
3271
|
+
"files.system-dirs",
|
|
3272
|
+
"review-system-file-upload",
|
|
3273
|
+
"Uploading a system file requires approval"
|
|
2975
3274
|
] : !mutation && hits(access, PASSWORD_HASH_FILES, [], context) ? [
|
|
2976
3275
|
"files.password-hashes",
|
|
2977
3276
|
"review-password-hash-read",
|
|
@@ -3232,16 +3531,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
|
|
|
3232
3531
|
if (EMAIL_VALUE_RE.test(value)) return false;
|
|
3233
3532
|
if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
|
|
3234
3533
|
if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
|
|
3534
|
+
if (CONCATENATION_RE.test(value)) return false;
|
|
3235
3535
|
if (rejectedByStructuredValidator(value)) return false;
|
|
3236
3536
|
return shannonEntropy(value) >= minEntropy;
|
|
3237
3537
|
}
|
|
3238
3538
|
function nameHoldsCredential(name) {
|
|
3239
3539
|
return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
|
|
3240
3540
|
}
|
|
3541
|
+
function isOtpDocSecret(value) {
|
|
3542
|
+
return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
|
|
3543
|
+
}
|
|
3241
3544
|
function validateQueryCredential(raw) {
|
|
3242
3545
|
const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
|
|
3243
3546
|
if (value === null) return false;
|
|
3244
|
-
if (OTP_SECRET_RE.test(value))
|
|
3547
|
+
if (OTP_SECRET_RE.test(value))
|
|
3548
|
+
return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
|
|
3245
3549
|
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
|
|
3246
3550
|
return looksGenerated(value, 12, 3);
|
|
3247
3551
|
}
|
|
@@ -3255,13 +3559,40 @@ function keywordEndsAPart(name) {
|
|
|
3255
3559
|
}
|
|
3256
3560
|
function validateAssignedSecret(raw, before = "") {
|
|
3257
3561
|
const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
|
|
3258
|
-
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
|
|
3562
|
+
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
|
|
3259
3563
|
if (value === null || name === null) return false;
|
|
3260
|
-
if (!keywordEndsAPart(name)
|
|
3261
|
-
|
|
3564
|
+
if (!keywordEndsAPart(name)) return false;
|
|
3565
|
+
if (ARN_CONTEXT_RE.test(before)) return false;
|
|
3566
|
+
const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
|
|
3262
3567
|
if (!nameHoldsCredential(prefix + name)) return false;
|
|
3263
3568
|
return looksGenerated(value, 12, 3.5);
|
|
3264
3569
|
}
|
|
3570
|
+
function validateBasicCredential(raw) {
|
|
3571
|
+
const b64 = capturedValue(raw, BASIC_VALUE_RE);
|
|
3572
|
+
if (b64 === null) return false;
|
|
3573
|
+
const bare = b64.replace(/=+$/, "");
|
|
3574
|
+
if (bare.length % 4 === 1) return false;
|
|
3575
|
+
const bytes = Buffer.from(b64, "base64");
|
|
3576
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
|
|
3577
|
+
const text = bytes.toString("utf8");
|
|
3578
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) return false;
|
|
3579
|
+
for (let i = 0; i < text.length; i++) {
|
|
3580
|
+
const c = text.charCodeAt(i);
|
|
3581
|
+
if (c < 32 || c === 127) return false;
|
|
3582
|
+
}
|
|
3583
|
+
const pair = BASIC_PAIR_RE.exec(text);
|
|
3584
|
+
if (!pair) return false;
|
|
3585
|
+
const password = pair[2];
|
|
3586
|
+
if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
|
|
3587
|
+
const lower2 = password.toLowerCase();
|
|
3588
|
+
if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
|
|
3589
|
+
return shannonEntropy(password) >= 3;
|
|
3590
|
+
}
|
|
3591
|
+
function validateOtpSecret(raw) {
|
|
3592
|
+
const secret = capturedValue(raw, OTP_CAPTURE_RE);
|
|
3593
|
+
if (secret === null || isOtpDocSecret(secret)) return false;
|
|
3594
|
+
return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
|
|
3595
|
+
}
|
|
3265
3596
|
function matchSensitivePath2(resolvedPath, originalPath, options = {}) {
|
|
3266
3597
|
const normalised = resolvedPath.replace(/\\/g, "/");
|
|
3267
3598
|
if (!options.systemAccessHandled && isLegacySystemPath(normalised))
|
|
@@ -3532,6 +3863,156 @@ function parsePySpec(raw) {
|
|
|
3532
3863
|
const exact = PY_EXACT_RE.exec(m[2].trim());
|
|
3533
3864
|
return { name, version: exact ? exact[1] : void 0 };
|
|
3534
3865
|
}
|
|
3866
|
+
function exactVersion(v) {
|
|
3867
|
+
if (!v) return void 0;
|
|
3868
|
+
const m = EXACT_VERSION_RE.exec(v.trim());
|
|
3869
|
+
return m ? m[1] : void 0;
|
|
3870
|
+
}
|
|
3871
|
+
function push(out, ecosystem, manager, name, version, raw) {
|
|
3872
|
+
out.push({ ecosystem, manager, name, ...version ? { version } : {}, raw });
|
|
3873
|
+
}
|
|
3874
|
+
function gemInstall(words, from, out) {
|
|
3875
|
+
const dash = words.indexOf("--", from);
|
|
3876
|
+
const own = dash < 0 ? words : words.slice(0, dash);
|
|
3877
|
+
const pinned = exactVersion(flagValues(own, from, ["-v", "--version"])[0]);
|
|
3878
|
+
for (const raw of operands(own, from, GEM_VALUE_FLAGS)) {
|
|
3879
|
+
if (NON_REGISTRY_RE.test(raw) || /\.gem$/i.test(raw)) continue;
|
|
3880
|
+
const colon = raw.indexOf(":");
|
|
3881
|
+
const name = colon < 0 ? raw : raw.slice(0, colon);
|
|
3882
|
+
if (!GEM_NAME_RE.test(name)) continue;
|
|
3883
|
+
push(
|
|
3884
|
+
out,
|
|
3885
|
+
"RubyGems",
|
|
3886
|
+
"gem",
|
|
3887
|
+
name,
|
|
3888
|
+
colon < 0 ? pinned : exactVersion(raw.slice(colon + 1)),
|
|
3889
|
+
raw
|
|
3890
|
+
);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
function cargoCrates(words, from, manager, out) {
|
|
3894
|
+
const flags = manager === "cargo add" ? CARGO_ADD_VALUE_FLAGS : CARGO_INSTALL_VALUE_FLAGS;
|
|
3895
|
+
if (flagValues(words, from, ["--git", "--path", "--registry", "--index"]).length > 0) return;
|
|
3896
|
+
if (words.some((w) => w === "--git" || w === "--path")) return;
|
|
3897
|
+
const flagVersion = manager === "cargo install" ? exactVersion(flagValues(words, from, ["--version", "--vers"])[0]) : void 0;
|
|
3898
|
+
for (const raw of operands(words, from, flags)) {
|
|
3899
|
+
const at = raw.indexOf("@");
|
|
3900
|
+
const name = at < 0 ? raw : raw.slice(0, at);
|
|
3901
|
+
if (!CRATE_NAME_RE.test(name)) continue;
|
|
3902
|
+
push(
|
|
3903
|
+
out,
|
|
3904
|
+
"crates.io",
|
|
3905
|
+
manager,
|
|
3906
|
+
name,
|
|
3907
|
+
at < 0 ? flagVersion : exactVersion(raw.slice(at + 1)),
|
|
3908
|
+
raw
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
function goModules(words, from, manager, out) {
|
|
3913
|
+
let specs = operands(words, from, GO_VALUE_FLAGS);
|
|
3914
|
+
if (manager === "go run") specs = specs.slice(0, 1).filter((s) => s.includes("@"));
|
|
3915
|
+
for (const raw of specs) {
|
|
3916
|
+
const at = raw.indexOf("@");
|
|
3917
|
+
const pathPart = (at < 0 ? raw : raw.slice(0, at)).replace(/\/\.\.\.$/, "");
|
|
3918
|
+
const ver = at < 0 ? void 0 : raw.slice(at + 1);
|
|
3919
|
+
if (ver === "none") continue;
|
|
3920
|
+
if (!GO_PATH_RE.test(pathPart) || /\.go$/i.test(pathPart)) continue;
|
|
3921
|
+
const exact = ver && /^v\d/.test(ver) ? exactVersion(ver) : void 0;
|
|
3922
|
+
push(out, "Go", manager, pathPart, exact, raw);
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
function dotnetAdd(words, from, out) {
|
|
3926
|
+
let k = -1;
|
|
3927
|
+
for (let i = from; i < Math.min(words.length, from + 2); i++) {
|
|
3928
|
+
if (lower(words[i]) === "package") {
|
|
3929
|
+
k = i;
|
|
3930
|
+
break;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
if (k < 0) return;
|
|
3934
|
+
const name = operands(words, k + 1, DOTNET_ADD_VALUE_FLAGS)[0];
|
|
3935
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
3936
|
+
const version = exactVersion(flagValues(words, k + 1, ["-v", "--version"])[0]);
|
|
3937
|
+
push(out, "NuGet", "dotnet add package", name, version, name);
|
|
3938
|
+
}
|
|
3939
|
+
function dotnetTool(words, from, out) {
|
|
3940
|
+
const name = operands(words, from, DOTNET_TOOL_VALUE_FLAGS)[0];
|
|
3941
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
3942
|
+
const version = exactVersion(flagValues(words, from, ["--version"])[0]);
|
|
3943
|
+
push(out, "NuGet", "dotnet tool", name, version, name);
|
|
3944
|
+
}
|
|
3945
|
+
function nugetInstall(words, from, out) {
|
|
3946
|
+
const lowered = words.map((w) => w !== null && w.startsWith("-") ? w.toLowerCase() : w);
|
|
3947
|
+
const name = operands(lowered, from, NUGET_VALUE_FLAGS)[0];
|
|
3948
|
+
if (!name || !NUGET_NAME_RE.test(name) || /\.config$/i.test(name)) return;
|
|
3949
|
+
const version = exactVersion(flagValues(lowered, from, ["-version"])[0]);
|
|
3950
|
+
push(out, "NuGet", "nuget install", name, version, name);
|
|
3951
|
+
}
|
|
3952
|
+
function mavenGet(words, from, out) {
|
|
3953
|
+
const goal = words.slice(from).some(
|
|
3954
|
+
(w) => w !== null && /(?:^|:)dependency:get$|^org\.apache\.maven\.plugins:maven-dependency-plugin:(?:[\w.-]+:)?get$/.test(
|
|
3955
|
+
w
|
|
3956
|
+
)
|
|
3957
|
+
);
|
|
3958
|
+
if (!goal) return;
|
|
3959
|
+
const prop = (key) => {
|
|
3960
|
+
for (let i = from; i < words.length; i++) {
|
|
3961
|
+
const w = words[i];
|
|
3962
|
+
if (w === null) continue;
|
|
3963
|
+
if (w.startsWith(`-D${key}=`)) return w.slice(key.length + 3);
|
|
3964
|
+
if (w === "-D" && words[i + 1]?.startsWith(`${key}=`))
|
|
3965
|
+
return words[i + 1].slice(key.length + 1);
|
|
3966
|
+
}
|
|
3967
|
+
return void 0;
|
|
3968
|
+
};
|
|
3969
|
+
const artifact = prop("artifact");
|
|
3970
|
+
let coord;
|
|
3971
|
+
let ver;
|
|
3972
|
+
if (artifact) {
|
|
3973
|
+
const parts = artifact.split(":");
|
|
3974
|
+
if (parts.length < 2) return;
|
|
3975
|
+
coord = `${parts[0]}:${parts[1]}`;
|
|
3976
|
+
ver = parts[2];
|
|
3977
|
+
} else {
|
|
3978
|
+
const g = prop("groupId");
|
|
3979
|
+
const a = prop("artifactId");
|
|
3980
|
+
if (!g || !a) return;
|
|
3981
|
+
coord = `${g}:${a}`;
|
|
3982
|
+
ver = prop("version");
|
|
3983
|
+
}
|
|
3984
|
+
if (!MAVEN_COORD_RE.test(coord)) return;
|
|
3985
|
+
const exact = ver && !/^(?:LATEST|RELEASE)$/i.test(ver) ? exactVersion(ver) : void 0;
|
|
3986
|
+
push(out, "Maven", "mvn dependency:get", coord, exact, artifact ?? coord);
|
|
3987
|
+
}
|
|
3988
|
+
function composerPackages(words, from, manager, out) {
|
|
3989
|
+
const ops = operands(words, from, COMPOSER_VALUE_FLAGS);
|
|
3990
|
+
const specs = manager === "composer create-project" ? ops.slice(0, 1) : ops;
|
|
3991
|
+
for (const raw of specs) {
|
|
3992
|
+
const sep = raw.search(/[:=]/);
|
|
3993
|
+
const name = sep < 0 ? raw : raw.slice(0, sep);
|
|
3994
|
+
if (!COMPOSER_NAME_RE.test(name)) continue;
|
|
3995
|
+
let ver = sep < 0 ? void 0 : raw.slice(sep + 1);
|
|
3996
|
+
if (manager === "composer create-project" && ver === void 0) ver = ops[2] ?? void 0;
|
|
3997
|
+
push(out, "Packagist", manager, name.toLowerCase(), exactVersion(ver), raw);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
function editorExtensions(words, from, out) {
|
|
4001
|
+
for (const raw of flagValues(words, from, ["--install-extension"])) {
|
|
4002
|
+
if (/\.vsix$/i.test(raw) || NON_REGISTRY_RE.test(raw)) continue;
|
|
4003
|
+
const at = raw.lastIndexOf("@");
|
|
4004
|
+
const name = at <= 0 ? raw : raw.slice(0, at);
|
|
4005
|
+
if (!EXTENSION_ID_RE.test(name)) continue;
|
|
4006
|
+
push(
|
|
4007
|
+
out,
|
|
4008
|
+
"VSCode",
|
|
4009
|
+
"code --install-extension",
|
|
4010
|
+
name,
|
|
4011
|
+
at <= 0 ? void 0 : exactVersion(raw.slice(at + 1)),
|
|
4012
|
+
raw
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3535
4016
|
function basename(w) {
|
|
3536
4017
|
return (w ?? "").toLowerCase().split("/").pop() ?? "";
|
|
3537
4018
|
}
|
|
@@ -3763,7 +4244,63 @@ function fromCall(words, out, depth) {
|
|
|
3763
4244
|
pushPy(out, "pipenv", operands(words, v + 1, PIPENV_VALUE_FLAGS));
|
|
3764
4245
|
return;
|
|
3765
4246
|
}
|
|
4247
|
+
case "gem": {
|
|
4248
|
+
const v = verbIndex(words, start + 1, GEM_VALUE_FLAGS);
|
|
4249
|
+
if (v >= 0 && ["install", "i"].includes(lower(words[v]))) gemInstall(words, v + 1, out);
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
4252
|
+
case "bundle": {
|
|
4253
|
+
const v = verbIndex(words, start + 1, BUNDLE_VALUE_FLAGS);
|
|
4254
|
+
if (v < 0 || lower(words[v]) !== "add") return;
|
|
4255
|
+
if (flagValues(words, v + 1, ["--git", "--github", "--path"]).length > 0) return;
|
|
4256
|
+
const version = exactVersion(flagValues(words, v + 1, ["-v", "--version"])[0]);
|
|
4257
|
+
for (const raw of operands(words, v + 1, BUNDLE_VALUE_FLAGS))
|
|
4258
|
+
if (GEM_NAME_RE.test(raw)) push(out, "RubyGems", "bundle add", raw, version, raw);
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
case "cargo": {
|
|
4262
|
+
const from = words[start + 1]?.startsWith("+") ? start + 2 : start + 1;
|
|
4263
|
+
const v = verbIndex(words, from, CARGO_ADD_VALUE_FLAGS);
|
|
4264
|
+
const verb = lower(words[v]);
|
|
4265
|
+
if (verb === "add") cargoCrates(words, v + 1, "cargo add", out);
|
|
4266
|
+
else if (verb === "install") cargoCrates(words, v + 1, "cargo install", out);
|
|
4267
|
+
return;
|
|
4268
|
+
}
|
|
4269
|
+
case "go": {
|
|
4270
|
+
const v = verbIndex(words, start + 1, GO_VALUE_FLAGS);
|
|
4271
|
+
const verb = lower(words[v]);
|
|
4272
|
+
if (verb === "get" || verb === "install" || verb === "run")
|
|
4273
|
+
goModules(words, v + 1, `go ${verb}`, out);
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
case "dotnet": {
|
|
4277
|
+
const v = verbIndex(words, start + 1, DOTNET_ADD_VALUE_FLAGS);
|
|
4278
|
+
const verb = lower(words[v]);
|
|
4279
|
+
if (verb === "add") dotnetAdd(words, v + 1, out);
|
|
4280
|
+
else if (verb === "tool" && ["install", "update"].includes(lower(words[v + 1])))
|
|
4281
|
+
dotnetTool(words, v + 2, out);
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
case "nuget": {
|
|
4285
|
+
if (lower(words[start + 1]) === "install") nugetInstall(words, start + 2, out);
|
|
4286
|
+
return;
|
|
4287
|
+
}
|
|
4288
|
+
case "mvn":
|
|
4289
|
+
case "mvnw":
|
|
4290
|
+
mavenGet(words, start + 1, out);
|
|
4291
|
+
return;
|
|
4292
|
+
case "composer": {
|
|
4293
|
+
let v = verbIndex(words, start + 1, COMPOSER_VALUE_FLAGS);
|
|
4294
|
+
if (v >= 0 && lower(words[v]) === "global") v = verbIndex(words, v + 1, COMPOSER_VALUE_FLAGS);
|
|
4295
|
+
const verb = lower(words[v]);
|
|
4296
|
+
if (verb === "require" || verb === "req" || verb === "r")
|
|
4297
|
+
composerPackages(words, v + 1, "composer require", out);
|
|
4298
|
+
else if (verb === "create-project")
|
|
4299
|
+
composerPackages(words, v + 1, "composer create-project", out);
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
3766
4302
|
default:
|
|
4303
|
+
if (EDITOR_HEADS.includes(family)) editorExtensions(words, start + 1, out);
|
|
3767
4304
|
return;
|
|
3768
4305
|
}
|
|
3769
4306
|
}
|
|
@@ -5268,11 +5805,11 @@ function isDecimalFraction(text, index, unseparated) {
|
|
|
5268
5805
|
const a = text[index - 2];
|
|
5269
5806
|
if (sep === ".") {
|
|
5270
5807
|
if (a !== void 0 && a >= "0" && a <= "9") return true;
|
|
5271
|
-
return a === void 0 ||
|
|
5808
|
+
return a === void 0 || (a === "-" || a === "+") && index === 2;
|
|
5272
5809
|
}
|
|
5273
5810
|
if (sep === "," && a === "0") {
|
|
5274
5811
|
const b = text[index - 3];
|
|
5275
|
-
return b === void 0 ||
|
|
5812
|
+
return b === void 0 || b === ";";
|
|
5276
5813
|
}
|
|
5277
5814
|
return false;
|
|
5278
5815
|
}
|
|
@@ -5946,7 +6483,7 @@ function matchCanaryArgs(args, values) {
|
|
|
5946
6483
|
return null;
|
|
5947
6484
|
}
|
|
5948
6485
|
}
|
|
5949
|
-
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_crypto4, import_mvdan_sh4, import_crypto5, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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,
|
|
6486
|
+
var import_picomatch, import_safe_regex2, import_picomatch2, import_mvdan_sh, import_safe_regex22, import_mvdan_sh2, import_safe_regex23, import_crypto2, import_crypto3, import_mvdan_sh3, import_crypto4, import_mvdan_sh4, import_crypto5, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, 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;
|
|
5950
6487
|
var init_dist = __esm({
|
|
5951
6488
|
"packages/policy-engine/dist/index.mjs"() {
|
|
5952
6489
|
"use strict";
|
|
@@ -6760,6 +7297,11 @@ var init_dist = __esm({
|
|
|
6760
7297
|
example: "cat /etc/shadow",
|
|
6761
7298
|
advice: "Keep Review, or choose Block when agents must never read system password hashes."
|
|
6762
7299
|
},
|
|
7300
|
+
"files.system-dirs": {
|
|
7301
|
+
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.",
|
|
7302
|
+
example: "wget -O /usr/bin/tool https://example.com/tool",
|
|
7303
|
+
advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
|
|
7304
|
+
},
|
|
6763
7305
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
6764
7306
|
"commands.inline-exec": {
|
|
6765
7307
|
plain: "The agent runs a small program written inside the command itself.",
|
|
@@ -6956,7 +7498,7 @@ var init_dist = __esm({
|
|
|
6956
7498
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
6957
7499
|
},
|
|
6958
7500
|
"loading.malicious-package": {
|
|
6959
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
|
|
7501
|
+
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.",
|
|
6960
7502
|
example: "npm install of a package flagged as malware",
|
|
6961
7503
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
6962
7504
|
}
|
|
@@ -6994,6 +7536,15 @@ var init_dist = __esm({
|
|
|
6994
7536
|
defaultValue: "review",
|
|
6995
7537
|
legacy: ["review-password-hash-read"]
|
|
6996
7538
|
},
|
|
7539
|
+
{
|
|
7540
|
+
id: "files.system-dirs",
|
|
7541
|
+
group: "files",
|
|
7542
|
+
title: "System directories",
|
|
7543
|
+
catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
|
|
7544
|
+
values: ALL,
|
|
7545
|
+
defaultValue: "review",
|
|
7546
|
+
legacy: ["review-system-dir-mutation", "review-system-file-upload"]
|
|
7547
|
+
},
|
|
6997
7548
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
6998
7549
|
{
|
|
6999
7550
|
id: "commands.inline-exec",
|
|
@@ -8291,6 +8842,10 @@ var init_dist = __esm({
|
|
|
8291
8842
|
AND_OP = probeBinOp("a && b");
|
|
8292
8843
|
OR_OP = probeBinOp("a || b");
|
|
8293
8844
|
AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
8845
|
+
MAX_CALL_DEPTH = 4;
|
|
8846
|
+
MAX_CALL_WALKS = 256;
|
|
8847
|
+
callDepth = 0;
|
|
8848
|
+
callWalks = 0;
|
|
8294
8849
|
FS_OP_CACHE_MAX = 5e3;
|
|
8295
8850
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
8296
8851
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
@@ -8343,10 +8898,79 @@ var init_dist = __esm({
|
|
|
8343
8898
|
MAX_ARGV_CHARACTERS = 262144;
|
|
8344
8899
|
({ syntax: syntax2 } = import_mvdan_sh2.default);
|
|
8345
8900
|
MAX_ALTERNATIVES = 64;
|
|
8901
|
+
MAX_CALL_DEPTH2 = 4;
|
|
8902
|
+
MAX_CALL_WALKS2 = 256;
|
|
8903
|
+
POSITIONAL = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"];
|
|
8346
8904
|
MAX_DEPTH = 64;
|
|
8347
8905
|
MAX_PAYLOAD_DEPTH = 24;
|
|
8348
8906
|
unique = (a) => [...new Set(a)].slice(0, MAX_ALTERNATIVES);
|
|
8349
|
-
clone = (s) => ({
|
|
8907
|
+
clone = (s) => ({
|
|
8908
|
+
cwd: [...s.cwd],
|
|
8909
|
+
vars: new Map(s.vars),
|
|
8910
|
+
...s.callDepth ? { callDepth: s.callDepth } : {}
|
|
8911
|
+
});
|
|
8912
|
+
SENDING_STDIN = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "socat", "telnet", "curl", "wget"]);
|
|
8913
|
+
DOWNLOAD_SHORT_VALUE = {
|
|
8914
|
+
curl: "odDeEFHKmPrtTuUwxXyYzAbcCQ",
|
|
8915
|
+
wget: "OoaPiBetTwQlARDXIUY",
|
|
8916
|
+
aria2c: "odisx"
|
|
8917
|
+
};
|
|
8918
|
+
DOWNLOAD_LONG_VALUE = {
|
|
8919
|
+
curl: [
|
|
8920
|
+
"--output",
|
|
8921
|
+
"--output-dir",
|
|
8922
|
+
"--dump-header",
|
|
8923
|
+
"--cookie-jar",
|
|
8924
|
+
"--data",
|
|
8925
|
+
"--data-binary",
|
|
8926
|
+
"--data-raw",
|
|
8927
|
+
"--data-ascii",
|
|
8928
|
+
"--data-urlencode",
|
|
8929
|
+
"--form",
|
|
8930
|
+
"--header",
|
|
8931
|
+
"--request",
|
|
8932
|
+
"--user",
|
|
8933
|
+
"--user-agent",
|
|
8934
|
+
"--referer",
|
|
8935
|
+
"--cookie",
|
|
8936
|
+
"--config",
|
|
8937
|
+
"--json",
|
|
8938
|
+
"--upload-file",
|
|
8939
|
+
"--max-time",
|
|
8940
|
+
"--proxy",
|
|
8941
|
+
"--write-out",
|
|
8942
|
+
"--url"
|
|
8943
|
+
],
|
|
8944
|
+
wget: [
|
|
8945
|
+
"--output-document",
|
|
8946
|
+
"--output-file",
|
|
8947
|
+
"--append-output",
|
|
8948
|
+
"--directory-prefix",
|
|
8949
|
+
"--input-file",
|
|
8950
|
+
"--post-data",
|
|
8951
|
+
"--post-file",
|
|
8952
|
+
"--body-data",
|
|
8953
|
+
"--body-file",
|
|
8954
|
+
"--method",
|
|
8955
|
+
"--header",
|
|
8956
|
+
"--user-agent",
|
|
8957
|
+
"--user",
|
|
8958
|
+
"--password",
|
|
8959
|
+
"--tries",
|
|
8960
|
+
"--timeout"
|
|
8961
|
+
],
|
|
8962
|
+
aria2c: ["--out", "--dir", "--input-file"]
|
|
8963
|
+
};
|
|
8964
|
+
DOWNLOAD_OUTPUT_FLAGS = {
|
|
8965
|
+
curl: ["-o", "--output", "-D", "--dump-header", "-c", "--cookie-jar"],
|
|
8966
|
+
wget: ["-O", "--output-document", "-o", "--output-file", "-a", "--append-output"],
|
|
8967
|
+
aria2c: ["-o", "--out"]
|
|
8968
|
+
};
|
|
8969
|
+
DOWNLOAD_DIR_FLAGS = {
|
|
8970
|
+
curl: ["--output-dir"],
|
|
8971
|
+
wget: ["-P", "--directory-prefix"],
|
|
8972
|
+
aria2c: ["-d", "--dir"]
|
|
8973
|
+
};
|
|
8350
8974
|
base = (s) => (s ?? "").replace(/\/+$/, "").split("/").pop() ?? "";
|
|
8351
8975
|
quoteGlob = (s) => s.replace(/[\\*?\[\]{}()!+@]/g, "\\$&");
|
|
8352
8976
|
asWord = (p) => ({ value: p.value, ...p.glob ? { pattern: p.pattern } : {} });
|
|
@@ -8371,6 +8995,18 @@ var init_dist = __esm({
|
|
|
8371
8995
|
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
8372
8996
|
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
8373
8997
|
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
8998
|
+
SYSTEM_DIRS = [
|
|
8999
|
+
"/usr",
|
|
9000
|
+
"/etc",
|
|
9001
|
+
"/boot",
|
|
9002
|
+
"/bin",
|
|
9003
|
+
"/sbin",
|
|
9004
|
+
"/lib",
|
|
9005
|
+
"/lib64",
|
|
9006
|
+
"/var/log"
|
|
9007
|
+
];
|
|
9008
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
9009
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
8374
9010
|
AGENT_INSTRUCTION_FILES = [
|
|
8375
9011
|
".gitlab-ci.yml",
|
|
8376
9012
|
"CLAUDE.md",
|
|
@@ -8388,6 +9024,7 @@ var init_dist = __esm({
|
|
|
8388
9024
|
".husky"
|
|
8389
9025
|
];
|
|
8390
9026
|
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9027
|
+
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
8391
9028
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
8392
9029
|
BIP39_ENGLISH = `abandon
|
|
8393
9030
|
ability
|
|
@@ -10673,15 +11310,20 @@ zoo`.split("\n");
|
|
|
10673
11310
|
IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
|
|
10674
11311
|
PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
|
|
10675
11312
|
DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
|
|
11313
|
+
CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
|
|
10676
11314
|
EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
|
|
10677
11315
|
FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
|
|
10678
|
-
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;
|
|
11316
|
+
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;
|
|
10679
11317
|
QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
|
|
10680
11318
|
OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
|
|
10681
|
-
|
|
11319
|
+
OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
|
|
11320
|
+
ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
|
|
10682
11321
|
ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
|
|
10683
|
-
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
|
|
11322
|
+
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;
|
|
10684
11323
|
ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
|
|
11324
|
+
BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
|
|
11325
|
+
BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
|
|
11326
|
+
OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
|
|
10685
11327
|
DLP_PATTERNS = [
|
|
10686
11328
|
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
10687
11329
|
{
|
|
@@ -11019,6 +11661,22 @@ zoo`.split("\n");
|
|
|
11019
11661
|
contextBoost: true,
|
|
11020
11662
|
minEntropy: 3
|
|
11021
11663
|
},
|
|
11664
|
+
// ── HTTP Basic credential ─────────────────────────────────────────────────
|
|
11665
|
+
// `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
|
|
11666
|
+
// config list. The validator decodes the value and accepts only a
|
|
11667
|
+
// `user:password` whose password looks generated (letters and digits, 8+
|
|
11668
|
+
// characters, the query-credential rule), so prose ("Basic authentication")
|
|
11669
|
+
// and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
|
|
11670
|
+
// review: the same weight as a bare Bearer token.
|
|
11671
|
+
{
|
|
11672
|
+
name: "HTTP Basic Credential",
|
|
11673
|
+
regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
|
|
11674
|
+
severity: "review",
|
|
11675
|
+
keywords: ["basic"],
|
|
11676
|
+
validate: validateBasicCredential,
|
|
11677
|
+
redactGroup: 1,
|
|
11678
|
+
argumentsOnly: true
|
|
11679
|
+
},
|
|
11022
11680
|
// ── Resend ────────────────────────────────────────────────────────────────
|
|
11023
11681
|
{
|
|
11024
11682
|
name: "Resend API Key",
|
|
@@ -11160,6 +11818,19 @@ zoo`.split("\n");
|
|
|
11160
11818
|
validate: validateQueryCredential,
|
|
11161
11819
|
argumentsOnly: true
|
|
11162
11820
|
},
|
|
11821
|
+
// ── OTP secret in a constructor ───────────────────────────────────────────
|
|
11822
|
+
// `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
|
|
11823
|
+
// the shared secret of a one-time-password generator written as a literal.
|
|
11824
|
+
// The otpauth URI form is `Credential in URL Query`. review.
|
|
11825
|
+
{
|
|
11826
|
+
name: "OTP Secret",
|
|
11827
|
+
regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
|
|
11828
|
+
severity: "review",
|
|
11829
|
+
keywords: ["otp"],
|
|
11830
|
+
validate: validateOtpSecret,
|
|
11831
|
+
redactGroup: 1,
|
|
11832
|
+
argumentsOnly: true
|
|
11833
|
+
},
|
|
11163
11834
|
// ── Secret assigned to a telling name ─────────────────────────────────────
|
|
11164
11835
|
// `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
|
|
11165
11836
|
// not a finding: most such lines hold a reference (`process.env.X`), a
|
|
@@ -11167,12 +11838,14 @@ zoo`.split("\n");
|
|
|
11167
11838
|
// generated: 12+ characters, not an identifier, Shannon entropy of 3.5
|
|
11168
11839
|
// bits per character or more. review, never block (node9Firewall#348,
|
|
11169
11840
|
// item 4). Tuned on CredData's public split; its look-alike lines are the
|
|
11170
|
-
// negative control.
|
|
11841
|
+
// negative control. A quote may be escaped (`\"access_token\":\"...\"`):
|
|
11842
|
+
// JSON written inside a string literal. The two optional quotes after the
|
|
11843
|
+
// name are written out, not as a counted group, to keep star height 1.
|
|
11171
11844
|
{
|
|
11172
11845
|
name: "Assigned Secret",
|
|
11173
|
-
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]
|
|
11846
|
+
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,
|
|
11174
11847
|
severity: "review",
|
|
11175
|
-
keywords: ["
|
|
11848
|
+
keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
|
|
11176
11849
|
validate: validateAssignedSecret,
|
|
11177
11850
|
argumentsOnly: true
|
|
11178
11851
|
}
|
|
@@ -11433,7 +12106,169 @@ zoo`.split("\n");
|
|
|
11433
12106
|
]);
|
|
11434
12107
|
PY_SPEC_RE = /^([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?:\[[^\]]*\])?\s*(.*)$/;
|
|
11435
12108
|
PY_EXACT_RE = /^===?\s*v?(\d+(?:\.\d+)*(?:[a-zA-Z0-9.+!-]*))$/;
|
|
12109
|
+
EXACT_VERSION_RE = /^=?v?(\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?)$/;
|
|
12110
|
+
GEM_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12111
|
+
GEM_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12112
|
+
"-v",
|
|
12113
|
+
"--version",
|
|
12114
|
+
"-i",
|
|
12115
|
+
"--install-dir",
|
|
12116
|
+
"-n",
|
|
12117
|
+
"--bindir",
|
|
12118
|
+
"--document",
|
|
12119
|
+
"--build-root",
|
|
12120
|
+
"-P",
|
|
12121
|
+
"--trust-policy",
|
|
12122
|
+
"-s",
|
|
12123
|
+
"--source",
|
|
12124
|
+
"-p",
|
|
12125
|
+
"--http-proxy",
|
|
12126
|
+
"--platform",
|
|
12127
|
+
"-g",
|
|
12128
|
+
"--file",
|
|
12129
|
+
"--without"
|
|
12130
|
+
]);
|
|
12131
|
+
BUNDLE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12132
|
+
"-v",
|
|
12133
|
+
"--version",
|
|
12134
|
+
"-g",
|
|
12135
|
+
"--group",
|
|
12136
|
+
"-s",
|
|
12137
|
+
"--source",
|
|
12138
|
+
"-r",
|
|
12139
|
+
"--require",
|
|
12140
|
+
"--path",
|
|
12141
|
+
"--git",
|
|
12142
|
+
"--github",
|
|
12143
|
+
"--branch",
|
|
12144
|
+
"--ref",
|
|
12145
|
+
"--glob"
|
|
12146
|
+
]);
|
|
12147
|
+
CRATE_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*$/u;
|
|
12148
|
+
CARGO_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12149
|
+
"-F",
|
|
12150
|
+
"--features",
|
|
12151
|
+
"--rename",
|
|
12152
|
+
"--registry",
|
|
12153
|
+
"--manifest-path",
|
|
12154
|
+
"-p",
|
|
12155
|
+
"--package",
|
|
12156
|
+
"--git",
|
|
12157
|
+
"--branch",
|
|
12158
|
+
"--tag",
|
|
12159
|
+
"--rev",
|
|
12160
|
+
"--path",
|
|
12161
|
+
"--base",
|
|
12162
|
+
"--target",
|
|
12163
|
+
"--color",
|
|
12164
|
+
"--config",
|
|
12165
|
+
"-Z",
|
|
12166
|
+
"--lockfile-path"
|
|
12167
|
+
]);
|
|
12168
|
+
CARGO_INSTALL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12169
|
+
"--version",
|
|
12170
|
+
"--vers",
|
|
12171
|
+
"--git",
|
|
12172
|
+
"--branch",
|
|
12173
|
+
"--tag",
|
|
12174
|
+
"--rev",
|
|
12175
|
+
"--path",
|
|
12176
|
+
"--root",
|
|
12177
|
+
"--index",
|
|
12178
|
+
"--registry",
|
|
12179
|
+
"-F",
|
|
12180
|
+
"--features",
|
|
12181
|
+
"--profile",
|
|
12182
|
+
"--target",
|
|
12183
|
+
"--target-dir",
|
|
12184
|
+
"-j",
|
|
12185
|
+
"--jobs",
|
|
12186
|
+
"--bin",
|
|
12187
|
+
"--example",
|
|
12188
|
+
"--color",
|
|
12189
|
+
"--config",
|
|
12190
|
+
"-Z"
|
|
12191
|
+
]);
|
|
12192
|
+
GO_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12193
|
+
"-C",
|
|
12194
|
+
"-tags",
|
|
12195
|
+
"-ldflags",
|
|
12196
|
+
"-gcflags",
|
|
12197
|
+
"-asmflags",
|
|
12198
|
+
"-mod",
|
|
12199
|
+
"-modfile",
|
|
12200
|
+
"-o",
|
|
12201
|
+
"-p",
|
|
12202
|
+
"-pkgdir",
|
|
12203
|
+
"-toolexec",
|
|
12204
|
+
"-exec",
|
|
12205
|
+
"-overlay",
|
|
12206
|
+
"-pgo",
|
|
12207
|
+
"-buildmode",
|
|
12208
|
+
"-compiler",
|
|
12209
|
+
"-gccgoflags",
|
|
12210
|
+
"-installsuffix",
|
|
12211
|
+
"-covermode",
|
|
12212
|
+
"-coverpkg"
|
|
12213
|
+
]);
|
|
12214
|
+
GO_PATH_RE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+(?:\/[\p{L}\p{N}_.~+-]+)*$/u;
|
|
12215
|
+
NUGET_NAME_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u;
|
|
12216
|
+
DOTNET_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12217
|
+
"-v",
|
|
12218
|
+
"--version",
|
|
12219
|
+
"-s",
|
|
12220
|
+
"--source",
|
|
12221
|
+
"-f",
|
|
12222
|
+
"--framework",
|
|
12223
|
+
"--package-directory"
|
|
12224
|
+
]);
|
|
12225
|
+
DOTNET_TOOL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12226
|
+
"--version",
|
|
12227
|
+
"--add-source",
|
|
12228
|
+
"--configfile",
|
|
12229
|
+
"--framework",
|
|
12230
|
+
"--tool-path",
|
|
12231
|
+
"-a",
|
|
12232
|
+
"--arch",
|
|
12233
|
+
"--tool-manifest",
|
|
12234
|
+
"-v",
|
|
12235
|
+
"--verbosity"
|
|
12236
|
+
]);
|
|
12237
|
+
NUGET_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12238
|
+
"-version",
|
|
12239
|
+
"-outputdirectory",
|
|
12240
|
+
"-o",
|
|
12241
|
+
"-source",
|
|
12242
|
+
"-configfile",
|
|
12243
|
+
"-fallbacksource",
|
|
12244
|
+
"-framework",
|
|
12245
|
+
"-packagesavemode",
|
|
12246
|
+
"-dependencyversion",
|
|
12247
|
+
"-solutiondirectory",
|
|
12248
|
+
"-verbosity"
|
|
12249
|
+
]);
|
|
12250
|
+
MAVEN_COORD_RE = /^[\p{L}\p{N}_.-]+:[\p{L}\p{N}_.-]+$/u;
|
|
12251
|
+
COMPOSER_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*\/[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12252
|
+
COMPOSER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12253
|
+
"-d",
|
|
12254
|
+
"--working-dir",
|
|
12255
|
+
"--stability",
|
|
12256
|
+
"--repository",
|
|
12257
|
+
"--add-repository"
|
|
12258
|
+
]);
|
|
12259
|
+
EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
|
|
12260
|
+
EXTENSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*\.[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
11436
12261
|
MANAGER_HEADS = /* @__PURE__ */ new Set([
|
|
12262
|
+
"gem",
|
|
12263
|
+
"bundle",
|
|
12264
|
+
"cargo",
|
|
12265
|
+
"go",
|
|
12266
|
+
"dotnet",
|
|
12267
|
+
"nuget",
|
|
12268
|
+
"mvn",
|
|
12269
|
+
"mvnw",
|
|
12270
|
+
"composer",
|
|
12271
|
+
...EDITOR_HEADS,
|
|
11437
12272
|
"npm",
|
|
11438
12273
|
"npx",
|
|
11439
12274
|
"pnpm",
|
|
@@ -11512,6 +12347,7 @@ zoo`.split("\n");
|
|
|
11512
12347
|
MAP_GOVERNED = /* @__PURE__ */ new Set([
|
|
11513
12348
|
"files.shell-startup",
|
|
11514
12349
|
"files.password-hashes",
|
|
12350
|
+
"files.system-dirs",
|
|
11515
12351
|
"commands.inline-exec",
|
|
11516
12352
|
"commands.eval-dynamic",
|
|
11517
12353
|
"commands.curl-pipe-shell",
|
|
@@ -11780,7 +12616,6 @@ zoo`.split("\n");
|
|
|
11780
12616
|
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/;
|
|
11781
12617
|
PII_CC15_RE = /\b3[47]\d{2}[-\s]?\d{6}[-\s]?\d{5}\b/;
|
|
11782
12618
|
PII_IBAN_RE = /\b[A-Z]{2}\d{2}(?:[A-Z0-9]|[ -][A-Z0-9]){11,30}\b/;
|
|
11783
|
-
NUMBER_LEAD_RE = /[\s:=;([{"'+-]/;
|
|
11784
12619
|
REALTIME_PII_PATTERNS = ["SSN", "Credit Card", "IBAN"];
|
|
11785
12620
|
MAX_PII_SCAN_BYTES = 1e5;
|
|
11786
12621
|
MASKED_KINDS = /* @__PURE__ */ new Set(["email", "iban", "account"]);
|
|
@@ -12118,6 +12953,9 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12118
12953
|
...transcriptPathField,
|
|
12119
12954
|
...taintFields,
|
|
12120
12955
|
...exposureFields,
|
|
12956
|
+
...meta?.decidedBy && { decidedBy: meta.decidedBy },
|
|
12957
|
+
...meta?.channel && { channel: meta.channel },
|
|
12958
|
+
...meta?.cardId && { cardId: meta.cardId },
|
|
12121
12959
|
...testRun,
|
|
12122
12960
|
agent: meta?.agent,
|
|
12123
12961
|
mcpServer: meta?.mcpServer,
|
|
@@ -14505,9 +15343,159 @@ function getPersistentDecision(toolName) {
|
|
|
14505
15343
|
|
|
14506
15344
|
// src/auth/daemon.ts
|
|
14507
15345
|
var import_fs10 = __toESM(require("fs"));
|
|
15346
|
+
var import_http = __toESM(require("http"));
|
|
14508
15347
|
var import_net = __toESM(require("net"));
|
|
14509
15348
|
var import_path10 = __toESM(require("path"));
|
|
14510
15349
|
var import_os8 = __toESM(require("os"));
|
|
15350
|
+
|
|
15351
|
+
// src/auth/decision-record.ts
|
|
15352
|
+
var CHANNELS = ["terminal", "monitor", "native", "cloud"];
|
|
15353
|
+
var PERSON_CHANNELS = /* @__PURE__ */ new Set([
|
|
15354
|
+
"terminal",
|
|
15355
|
+
"monitor",
|
|
15356
|
+
"native"
|
|
15357
|
+
]);
|
|
15358
|
+
var CARD_PROTOCOL = 2;
|
|
15359
|
+
var PERSON_LABELS = {
|
|
15360
|
+
terminal: "User Decision (Terminal (node9 tail))",
|
|
15361
|
+
monitor: "User Decision (node9 monitor)",
|
|
15362
|
+
native: "User Decision (Native)"
|
|
15363
|
+
};
|
|
15364
|
+
var REDIRECT_LABEL = "Steered Redirect (Terminal)";
|
|
15365
|
+
var TIMER_LABEL = "Approval Timeout";
|
|
15366
|
+
var CLOUD_LABEL = "Organization Policy (SaaS)";
|
|
15367
|
+
var ABANDONED_LABEL = "No Approver (node9 tail closed)";
|
|
15368
|
+
function personRecord(channel, decision, extra = {}, now = Date.now()) {
|
|
15369
|
+
return {
|
|
15370
|
+
decision,
|
|
15371
|
+
decidedBy: "person",
|
|
15372
|
+
channel,
|
|
15373
|
+
label: extra.redirect ? REDIRECT_LABEL : PERSON_LABELS[channel],
|
|
15374
|
+
...extra,
|
|
15375
|
+
decidedAt: now
|
|
15376
|
+
};
|
|
15377
|
+
}
|
|
15378
|
+
function timerRecord(timeoutMs, now = Date.now()) {
|
|
15379
|
+
return {
|
|
15380
|
+
decision: "deny",
|
|
15381
|
+
decidedBy: "timer",
|
|
15382
|
+
label: TIMER_LABEL,
|
|
15383
|
+
reason: `No response within ${Math.round(timeoutMs / 1e3)}s: auto-denied by the approval timeout.`,
|
|
15384
|
+
decidedAt: now
|
|
15385
|
+
};
|
|
15386
|
+
}
|
|
15387
|
+
function abandonedRecord(now = Date.now()) {
|
|
15388
|
+
return {
|
|
15389
|
+
decision: "deny",
|
|
15390
|
+
decidedBy: "abandoned",
|
|
15391
|
+
label: ABANDONED_LABEL,
|
|
15392
|
+
reason: "No approver was left to answer: node9 tail was closed.",
|
|
15393
|
+
decidedAt: now
|
|
15394
|
+
};
|
|
15395
|
+
}
|
|
15396
|
+
var DECIDED_BY = /* @__PURE__ */ new Set([
|
|
15397
|
+
"person",
|
|
15398
|
+
"timer",
|
|
15399
|
+
"cloud",
|
|
15400
|
+
"policy",
|
|
15401
|
+
"abandoned"
|
|
15402
|
+
]);
|
|
15403
|
+
var STANDING = /* @__PURE__ */ new Set([
|
|
15404
|
+
"always-allow",
|
|
15405
|
+
"always-deny",
|
|
15406
|
+
"trust",
|
|
15407
|
+
"trust-recipients"
|
|
15408
|
+
]);
|
|
15409
|
+
function boundedString(v, max) {
|
|
15410
|
+
return typeof v === "string" && v.length > 0 ? v.slice(0, max) : void 0;
|
|
15411
|
+
}
|
|
15412
|
+
function parseDecisionRecord(x) {
|
|
15413
|
+
if (!x || typeof x !== "object") return null;
|
|
15414
|
+
const r = x;
|
|
15415
|
+
if (r.decision !== "allow" && r.decision !== "deny") return null;
|
|
15416
|
+
if (typeof r.decidedBy !== "string" || !DECIDED_BY.has(r.decidedBy)) return null;
|
|
15417
|
+
const label2 = boundedString(r.label, 200);
|
|
15418
|
+
if (!label2) return null;
|
|
15419
|
+
const channel = typeof r.channel === "string" && CHANNELS.includes(r.channel) ? r.channel : void 0;
|
|
15420
|
+
if (r.decidedBy === "person" && (!channel || !PERSON_CHANNELS.has(channel))) return null;
|
|
15421
|
+
const out = {
|
|
15422
|
+
decision: r.decision,
|
|
15423
|
+
decidedBy: r.decidedBy,
|
|
15424
|
+
label: label2,
|
|
15425
|
+
decidedAt: typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt) ? r.decidedAt : 0
|
|
15426
|
+
};
|
|
15427
|
+
if (channel) out.channel = channel;
|
|
15428
|
+
const redirect = boundedString(r.redirect, 2e3);
|
|
15429
|
+
if (redirect) out.redirect = redirect;
|
|
15430
|
+
const reason = boundedString(r.reason, 2e3);
|
|
15431
|
+
if (reason) out.reason = reason;
|
|
15432
|
+
const ruleName = boundedString(r.ruleName, 256);
|
|
15433
|
+
if (ruleName) out.ruleName = ruleName;
|
|
15434
|
+
if (typeof r.standing === "string" && STANDING.has(r.standing))
|
|
15435
|
+
out.standing = r.standing;
|
|
15436
|
+
const trustDuration = boundedString(r.trustDuration, 8);
|
|
15437
|
+
if (trustDuration) out.trustDuration = trustDuration;
|
|
15438
|
+
return out;
|
|
15439
|
+
}
|
|
15440
|
+
function legacyWaitFields(record) {
|
|
15441
|
+
switch (record.decidedBy) {
|
|
15442
|
+
case "person": {
|
|
15443
|
+
const source = record.redirect ? "terminal-redirect" : record.channel === "native" ? "native" : "terminal";
|
|
15444
|
+
const reason = record.redirect ?? record.reason;
|
|
15445
|
+
return { decision: record.decision, source, ...reason && { reason } };
|
|
15446
|
+
}
|
|
15447
|
+
case "timer":
|
|
15448
|
+
return {
|
|
15449
|
+
decision: record.decision,
|
|
15450
|
+
source: "timeout",
|
|
15451
|
+
...record.reason && { reason: record.reason }
|
|
15452
|
+
};
|
|
15453
|
+
case "cloud":
|
|
15454
|
+
case "policy":
|
|
15455
|
+
return {
|
|
15456
|
+
decision: record.decision,
|
|
15457
|
+
source: record.decidedBy,
|
|
15458
|
+
...record.reason && { reason: record.reason }
|
|
15459
|
+
};
|
|
15460
|
+
case "abandoned":
|
|
15461
|
+
return { decision: "abandoned" };
|
|
15462
|
+
}
|
|
15463
|
+
}
|
|
15464
|
+
function recordFromLegacyWait(body, now = Date.now()) {
|
|
15465
|
+
if (body.decision === "abandoned") return abandonedRecord(now);
|
|
15466
|
+
const decision = body.decision;
|
|
15467
|
+
switch (body.source) {
|
|
15468
|
+
case "terminal":
|
|
15469
|
+
case "browser":
|
|
15470
|
+
return personRecord("terminal", decision, body.reason ? { reason: body.reason } : {}, now);
|
|
15471
|
+
case "native":
|
|
15472
|
+
return personRecord("native", decision, {}, now);
|
|
15473
|
+
case "terminal-redirect":
|
|
15474
|
+
return personRecord("terminal", decision, body.reason ? { redirect: body.reason } : {}, now);
|
|
15475
|
+
case "timeout":
|
|
15476
|
+
return { ...timerRecord(0, now), ...body.reason && { reason: body.reason } };
|
|
15477
|
+
case "cloud":
|
|
15478
|
+
return {
|
|
15479
|
+
decision,
|
|
15480
|
+
decidedBy: "cloud",
|
|
15481
|
+
label: CLOUD_LABEL,
|
|
15482
|
+
...body.reason && { reason: body.reason },
|
|
15483
|
+
decidedAt: now
|
|
15484
|
+
};
|
|
15485
|
+
case "policy":
|
|
15486
|
+
return {
|
|
15487
|
+
decision,
|
|
15488
|
+
decidedBy: "policy",
|
|
15489
|
+
label: "Policy",
|
|
15490
|
+
...body.reason && { reason: body.reason },
|
|
15491
|
+
decidedAt: now
|
|
15492
|
+
};
|
|
15493
|
+
default:
|
|
15494
|
+
return null;
|
|
15495
|
+
}
|
|
15496
|
+
}
|
|
15497
|
+
|
|
15498
|
+
// src/auth/daemon.ts
|
|
14511
15499
|
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path10.default.join(import_os8.default.tmpdir(), "node9-activity.sock");
|
|
14512
15500
|
function notifyActivitySocket(data) {
|
|
14513
15501
|
return new Promise((resolve) => {
|
|
@@ -14605,7 +15593,7 @@ async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
|
14605
15593
|
return false;
|
|
14606
15594
|
}
|
|
14607
15595
|
}
|
|
14608
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId) {
|
|
15596
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId, card) {
|
|
14609
15597
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14610
15598
|
const ctrl = new AbortController();
|
|
14611
15599
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -14631,19 +15619,43 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
14631
15619
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
14632
15620
|
...viewOnly && { viewOnly: true },
|
|
14633
15621
|
...localSmartRuleMatched && { localSmartRuleMatched: true },
|
|
14634
|
-
...sessionId && { sessionId }
|
|
15622
|
+
...sessionId && { sessionId },
|
|
15623
|
+
...cardFields(card)
|
|
14635
15624
|
}),
|
|
14636
15625
|
signal: ctrl.signal
|
|
14637
15626
|
});
|
|
14638
15627
|
if (!res.ok) throw new Error("Daemon fail");
|
|
14639
|
-
|
|
14640
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
15628
|
+
return registeredCard(await res.json());
|
|
14641
15629
|
} finally {
|
|
14642
15630
|
clearTimeout(timer);
|
|
14643
15631
|
}
|
|
14644
15632
|
}
|
|
15633
|
+
function cardFields(card) {
|
|
15634
|
+
return {
|
|
15635
|
+
// A review card: the hook already decided this call needs a person; the
|
|
15636
|
+
// daemon never checks it again (approval-card design, defect 1).
|
|
15637
|
+
kind: "review",
|
|
15638
|
+
// Protocol 2 makes the card's /wait require the internal token: claim it
|
|
15639
|
+
// only when this process can read the token (a daemon under another uid
|
|
15640
|
+
// is running but its pid file probe fails with EPERM), or every wait
|
|
15641
|
+
// would get 403 and the review would be denied unseen.
|
|
15642
|
+
...getInternalToken() && { protocol: CARD_PROTOCOL },
|
|
15643
|
+
...card?.channels && { channels: card.channels },
|
|
15644
|
+
...typeof card?.timeoutMs === "number" && { timeoutMs: card.timeoutMs }
|
|
15645
|
+
};
|
|
15646
|
+
}
|
|
15647
|
+
function registeredCard(body) {
|
|
15648
|
+
const b = body;
|
|
15649
|
+
return {
|
|
15650
|
+
id: b.id,
|
|
15651
|
+
allowCount: b.allowCount ?? 1,
|
|
15652
|
+
...b.protocol === CARD_PROTOCOL && {
|
|
15653
|
+
protocol: CARD_PROTOCOL,
|
|
15654
|
+
deadline: typeof b.deadline === "number" ? b.deadline : null
|
|
15655
|
+
}
|
|
15656
|
+
};
|
|
15657
|
+
}
|
|
14645
15658
|
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
14646
|
-
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14647
15659
|
const waitCtrl = new AbortController();
|
|
14648
15660
|
let capped = false;
|
|
14649
15661
|
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
@@ -14653,14 +15665,18 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
14653
15665
|
const onAbort = () => waitCtrl.abort();
|
|
14654
15666
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
14655
15667
|
try {
|
|
14656
|
-
const
|
|
15668
|
+
const { status, body } = await daemonGet(`/wait/${id}`, waitCtrl.signal).catch(
|
|
14657
15669
|
(err) => {
|
|
14658
15670
|
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
14659
15671
|
throw err;
|
|
14660
15672
|
}
|
|
14661
15673
|
);
|
|
14662
|
-
if (
|
|
14663
|
-
|
|
15674
|
+
if (status === 409) throw new Error("node9 card was withdrawn");
|
|
15675
|
+
if (status !== 200) return { decision: "deny" };
|
|
15676
|
+
const parsed = JSON.parse(body);
|
|
15677
|
+
const record = parseDecisionRecord(parsed.record);
|
|
15678
|
+
if (record) return { ...legacyWaitFields(record), record };
|
|
15679
|
+
const { decision, source, reason } = parsed;
|
|
14664
15680
|
if (decision === "allow") return { decision: "allow", source };
|
|
14665
15681
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
14666
15682
|
return { decision: "deny", source, reason };
|
|
@@ -14669,7 +15685,29 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
14669
15685
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
14670
15686
|
}
|
|
14671
15687
|
}
|
|
14672
|
-
|
|
15688
|
+
function daemonGet(urlPath, signal, token = getInternalToken()) {
|
|
15689
|
+
return new Promise((resolve, reject) => {
|
|
15690
|
+
const req = import_http.default.get(
|
|
15691
|
+
{
|
|
15692
|
+
host: DAEMON_HOST,
|
|
15693
|
+
port: DAEMON_PORT,
|
|
15694
|
+
path: urlPath,
|
|
15695
|
+
signal,
|
|
15696
|
+
agent: false,
|
|
15697
|
+
headers: token ? { "X-Node9-Internal": token } : {}
|
|
15698
|
+
},
|
|
15699
|
+
(res) => {
|
|
15700
|
+
let body = "";
|
|
15701
|
+
res.setEncoding("utf8");
|
|
15702
|
+
res.on("data", (chunk) => body += chunk);
|
|
15703
|
+
res.on("end", () => resolve({ status: res.statusCode ?? 0, body }));
|
|
15704
|
+
res.on("error", reject);
|
|
15705
|
+
}
|
|
15706
|
+
);
|
|
15707
|
+
req.on("error", reject);
|
|
15708
|
+
});
|
|
15709
|
+
}
|
|
15710
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent, extra) {
|
|
14673
15711
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14674
15712
|
const res = await fetch(`${base2}/check`, {
|
|
14675
15713
|
method: "POST",
|
|
@@ -14685,13 +15723,16 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId
|
|
|
14685
15723
|
// event via socket. Same contract as registerDaemonEntry — without
|
|
14686
15724
|
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
14687
15725
|
fromCLI: socketActivitySent !== false,
|
|
14688
|
-
activityId
|
|
15726
|
+
activityId,
|
|
15727
|
+
...extra?.cwd && { cwd: extra.cwd },
|
|
15728
|
+
...extra?.recoveryCommand && { recoveryCommand: extra.recoveryCommand },
|
|
15729
|
+
...extra?.sessionId && { sessionId: extra.sessionId },
|
|
15730
|
+
...cardFields(extra?.card)
|
|
14689
15731
|
}),
|
|
14690
15732
|
signal: AbortSignal.timeout(3e3)
|
|
14691
15733
|
});
|
|
14692
15734
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
14693
|
-
|
|
14694
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
15735
|
+
return registeredCard(await res.json());
|
|
14695
15736
|
}
|
|
14696
15737
|
async function notifyTaint(filePath, source, fromEid) {
|
|
14697
15738
|
if (!isDaemonRunning()) return;
|
|
@@ -14784,14 +15825,18 @@ async function approveSessionExposure(sessionId, keys) {
|
|
|
14784
15825
|
} catch {
|
|
14785
15826
|
}
|
|
14786
15827
|
}
|
|
14787
|
-
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
15828
|
+
async function resolveViaDaemon(id, decision, internalToken, source, record) {
|
|
14788
15829
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
14789
|
-
await fetch(`${base2}/resolve/${id}`, {
|
|
15830
|
+
const res = await fetch(`${base2}/resolve/${id}`, {
|
|
14790
15831
|
method: "POST",
|
|
14791
15832
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
14792
|
-
body: JSON.stringify({ decision, ...source && { source } }),
|
|
15833
|
+
body: JSON.stringify({ decision, ...source && { source }, ...record && { record } }),
|
|
14793
15834
|
signal: AbortSignal.timeout(3e3)
|
|
14794
15835
|
});
|
|
15836
|
+
if (!res.ok) return null;
|
|
15837
|
+
const body = await res.json().catch(() => null);
|
|
15838
|
+
const winner = parseDecisionRecord(body?.record);
|
|
15839
|
+
return winner && typeof body?.won === "boolean" ? { won: body.won, record: winner } : null;
|
|
14795
15840
|
}
|
|
14796
15841
|
|
|
14797
15842
|
// src/auth/orchestrator.ts
|
|
@@ -15908,7 +16953,17 @@ function ecoDir(eco) {
|
|
|
15908
16953
|
return import_path23.default.join(osvRoot(), eco);
|
|
15909
16954
|
}
|
|
15910
16955
|
function indexKey(eco, name) {
|
|
15911
|
-
|
|
16956
|
+
switch (eco) {
|
|
16957
|
+
case "PyPI":
|
|
16958
|
+
return normalizePyPiName(name);
|
|
16959
|
+
case "crates.io":
|
|
16960
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
16961
|
+
case "Go":
|
|
16962
|
+
case "Maven":
|
|
16963
|
+
return name;
|
|
16964
|
+
default:
|
|
16965
|
+
return name.toLowerCase();
|
|
16966
|
+
}
|
|
15912
16967
|
}
|
|
15913
16968
|
function shardOf(key) {
|
|
15914
16969
|
return (0, import_crypto9.createHash)("sha1").update(key).digest("hex").slice(0, 2);
|
|
@@ -16119,8 +17174,11 @@ async function pypiInfo(name, version) {
|
|
|
16119
17174
|
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0
|
|
16120
17175
|
};
|
|
16121
17176
|
}
|
|
17177
|
+
var HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
16122
17178
|
async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
16123
|
-
|
|
17179
|
+
if (eco === "npm") return npmInfo(name, version, freshWindowMs, raw);
|
|
17180
|
+
if (eco === "PyPI") return pypiInfo(name, version);
|
|
17181
|
+
return null;
|
|
16124
17182
|
}
|
|
16125
17183
|
|
|
16126
17184
|
// src/supply-chain/local-resolve.ts
|
|
@@ -16159,26 +17217,51 @@ function commandOf(toolName, args) {
|
|
|
16159
17217
|
const a = args;
|
|
16160
17218
|
return typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : null;
|
|
16161
17219
|
}
|
|
17220
|
+
var KIND = {
|
|
17221
|
+
npm: "npm package",
|
|
17222
|
+
PyPI: "PyPI package",
|
|
17223
|
+
RubyGems: "RubyGems package",
|
|
17224
|
+
"crates.io": "crates.io package",
|
|
17225
|
+
Go: "Go module",
|
|
17226
|
+
NuGet: "NuGet package",
|
|
17227
|
+
Maven: "Maven artifact",
|
|
17228
|
+
Packagist: "Packagist package",
|
|
17229
|
+
VSCode: "VS Code extension"
|
|
17230
|
+
};
|
|
16162
17231
|
function label(p, version) {
|
|
16163
|
-
return `${p.ecosystem
|
|
17232
|
+
return `${KIND[p.ecosystem]} "${p.name}${version ? `@${version}` : ""}"`;
|
|
17233
|
+
}
|
|
17234
|
+
function osvNames(p) {
|
|
17235
|
+
if (p.ecosystem !== "Go") return [p.name];
|
|
17236
|
+
const parts = p.name.split("/");
|
|
17237
|
+
const out = [];
|
|
17238
|
+
for (let n = parts.length; n >= 2 && out.length < 6; n--) out.push(parts.slice(0, n).join("/"));
|
|
17239
|
+
return out;
|
|
16164
17240
|
}
|
|
16165
17241
|
async function maliciousFor(p, version, cfg, misses, networkCapped) {
|
|
16166
|
-
const
|
|
16167
|
-
const
|
|
16168
|
-
|
|
17242
|
+
const names = osvNames(p);
|
|
17243
|
+
const local = names.map((n) => lookupIndex(p.ecosystem, n));
|
|
17244
|
+
const hits2 = local.flatMap((l) => l.status === "hit" ? l.entries : []);
|
|
17245
|
+
const entries = hits2.length > 0 ? hits2 : null;
|
|
17246
|
+
const freshClean = local.every((l) => l.status === "clean" && l.fresh);
|
|
17247
|
+
const status = local.some((l) => l.status === "unavailable") ? "unavailable" : local[0].status;
|
|
17248
|
+
if (!entries && !freshClean) {
|
|
16169
17249
|
if (!cfg.onlineFallback || networkCapped) {
|
|
16170
17250
|
misses.push(
|
|
16171
|
-
`${label(p, version)}: local index ${
|
|
17251
|
+
`${label(p, version)}: local index ${status}, no online lookup (${networkCapped ? "over the per-command network cap" : "online fallback off"})`
|
|
16172
17252
|
);
|
|
16173
17253
|
return null;
|
|
16174
17254
|
}
|
|
16175
|
-
let
|
|
17255
|
+
let perName = null;
|
|
16176
17256
|
try {
|
|
16177
|
-
|
|
17257
|
+
perName = await queryOsvMalicious(
|
|
17258
|
+
names.map((name) => ({ ecosystem: p.ecosystem, name, version }))
|
|
17259
|
+
);
|
|
16178
17260
|
} catch (err) {
|
|
16179
17261
|
misses.push(`${label(p, version)}: OSV online ${err.message}`);
|
|
16180
17262
|
return null;
|
|
16181
17263
|
}
|
|
17264
|
+
const ids = perName ? [[...new Set(perName.flat())]] : null;
|
|
16182
17265
|
if (!ids) {
|
|
16183
17266
|
misses.push(`${label(p, version)}: OSV online unavailable`);
|
|
16184
17267
|
return null;
|
|
@@ -16248,7 +17331,7 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
16248
17331
|
}
|
|
16249
17332
|
const findings = [];
|
|
16250
17333
|
let info = null;
|
|
16251
|
-
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
17334
|
+
const wantsRegistry = (cfg.newPackage !== "off" || cfg.installScript !== "off") && HAS_REGISTRY_CLIENT.has(p.ecosystem);
|
|
16252
17335
|
if (wantsRegistry && !networkCapped) {
|
|
16253
17336
|
try {
|
|
16254
17337
|
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
@@ -16347,17 +17430,98 @@ function asApprovalRequest(description) {
|
|
|
16347
17430
|
return description;
|
|
16348
17431
|
}
|
|
16349
17432
|
|
|
16350
|
-
// src/auth/
|
|
16351
|
-
var
|
|
16352
|
-
|
|
16353
|
-
"
|
|
16354
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
...
|
|
16359
|
-
|
|
16360
|
-
|
|
17433
|
+
// src/auth/card-result.ts
|
|
17434
|
+
var CHANNEL_NAMES = {
|
|
17435
|
+
terminal: "Terminal (node9 tail)",
|
|
17436
|
+
monitor: "node9 monitor",
|
|
17437
|
+
native: "system dialog window",
|
|
17438
|
+
cloud: "organization"
|
|
17439
|
+
};
|
|
17440
|
+
function authResultFromRecord(r, ctx = {}) {
|
|
17441
|
+
const who = { decidedBy: r.decidedBy, ...r.channel && { channel: r.channel } };
|
|
17442
|
+
switch (r.decidedBy) {
|
|
17443
|
+
case "person": {
|
|
17444
|
+
const channel = r.channel ?? "terminal";
|
|
17445
|
+
const decisionSource = channel === "native" ? "native" : "terminal";
|
|
17446
|
+
if (r.decision === "allow") {
|
|
17447
|
+
return { approved: true, checkedBy: "daemon", decisionSource, ...who };
|
|
17448
|
+
}
|
|
17449
|
+
return {
|
|
17450
|
+
approved: false,
|
|
17451
|
+
reason: r.redirect || `The human user rejected this action via the Node9 ${CHANNEL_NAMES[channel]}.`,
|
|
17452
|
+
blockedBy: "local-decision",
|
|
17453
|
+
blockedByLabel: r.label,
|
|
17454
|
+
decisionSource,
|
|
17455
|
+
...who,
|
|
17456
|
+
...r.redirect && { redirect: r.redirect }
|
|
17457
|
+
};
|
|
17458
|
+
}
|
|
17459
|
+
case "timer":
|
|
17460
|
+
if (r.decision === "deny" && ctx.downgradedBlock)
|
|
17461
|
+
return { ...ctx.downgradedBlock, blockedBy: "timeout", ...who };
|
|
17462
|
+
return r.decision === "allow" ? { approved: true, ...who } : {
|
|
17463
|
+
approved: false,
|
|
17464
|
+
reason: ctx.timeoutReason || r.reason || "No response: auto-denied after the approval timeout.",
|
|
17465
|
+
blockedBy: "timeout",
|
|
17466
|
+
blockedByLabel: TIMER_LABEL,
|
|
17467
|
+
...who
|
|
17468
|
+
};
|
|
17469
|
+
case "cloud":
|
|
17470
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "cloud", decisionSource: "cloud", ...who } : {
|
|
17471
|
+
approved: false,
|
|
17472
|
+
reason: r.reason || "Action rejected by organization administrator via Slack.",
|
|
17473
|
+
blockedBy: "team-policy",
|
|
17474
|
+
blockedByLabel: CLOUD_LABEL,
|
|
17475
|
+
decisionSource: "cloud",
|
|
17476
|
+
...who
|
|
17477
|
+
};
|
|
17478
|
+
case "policy":
|
|
17479
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "local-policy", ...who } : {
|
|
17480
|
+
approved: false,
|
|
17481
|
+
reason: r.reason || "Action blocked by policy.",
|
|
17482
|
+
blockedBy: "local-config",
|
|
17483
|
+
blockedByLabel: r.label,
|
|
17484
|
+
...r.ruleName && { ruleHit: r.ruleName },
|
|
17485
|
+
...who
|
|
17486
|
+
};
|
|
17487
|
+
case "abandoned":
|
|
17488
|
+
return {
|
|
17489
|
+
approved: false,
|
|
17490
|
+
reason: r.reason || "No approver was left to answer.",
|
|
17491
|
+
blockedBy: "no-approval-mechanism",
|
|
17492
|
+
blockedByLabel: ABANDONED_LABEL,
|
|
17493
|
+
...who
|
|
17494
|
+
};
|
|
17495
|
+
}
|
|
17496
|
+
}
|
|
17497
|
+
function approvalUnavailable(reason) {
|
|
17498
|
+
return {
|
|
17499
|
+
approved: false,
|
|
17500
|
+
reason: reason || "node9 could not get an approval decision for this call.",
|
|
17501
|
+
blockedBy: "no-approval-mechanism",
|
|
17502
|
+
blockedByLabel: "Approval Unavailable"
|
|
17503
|
+
};
|
|
17504
|
+
}
|
|
17505
|
+
function decidedByOf(res) {
|
|
17506
|
+
if (res.decidedBy) return res.decidedBy;
|
|
17507
|
+
if (res.blockedBy === "timeout") return "timer";
|
|
17508
|
+
if (res.checkedBy === "cloud" || res.blockedBy === "team-policy") return "cloud";
|
|
17509
|
+
return "policy";
|
|
17510
|
+
}
|
|
17511
|
+
function recordFromAuthResult(res, now = Date.now()) {
|
|
17512
|
+
const decidedBy = decidedByOf(res);
|
|
17513
|
+
const channel = res.channel ?? (decidedBy === "cloud" ? "cloud" : decidedBy === "person" ? "native" : void 0);
|
|
17514
|
+
return {
|
|
17515
|
+
decision: res.approved ? "allow" : "deny",
|
|
17516
|
+
decidedBy,
|
|
17517
|
+
...channel && { channel },
|
|
17518
|
+
label: res.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : decidedBy === "timer" ? TIMER_LABEL : res.approved ? "Allowed" : "Blocked"),
|
|
17519
|
+
...res.reason && { reason: res.reason.slice(0, 2e3) },
|
|
17520
|
+
...res.ruleHit && { ruleName: res.ruleHit },
|
|
17521
|
+
...res.redirect && { redirect: res.redirect },
|
|
17522
|
+
decidedAt: now
|
|
17523
|
+
};
|
|
17524
|
+
}
|
|
16361
17525
|
|
|
16362
17526
|
// src/auth/orchestrator.ts
|
|
16363
17527
|
var SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
@@ -17397,28 +18561,22 @@ ${packageReview}`;
|
|
|
17397
18561
|
const { signal } = abortController;
|
|
17398
18562
|
const racePromises = [];
|
|
17399
18563
|
const approvalTimeoutMs = config.settings.approvalTimeoutMs ?? 0;
|
|
17400
|
-
if (approvalTimeoutMs > 0) {
|
|
17401
|
-
racePromises.push(
|
|
17402
|
-
new Promise((resolve, reject) => {
|
|
17403
|
-
const timer = setTimeout(() => {
|
|
17404
|
-
resolve({
|
|
17405
|
-
approved: false,
|
|
17406
|
-
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
17407
|
-
blockedBy: "timeout",
|
|
17408
|
-
blockedByLabel: "Approval Timeout"
|
|
17409
|
-
});
|
|
17410
|
-
}, approvalTimeoutMs);
|
|
17411
|
-
signal.addEventListener("abort", () => {
|
|
17412
|
-
clearTimeout(timer);
|
|
17413
|
-
reject(new Error("Aborted"));
|
|
17414
|
-
});
|
|
17415
|
-
})
|
|
17416
|
-
);
|
|
17417
|
-
}
|
|
17418
18564
|
let viewerId = null;
|
|
17419
18565
|
const internalToken = getInternalToken();
|
|
17420
18566
|
let daemonEntryId = null;
|
|
17421
18567
|
let daemonAllowCount = 1;
|
|
18568
|
+
let cardDeadline;
|
|
18569
|
+
const cardOptions = {
|
|
18570
|
+
channels: [
|
|
18571
|
+
"terminal",
|
|
18572
|
+
"monitor",
|
|
18573
|
+
// Only when the popup can show: a card listing a channel nobody can
|
|
18574
|
+
// use is never abandoned when node9 tail closes, and waits unanswered.
|
|
18575
|
+
...approvers.native && !isManual && !options?.calledFromDaemon && (process.platform === "darwin" || process.platform === "win32" || !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY)) ? ["native"] : [],
|
|
18576
|
+
...cloudEnforced && cloudRequestId && cloudMayVote ? ["cloud"] : []
|
|
18577
|
+
],
|
|
18578
|
+
timeoutMs: approvalTimeoutMs
|
|
18579
|
+
};
|
|
17422
18580
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
17423
18581
|
if (cloudEnforced && cloudRequestId) {
|
|
17424
18582
|
const viewer = await notifyDaemonViewer(
|
|
@@ -17427,11 +18585,20 @@ ${packageReview}`;
|
|
|
17427
18585
|
meta,
|
|
17428
18586
|
riskMetadata,
|
|
17429
18587
|
options?.activityId,
|
|
17430
|
-
options?.socketActivitySent
|
|
18588
|
+
options?.socketActivitySent,
|
|
18589
|
+
{
|
|
18590
|
+
cwd: options?.cwd,
|
|
18591
|
+
recoveryCommand: statefulRecoveryCommand,
|
|
18592
|
+
sessionId: meta?.sessionId,
|
|
18593
|
+
card: cardOptions
|
|
18594
|
+
}
|
|
17431
18595
|
).catch(() => null);
|
|
17432
18596
|
viewerId = viewer?.id ?? null;
|
|
17433
18597
|
daemonEntryId = viewerId;
|
|
17434
|
-
if (viewer)
|
|
18598
|
+
if (viewer) {
|
|
18599
|
+
daemonAllowCount = viewer.allowCount;
|
|
18600
|
+
cardDeadline = viewer.deadline;
|
|
18601
|
+
}
|
|
17435
18602
|
} else {
|
|
17436
18603
|
try {
|
|
17437
18604
|
const entry = await registerDaemonEntry(
|
|
@@ -17455,14 +18622,35 @@ ${packageReview}`;
|
|
|
17455
18622
|
void 0,
|
|
17456
18623
|
localSmartRuleMatched || options?.localSmartRuleMatched,
|
|
17457
18624
|
options?.socketActivitySent,
|
|
17458
|
-
meta?.sessionId
|
|
18625
|
+
meta?.sessionId,
|
|
18626
|
+
cardOptions
|
|
17459
18627
|
);
|
|
17460
18628
|
daemonEntryId = entry.id;
|
|
17461
18629
|
daemonAllowCount = entry.allowCount;
|
|
18630
|
+
cardDeadline = entry.deadline;
|
|
17462
18631
|
} catch {
|
|
17463
18632
|
}
|
|
17464
18633
|
}
|
|
17465
18634
|
}
|
|
18635
|
+
if (approvalTimeoutMs > 0 && cardDeadline === void 0) {
|
|
18636
|
+
racePromises.push(
|
|
18637
|
+
new Promise((resolve, reject) => {
|
|
18638
|
+
const timer = setTimeout(() => {
|
|
18639
|
+
resolve({
|
|
18640
|
+
approved: false,
|
|
18641
|
+
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
18642
|
+
blockedBy: "timeout",
|
|
18643
|
+
blockedByLabel: "Approval Timeout",
|
|
18644
|
+
decidedBy: "timer"
|
|
18645
|
+
});
|
|
18646
|
+
}, approvalTimeoutMs);
|
|
18647
|
+
signal.addEventListener("abort", () => {
|
|
18648
|
+
clearTimeout(timer);
|
|
18649
|
+
reject(new Error("Aborted"));
|
|
18650
|
+
});
|
|
18651
|
+
})
|
|
18652
|
+
);
|
|
18653
|
+
}
|
|
17466
18654
|
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
17467
18655
|
racePromises.push(
|
|
17468
18656
|
(async () => {
|
|
@@ -17473,7 +18661,9 @@ ${packageReview}`;
|
|
|
17473
18661
|
reason: cloudResult.approved ? void 0 : cloudResult.reason || "Action rejected by organization administrator via Slack.",
|
|
17474
18662
|
checkedBy: cloudResult.approved ? "cloud" : void 0,
|
|
17475
18663
|
blockedBy: cloudResult.approved ? void 0 : "team-policy",
|
|
17476
|
-
blockedByLabel: "Organization Policy (SaaS)"
|
|
18664
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
18665
|
+
decidedBy: "cloud",
|
|
18666
|
+
channel: "cloud"
|
|
17477
18667
|
};
|
|
17478
18668
|
} catch (err) {
|
|
17479
18669
|
const error = err;
|
|
@@ -17503,14 +18693,31 @@ ${packageReview}`;
|
|
|
17503
18693
|
if (config.policySource !== "workspace") {
|
|
17504
18694
|
for (const d of exposureDestinations) addTrustedRecipient(d, "approval");
|
|
17505
18695
|
}
|
|
17506
|
-
return {
|
|
18696
|
+
return {
|
|
18697
|
+
approved: true,
|
|
18698
|
+
checkedBy: "daemon",
|
|
18699
|
+
decisionSource: "native",
|
|
18700
|
+
decidedBy: "person",
|
|
18701
|
+
channel: "native"
|
|
18702
|
+
};
|
|
17507
18703
|
}
|
|
17508
18704
|
if (decision === "always_allow") {
|
|
17509
18705
|
if (standingTrustApplies()) {
|
|
17510
18706
|
writeTrustSession(toolName, 36e5, args);
|
|
17511
|
-
return {
|
|
18707
|
+
return {
|
|
18708
|
+
approved: true,
|
|
18709
|
+
checkedBy: "trust",
|
|
18710
|
+
decidedBy: "person",
|
|
18711
|
+
channel: "native"
|
|
18712
|
+
};
|
|
17512
18713
|
}
|
|
17513
|
-
return {
|
|
18714
|
+
return {
|
|
18715
|
+
approved: true,
|
|
18716
|
+
checkedBy: "daemon",
|
|
18717
|
+
decisionSource: "native",
|
|
18718
|
+
decidedBy: "person",
|
|
18719
|
+
channel: "native"
|
|
18720
|
+
};
|
|
17514
18721
|
}
|
|
17515
18722
|
const isApproved = decision === "allow";
|
|
17516
18723
|
return {
|
|
@@ -17519,75 +18726,32 @@ ${packageReview}`;
|
|
|
17519
18726
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
17520
18727
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
17521
18728
|
blockedByLabel: "User Decision (Native)",
|
|
17522
|
-
decisionSource: "native"
|
|
18729
|
+
decisionSource: "native",
|
|
18730
|
+
decidedBy: "person",
|
|
18731
|
+
channel: "native"
|
|
17523
18732
|
};
|
|
17524
18733
|
})()
|
|
17525
18734
|
);
|
|
17526
18735
|
}
|
|
18736
|
+
const fromCard = /* @__PURE__ */ new WeakSet();
|
|
18737
|
+
const timeoutReason = approvalTimeoutMs > 0 ? approvalTimeoutReason(approvalTimeoutMs) : void 0;
|
|
17527
18738
|
if (daemonEntryId && approvers.terminal) {
|
|
17528
18739
|
racePromises.push(
|
|
17529
18740
|
(async () => {
|
|
17530
|
-
const
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17536
|
-
|
|
17537
|
-
|
|
17538
|
-
);
|
|
17539
|
-
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
17540
|
-
const isApproved = daemonDecision === "allow";
|
|
17541
|
-
const isRedirect = decisionSource === REDIRECT_SOURCE;
|
|
17542
|
-
if (!isApproved && !isRedirect && !(decisionSource && HUMAN_DECISION_SOURCES.has(decisionSource))) {
|
|
17543
|
-
if (decisionSource === "cloud")
|
|
17544
|
-
return {
|
|
17545
|
-
approved: false,
|
|
17546
|
-
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
17547
|
-
blockedBy: "team-policy",
|
|
17548
|
-
blockedByLabel: "Organization Policy (SaaS)",
|
|
17549
|
-
decisionSource: "cloud"
|
|
17550
|
-
};
|
|
17551
|
-
const timedOut = decisionSource === "timeout";
|
|
17552
|
-
if (downgradedBlock)
|
|
17553
|
-
return {
|
|
17554
|
-
...downgradedBlock,
|
|
17555
|
-
blockedBy: timedOut ? "timeout" : "local-config"
|
|
17556
|
-
};
|
|
17557
|
-
if (timedOut)
|
|
17558
|
-
return {
|
|
17559
|
-
approved: false,
|
|
17560
|
-
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
17561
|
-
blockedBy: "timeout",
|
|
17562
|
-
blockedByLabel: "Approval Timeout"
|
|
17563
|
-
};
|
|
17564
|
-
if (decisionSource === "policy")
|
|
17565
|
-
return {
|
|
17566
|
-
approved: false,
|
|
17567
|
-
reason: daemonReason || "Action blocked by policy.",
|
|
17568
|
-
blockedBy: "local-config",
|
|
17569
|
-
blockedByLabel: explainableLabel
|
|
17570
|
-
};
|
|
17571
|
-
return {
|
|
17572
|
-
approved: false,
|
|
17573
|
-
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
17574
|
-
blockedBy: "no-approval-mechanism",
|
|
17575
|
-
blockedByLabel: "Approval Unavailable"
|
|
17576
|
-
};
|
|
18741
|
+
const cap = cardDeadline === void 0 ? daemonWaitCapMs(config.settings.approvalTimeoutMs) : cardDeadline === null ? 0 : Math.max(cardDeadline - Date.now(), 0) + 5e3;
|
|
18742
|
+
const outcome = await waitForDaemonDecision(daemonEntryId, signal, cap);
|
|
18743
|
+
const legacy = !outcome.record;
|
|
18744
|
+
const record = outcome.record ?? recordFromLegacyWait(outcome);
|
|
18745
|
+
if (downgradedBlock && (!record || legacy && record.decidedBy === "policy" && record.decision === "deny")) {
|
|
18746
|
+
const softened = { ...downgradedBlock, blockedBy: "local-config" };
|
|
18747
|
+
fromCard.add(softened);
|
|
18748
|
+
return softened;
|
|
17577
18749
|
}
|
|
17578
|
-
if (
|
|
17579
|
-
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
|
|
17583
|
-
return {
|
|
17584
|
-
approved: isApproved,
|
|
17585
|
-
reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
|
|
17586
|
-
checkedBy: isApproved ? "daemon" : void 0,
|
|
17587
|
-
blockedBy: isApproved ? void 0 : "local-decision",
|
|
17588
|
-
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
17589
|
-
decisionSource: "terminal"
|
|
17590
|
-
};
|
|
18750
|
+
if (!record) return approvalUnavailable(outcome.reason);
|
|
18751
|
+
if (legacy && record.decidedBy === "policy") record.label = explainableLabel;
|
|
18752
|
+
const result = authResultFromRecord(record, { downgradedBlock, timeoutReason });
|
|
18753
|
+
fromCard.add(result);
|
|
18754
|
+
return result;
|
|
17591
18755
|
})()
|
|
17592
18756
|
);
|
|
17593
18757
|
}
|
|
@@ -17619,7 +18783,7 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17619
18783
|
blockedByLabel: explainableLabel
|
|
17620
18784
|
};
|
|
17621
18785
|
}
|
|
17622
|
-
const
|
|
18786
|
+
const raceResult = await new Promise((resolve) => {
|
|
17623
18787
|
let resolved = false;
|
|
17624
18788
|
let failures = 0;
|
|
17625
18789
|
const total = racePromises.length;
|
|
@@ -17627,14 +18791,6 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17627
18791
|
if (!resolved) {
|
|
17628
18792
|
resolved = true;
|
|
17629
18793
|
abortController.abort();
|
|
17630
|
-
if (daemonEntryId && internalToken) {
|
|
17631
|
-
resolveViaDaemon(
|
|
17632
|
-
daemonEntryId,
|
|
17633
|
-
res.approved ? "allow" : "deny",
|
|
17634
|
-
internalToken,
|
|
17635
|
-
res.decisionSource
|
|
17636
|
-
).catch(() => null);
|
|
17637
|
-
}
|
|
17638
18794
|
resolve(res);
|
|
17639
18795
|
}
|
|
17640
18796
|
};
|
|
@@ -17642,26 +18798,29 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17642
18798
|
p.then(finish).catch((err) => {
|
|
17643
18799
|
if (err.name === "AbortError" || err.message?.includes("canceled") || err.message?.includes("Aborted"))
|
|
17644
18800
|
return;
|
|
17645
|
-
if (err.message === "Abandoned") {
|
|
17646
|
-
finish({
|
|
17647
|
-
approved: false,
|
|
17648
|
-
reason: "Browser dashboard closed without making a decision.",
|
|
17649
|
-
blockedBy: "local-decision",
|
|
17650
|
-
blockedByLabel: "Browser Dashboard (Abandoned)"
|
|
17651
|
-
});
|
|
17652
|
-
return;
|
|
17653
|
-
}
|
|
17654
18801
|
failures++;
|
|
17655
18802
|
if (failures === total && !resolved) {
|
|
17656
|
-
finish(
|
|
18803
|
+
finish(approvalUnavailable("All approval channels failed or disconnected."));
|
|
17657
18804
|
}
|
|
17658
18805
|
});
|
|
17659
18806
|
}
|
|
17660
18807
|
});
|
|
18808
|
+
let finalResult = raceResult;
|
|
18809
|
+
if (daemonEntryId && internalToken && !fromCard.has(raceResult)) {
|
|
18810
|
+
const reply = await resolveViaDaemon(
|
|
18811
|
+
daemonEntryId,
|
|
18812
|
+
raceResult.approved ? "allow" : "deny",
|
|
18813
|
+
internalToken,
|
|
18814
|
+
raceResult.decisionSource,
|
|
18815
|
+
recordFromAuthResult(raceResult)
|
|
18816
|
+
).catch(() => null);
|
|
18817
|
+
if (reply && !reply.won)
|
|
18818
|
+
finalResult = authResultFromRecord(reply.record, { downgradedBlock, timeoutReason });
|
|
18819
|
+
}
|
|
17661
18820
|
if (finalResult.approved && exposureKeys.length > 0 && meta?.sessionId && ucPolicy?.afterExposure === "ask-once") {
|
|
17662
18821
|
await approveSessionExposure(meta.sessionId, exposureKeys);
|
|
17663
18822
|
}
|
|
17664
|
-
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
18823
|
+
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud" && finalResult.decidedBy !== "cloud") {
|
|
17665
18824
|
await resolveNode9SaaS(
|
|
17666
18825
|
cloudRequestId,
|
|
17667
18826
|
creds,
|
|
@@ -17685,7 +18844,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
17685
18844
|
{
|
|
17686
18845
|
...ownRowMeta(finalResult.approved),
|
|
17687
18846
|
...cloudRequestId && { cloudRequestId },
|
|
17688
|
-
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` }
|
|
18847
|
+
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` },
|
|
18848
|
+
// Who decided, from the decision record, and the card: the daemon
|
|
18849
|
+
// writes its own row for the same card, and the report counts one.
|
|
18850
|
+
...finalResult.decidedBy && { decidedBy: finalResult.decidedBy },
|
|
18851
|
+
...finalResult.channel && { channel: finalResult.channel },
|
|
18852
|
+
...daemonEntryId && { cardId: daemonEntryId }
|
|
17689
18853
|
},
|
|
17690
18854
|
hashAuditArgs
|
|
17691
18855
|
);
|