@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/cli.mjs
CHANGED
|
@@ -1210,6 +1210,120 @@ function expandPlainParam(p) {
|
|
|
1210
1210
|
if (recorded !== void 0) return recorded;
|
|
1211
1211
|
return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
|
|
1212
1212
|
}
|
|
1213
|
+
function functionBodies(f) {
|
|
1214
|
+
const out = /* @__PURE__ */ new Map();
|
|
1215
|
+
syntax.Walk(f, (node) => {
|
|
1216
|
+
const n = node;
|
|
1217
|
+
if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
|
|
1218
|
+
const list = out.get(n.Name.Value) ?? [];
|
|
1219
|
+
list.push(n.Body);
|
|
1220
|
+
out.set(n.Name.Value, list);
|
|
1221
|
+
}
|
|
1222
|
+
return true;
|
|
1223
|
+
});
|
|
1224
|
+
return out;
|
|
1225
|
+
}
|
|
1226
|
+
function rebindsPositionals(body) {
|
|
1227
|
+
let found = false;
|
|
1228
|
+
syntax.Walk(body, (node) => {
|
|
1229
|
+
const n = node;
|
|
1230
|
+
if (found || !n) return false;
|
|
1231
|
+
if (syntax.NodeType(n) === "CallExpr") {
|
|
1232
|
+
const words = (n.Args || []).map((a) => resolveWordLiteral(a));
|
|
1233
|
+
if (words[0] === "shift") found = true;
|
|
1234
|
+
else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
|
|
1235
|
+
found = true;
|
|
1236
|
+
}
|
|
1237
|
+
return !found;
|
|
1238
|
+
});
|
|
1239
|
+
return found;
|
|
1240
|
+
}
|
|
1241
|
+
function tableBindings() {
|
|
1242
|
+
if (!assignmentTable) return "";
|
|
1243
|
+
const out = [];
|
|
1244
|
+
for (const [name, rec] of assignmentTable)
|
|
1245
|
+
if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
|
|
1246
|
+
return out.sort().join("");
|
|
1247
|
+
}
|
|
1248
|
+
function walkWithCalls(f, visit2) {
|
|
1249
|
+
const bodies = functionBodies(f);
|
|
1250
|
+
if (bodies.size === 0) {
|
|
1251
|
+
syntax.Walk(f, visit2);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
1255
|
+
const inline = (call) => {
|
|
1256
|
+
const words = (call.Args || []).map((a) => resolveWordLiteral(a));
|
|
1257
|
+
const list = words[0] ? bodies.get(words[0]) : void 0;
|
|
1258
|
+
if (!list || callDepth >= MAX_CALL_DEPTH) return;
|
|
1259
|
+
const args = words.slice(1);
|
|
1260
|
+
const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
|
|
1261
|
+
if (seenCalls.has(key)) return;
|
|
1262
|
+
seenCalls.add(key);
|
|
1263
|
+
const outerTable = assignmentTable;
|
|
1264
|
+
const outerOffset = currentStmtOffset;
|
|
1265
|
+
for (const body of list) {
|
|
1266
|
+
if (callWalks >= MAX_CALL_WALKS) return;
|
|
1267
|
+
callWalks++;
|
|
1268
|
+
const table = /* @__PURE__ */ new Map();
|
|
1269
|
+
for (const [name, rec] of outerTable ?? [])
|
|
1270
|
+
if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
|
|
1271
|
+
for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
|
|
1272
|
+
table.delete(name);
|
|
1273
|
+
if (!rebindsPositionals(body)) {
|
|
1274
|
+
args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
|
|
1275
|
+
if (args.length === 1) {
|
|
1276
|
+
table.set("@", { value: args[0], at: -1 });
|
|
1277
|
+
table.set("*", { value: args[0], at: -1 });
|
|
1278
|
+
}
|
|
1279
|
+
table.set("#", { value: String(args.length), at: -1 });
|
|
1280
|
+
}
|
|
1281
|
+
assignmentTable = table;
|
|
1282
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1283
|
+
callDepth++;
|
|
1284
|
+
try {
|
|
1285
|
+
if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
|
|
1286
|
+
syntax.Walk(body, step);
|
|
1287
|
+
} finally {
|
|
1288
|
+
callDepth--;
|
|
1289
|
+
assignmentTable = outerTable;
|
|
1290
|
+
currentStmtOffset = outerOffset;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
const step = (node) => {
|
|
1295
|
+
if (!node) return false;
|
|
1296
|
+
const go = visit2(node);
|
|
1297
|
+
if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
|
|
1298
|
+
return go;
|
|
1299
|
+
};
|
|
1300
|
+
syntax.Walk(f, step);
|
|
1301
|
+
}
|
|
1302
|
+
function withAssignments(f, body) {
|
|
1303
|
+
const outerTable = assignmentTable;
|
|
1304
|
+
const outerOffset = currentStmtOffset;
|
|
1305
|
+
const outerWalks = callWalks;
|
|
1306
|
+
callWalks = 0;
|
|
1307
|
+
assignmentTable = new Map(
|
|
1308
|
+
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
1309
|
+
);
|
|
1310
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1311
|
+
try {
|
|
1312
|
+
recordTopLevelAssignments(f);
|
|
1313
|
+
return body();
|
|
1314
|
+
} finally {
|
|
1315
|
+
assignmentTable = outerTable;
|
|
1316
|
+
currentStmtOffset = outerOffset;
|
|
1317
|
+
callWalks = outerWalks;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function trackStatement(n) {
|
|
1321
|
+
try {
|
|
1322
|
+
currentStmtOffset = n.Pos().Offset();
|
|
1323
|
+
} catch {
|
|
1324
|
+
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1213
1327
|
function resolveWordLiteral(w) {
|
|
1214
1328
|
const parts = w?.Parts || [];
|
|
1215
1329
|
let s = "";
|
|
@@ -1330,27 +1444,31 @@ function extractShellDestinations(command) {
|
|
|
1330
1444
|
const out = [];
|
|
1331
1445
|
const seen = /* @__PURE__ */ new Set();
|
|
1332
1446
|
try {
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1447
|
+
withAssignments(
|
|
1448
|
+
f,
|
|
1449
|
+
() => walkWithCalls(f, (node) => {
|
|
1450
|
+
if (!node) return false;
|
|
1451
|
+
const n = node;
|
|
1452
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1453
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1454
|
+
const callArgs = n.Args || [];
|
|
1455
|
+
if (callArgs.length === 0) return true;
|
|
1456
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1457
|
+
const at = unwrapCommandHead(words);
|
|
1458
|
+
const name = (words[at] || "").toLowerCase().split("/").pop() ?? "";
|
|
1459
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1460
|
+
const rest = words.slice(at + 1);
|
|
1461
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1462
|
+
const host2 = parseDestHost(raw);
|
|
1463
|
+
if (!host2) continue;
|
|
1464
|
+
const key = `${name}:${host2}`;
|
|
1465
|
+
if (seen.has(key)) continue;
|
|
1466
|
+
seen.add(key);
|
|
1467
|
+
out.push({ host: host2, binary: name, raw });
|
|
1468
|
+
}
|
|
1469
|
+
return true;
|
|
1470
|
+
})
|
|
1471
|
+
);
|
|
1354
1472
|
} catch {
|
|
1355
1473
|
return out;
|
|
1356
1474
|
}
|
|
@@ -1362,39 +1480,45 @@ function extractShellDestTokens(command) {
|
|
|
1362
1480
|
const out = [];
|
|
1363
1481
|
const seen = /* @__PURE__ */ new Set();
|
|
1364
1482
|
try {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
tok = tok.
|
|
1483
|
+
withAssignments(
|
|
1484
|
+
f,
|
|
1485
|
+
() => walkWithCalls(f, (node) => {
|
|
1486
|
+
if (!node) return false;
|
|
1487
|
+
const n = node;
|
|
1488
|
+
if (syntax.NodeType(n) === "Stmt") trackStatement(n);
|
|
1489
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1490
|
+
const callArgs = n.Args || [];
|
|
1491
|
+
if (callArgs.length === 0) return true;
|
|
1492
|
+
const words = callArgs.map((a) => resolveWordLiteral(a));
|
|
1493
|
+
const head = unwrapCommandHead(words);
|
|
1494
|
+
const name = (words[head] || "").toLowerCase().split("/").pop() ?? "";
|
|
1495
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1496
|
+
const rest = words.slice(head + 1);
|
|
1497
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1498
|
+
if (!raw) continue;
|
|
1499
|
+
let tok = raw.trim();
|
|
1500
|
+
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(tok);
|
|
1501
|
+
const hasScheme = scheme !== null;
|
|
1502
|
+
if (hasScheme) tok = tok.slice(scheme[0].length);
|
|
1503
|
+
tok = tok.split(/[/?#]/)[0];
|
|
1504
|
+
const at = tok.lastIndexOf("@");
|
|
1505
|
+
if (at >= 0) tok = tok.slice(at + 1);
|
|
1506
|
+
if (tok.startsWith("[")) {
|
|
1507
|
+
const close = tok.indexOf("]");
|
|
1508
|
+
if (close > 0) tok = tok.slice(0, close + 1);
|
|
1509
|
+
} else {
|
|
1510
|
+
tok = tok.split(":")[0];
|
|
1511
|
+
}
|
|
1512
|
+
if (!tok) continue;
|
|
1513
|
+
if (!hasScheme && /^\d+$/.test(tok) && Number(tok) < 16777216) continue;
|
|
1514
|
+
const key = `${name}:${tok}`;
|
|
1515
|
+
if (seen.has(key)) continue;
|
|
1516
|
+
seen.add(key);
|
|
1517
|
+
out.push({ token: tok, binary: name });
|
|
1388
1518
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if (seen.has(key)) continue;
|
|
1393
|
-
seen.add(key);
|
|
1394
|
-
out.push({ token: tok, binary: name });
|
|
1395
|
-
}
|
|
1396
|
-
return true;
|
|
1397
|
-
});
|
|
1519
|
+
return true;
|
|
1520
|
+
})
|
|
1521
|
+
);
|
|
1398
1522
|
} catch {
|
|
1399
1523
|
return out;
|
|
1400
1524
|
}
|
|
@@ -1513,6 +1637,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1513
1637
|
if (depth === 0) {
|
|
1514
1638
|
seenPayloads = /* @__PURE__ */ new Set();
|
|
1515
1639
|
payloadBudget = PAYLOAD_BUDGET;
|
|
1640
|
+
if (callDepth === 0) callWalks = 0;
|
|
1516
1641
|
}
|
|
1517
1642
|
assignmentTable = new Map(
|
|
1518
1643
|
[...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
|
|
@@ -1520,7 +1645,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1520
1645
|
currentStmtOffset = Number.MAX_SAFE_INTEGER;
|
|
1521
1646
|
try {
|
|
1522
1647
|
recordTopLevelAssignments(f);
|
|
1523
|
-
|
|
1648
|
+
walkWithCalls(f, (node) => {
|
|
1524
1649
|
if (!node || result?.verdict === "block") return false;
|
|
1525
1650
|
const n = node;
|
|
1526
1651
|
const nodeType = syntax.NodeType(n);
|
|
@@ -2309,6 +2434,19 @@ function runnablePrefix(command) {
|
|
|
2309
2434
|
}
|
|
2310
2435
|
return null;
|
|
2311
2436
|
}
|
|
2437
|
+
function uploadedFile(tool, flag, value) {
|
|
2438
|
+
const v = value.value;
|
|
2439
|
+
const file2 = (f) => f && f !== "-" && f !== "." ? { value: f } : null;
|
|
2440
|
+
if (tool === "curl") {
|
|
2441
|
+
if (["-d", "--data", "--data-ascii", "--data-binary", "--json"].includes(flag))
|
|
2442
|
+
return v.startsWith("@") ? file2(v.slice(1)) : null;
|
|
2443
|
+
if (flag === "--data-urlencode") return file2(/^[^=@]*@(.+)$/.exec(v)?.[1]);
|
|
2444
|
+
if (flag === "-F" || flag === "--form") return file2(/^[^=]*=[@<]([^;]+)/.exec(v)?.[1]);
|
|
2445
|
+
if (flag === "-T" || flag === "--upload-file") return file2(v);
|
|
2446
|
+
}
|
|
2447
|
+
if (tool === "wget" && ["--post-file", "--body-file"].includes(flag)) return file2(v);
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2312
2450
|
function merge(a, b) {
|
|
2313
2451
|
a.cwd = unique([...a.cwd, ...b.cwd]);
|
|
2314
2452
|
for (const key of /* @__PURE__ */ new Set([...a.vars.keys(), ...b.vars.keys()])) {
|
|
@@ -2382,6 +2520,9 @@ function wordValues(word, state) {
|
|
|
2382
2520
|
function collectShellFileAccesses(command, context = {}) {
|
|
2383
2521
|
const out = [];
|
|
2384
2522
|
let payloads = 256;
|
|
2523
|
+
const functions = /* @__PURE__ */ new Map();
|
|
2524
|
+
const seenCalls = /* @__PURE__ */ new Set();
|
|
2525
|
+
let callWalks2 = MAX_CALL_WALKS2;
|
|
2385
2526
|
const initial = {
|
|
2386
2527
|
cwd: [context.cwd ?? ""],
|
|
2387
2528
|
vars: /* @__PURE__ */ new Map([["HOME", [context.homeDirs?.[0] ?? "~"]]])
|
|
@@ -2639,8 +2780,98 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2639
2780
|
}
|
|
2640
2781
|
return;
|
|
2641
2782
|
}
|
|
2783
|
+
if (name === "wget" || name === "curl" || name === "aria2c") {
|
|
2784
|
+
downloadOutputs(name, args, state);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
if (["http", "https", "xh", "xhs"].includes(name)) {
|
|
2788
|
+
for (const token of args) {
|
|
2789
|
+
if (!token || token.dynamic || /^[a-z][a-z0-9+.-]*:\/\//i.test(token.value)) continue;
|
|
2790
|
+
const m = /^(?:@|[^=:@\s]+@|[^=:@\s]*=@)(.+)$/.exec(token.value);
|
|
2791
|
+
if (m && m[1] !== "-") add("read", { value: m[1] }, state, { upload: true });
|
|
2792
|
+
}
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
if (name === "scp" || name === "rsync") {
|
|
2796
|
+
const operands2 = strings.filter((s) => s && !s.startsWith("-"));
|
|
2797
|
+
const remote = operands2.slice(1).some((o) => /^[^/\s]+:/.test(o));
|
|
2798
|
+
for (const p of copySourcePaths(words))
|
|
2799
|
+
add("read", locate(p), state, remote ? { upload: true } : {});
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2642
2802
|
for (const p of copySourcePaths(words)) add("read", locate(p), state);
|
|
2643
2803
|
}
|
|
2804
|
+
function downloadOutputs(name, args, state) {
|
|
2805
|
+
const valueShort = DOWNLOAD_SHORT_VALUE[name];
|
|
2806
|
+
const outputs = [];
|
|
2807
|
+
const urls = [];
|
|
2808
|
+
let dir2 = null;
|
|
2809
|
+
let remoteName = false;
|
|
2810
|
+
let document = false;
|
|
2811
|
+
let serverNamed = false;
|
|
2812
|
+
const take = (flag, value) => {
|
|
2813
|
+
if (!value) return;
|
|
2814
|
+
const sent = uploadedFile(name, flag, value);
|
|
2815
|
+
if (sent) add("read", sent, state, { upload: true });
|
|
2816
|
+
if (DOWNLOAD_OUTPUT_FLAGS[name].includes(flag)) {
|
|
2817
|
+
if (["-O", "--output-document", "--out"].includes(flag) || name === "aria2c" && flag === "-o")
|
|
2818
|
+
document = true;
|
|
2819
|
+
if (value.value !== "-" || value.pattern || value.dynamic) outputs.push(value);
|
|
2820
|
+
} else if (DOWNLOAD_DIR_FLAGS[name].includes(flag)) dir2 = value;
|
|
2821
|
+
else if (name === "wget" && (flag === "-i" || flag === "--input-file")) serverNamed = true;
|
|
2822
|
+
};
|
|
2823
|
+
for (let i = 0; i < args.length; i++) {
|
|
2824
|
+
const token = args[i];
|
|
2825
|
+
if (!token) continue;
|
|
2826
|
+
const v = token.value;
|
|
2827
|
+
if (v.startsWith("--")) {
|
|
2828
|
+
const eq = v.indexOf("=");
|
|
2829
|
+
const flag = eq < 0 ? v : v.slice(0, eq);
|
|
2830
|
+
if (["--remote-name", "--remote-name-all"].includes(flag)) remoteName = true;
|
|
2831
|
+
if (["--remote-header-name", "--recursive", "--mirror", "--page-requisites"].includes(flag))
|
|
2832
|
+
serverNamed = true;
|
|
2833
|
+
if (DOWNLOAD_LONG_VALUE[name].includes(flag))
|
|
2834
|
+
take(flag, eq < 0 ? args[++i] ?? null : { ...token, value: v.slice(eq + 1) });
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
if (v.startsWith("-") && v.length > 1 && !token.dynamic) {
|
|
2838
|
+
for (let j = 1; j < v.length; j++) {
|
|
2839
|
+
const c = v[j];
|
|
2840
|
+
if (name === "wget" && c === "n") {
|
|
2841
|
+
j++;
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
if (name === "curl" && c === "O") remoteName = true;
|
|
2845
|
+
if (name === "curl" && c === "J") serverNamed = true;
|
|
2846
|
+
if (name === "wget" && (c === "r" || c === "m" || c === "p")) serverNamed = true;
|
|
2847
|
+
if (valueShort.includes(c)) {
|
|
2848
|
+
const rest = v.slice(j + 1);
|
|
2849
|
+
take(`-${c}`, rest ? { value: rest } : args[++i] ?? null);
|
|
2850
|
+
break;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
if (/^(?:https?|ftps?):\/\//i.test(v)) urls.push(v);
|
|
2856
|
+
}
|
|
2857
|
+
for (const out2 of outputs) {
|
|
2858
|
+
const spelled = name === "aria2c" && dir2?.value && !out2.value.startsWith("/") ? { ...out2, value: `${dir2.value.replace(/\/+$/, "")}/${out2.value}` } : out2;
|
|
2859
|
+
if (name === "curl" && /#\d/.test(spelled.value))
|
|
2860
|
+
add("write", { value: "", pattern: spelled.value.replace(/#\d+/g, "*"), dot: true }, state);
|
|
2861
|
+
else add("write", spelled, state);
|
|
2862
|
+
}
|
|
2863
|
+
const remote = name === "curl" ? remoteName || serverNamed : !document || serverNamed;
|
|
2864
|
+
if (!remote) return;
|
|
2865
|
+
const into = dir2?.value ? dir2.value.replace(/\/+$/, "") : ".";
|
|
2866
|
+
if (serverNamed || urls.length === 0) {
|
|
2867
|
+
add("write", { value: "", pattern: `${quoteGlob(into)}/*`, dot: true }, state);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
for (const url of urls) {
|
|
2871
|
+
const file2 = url.replace(/[?#].*$/, "").replace(/^[a-z]+:\/\/[^/]*/i, "").split("/").pop();
|
|
2872
|
+
add("write", { value: `${into}/${file2 || "index.html"}` }, state);
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2644
2875
|
function findActions2(tokens, words, head, state, nested, depth) {
|
|
2645
2876
|
const starts = findStartPoints(words, head).starts;
|
|
2646
2877
|
const roots = (starts.length ? starts : ["."]).slice(0, MAX_ALTERNATIVES);
|
|
@@ -2712,7 +2943,35 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2712
2943
|
const words = wordValues(node, state);
|
|
2713
2944
|
alternatives = alternatives.flatMap((prefix) => (words.length ? words : [null]).map((w) => [...prefix, w])).slice(0, MAX_ALTERNATIVES);
|
|
2714
2945
|
}
|
|
2715
|
-
for (const tokens of alternatives)
|
|
2946
|
+
for (const tokens of alternatives) {
|
|
2947
|
+
fromWords(tokens, state, nested, depth);
|
|
2948
|
+
inlineCall(tokens, state, depth);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
function inlineCall(tokens, state, depth) {
|
|
2952
|
+
const head = tokens[0];
|
|
2953
|
+
if (!head || head.dynamic || head.pattern) return;
|
|
2954
|
+
const bodies = functions.get(head.value);
|
|
2955
|
+
if (!bodies || (state.callDepth ?? 0) >= MAX_CALL_DEPTH2) return;
|
|
2956
|
+
const args = tokens.slice(1).map((a) => a && !a.dynamic ? a.value : null);
|
|
2957
|
+
const vars = JSON.stringify([...state.vars].sort(([a], [b]) => a < b ? -1 : 1));
|
|
2958
|
+
const key = `${head.value}\0${JSON.stringify(args)}\0${state.cwd.join("")}\0${vars}`;
|
|
2959
|
+
if (seenCalls.has(key)) return;
|
|
2960
|
+
seenCalls.add(key);
|
|
2961
|
+
for (const body of bodies) {
|
|
2962
|
+
if (callWalks2-- <= 0) return;
|
|
2963
|
+
const local = clone(state);
|
|
2964
|
+
local.callDepth = (state.callDepth ?? 0) + 1;
|
|
2965
|
+
for (const name of POSITIONAL) local.vars.delete(name);
|
|
2966
|
+
if (!rebindsPositionals(body)) {
|
|
2967
|
+
args.slice(0, 9).forEach((v, i) => local.vars.set(String(i + 1), v === null ? null : [v]));
|
|
2968
|
+
const all = args.every((v) => v !== null) && args.length ? unique(args) : null;
|
|
2969
|
+
local.vars.set("@", all);
|
|
2970
|
+
local.vars.set("*", all);
|
|
2971
|
+
local.vars.set("#", [String(args.length)]);
|
|
2972
|
+
}
|
|
2973
|
+
statement(body, local, depth + 1);
|
|
2974
|
+
}
|
|
2716
2975
|
}
|
|
2717
2976
|
function readsScriptFromStdin(stmt) {
|
|
2718
2977
|
const cmd = stmt?.Cmd;
|
|
@@ -2749,11 +3008,13 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2749
3008
|
if (!stmt || depth > MAX_DEPTH) return;
|
|
2750
3009
|
state.onSuccess = void 0;
|
|
2751
3010
|
if (stmt.Background) state = clone(state);
|
|
3011
|
+
const headWord = stmt.Cmd && syntax2.NodeType(stmt.Cmd) === "CallExpr" ? base(resolveWordLiteral(stmt.Cmd.Args?.[0])) : "";
|
|
3012
|
+
const sends = SENDING_STDIN.has(headWord);
|
|
2752
3013
|
for (const redir of stmt.Redirs ?? []) {
|
|
2753
3014
|
substitutions(redir.Word, state, depth);
|
|
2754
3015
|
for (const word of wordValues(redir.Word, state)) {
|
|
2755
3016
|
if (WRITES.has(redir.Op)) add("write", word, state);
|
|
2756
|
-
if (READS.has(redir.Op)) add("read", word, state);
|
|
3017
|
+
if (READS.has(redir.Op)) add("read", word, state, sends ? { upload: true } : {});
|
|
2757
3018
|
if (redir.Op === DUP_OUT && word.value && !/^\d+$/.test(word.value))
|
|
2758
3019
|
add("write", word, state);
|
|
2759
3020
|
}
|
|
@@ -2862,7 +3123,10 @@ function collectShellFileAccesses(command, context = {}) {
|
|
|
2862
3123
|
}
|
|
2863
3124
|
const parsed = parseShared(command);
|
|
2864
3125
|
const program2 = typeof parsed !== "symbol" ? parsed : runnablePrefix(command);
|
|
2865
|
-
if (program2)
|
|
3126
|
+
if (program2) {
|
|
3127
|
+
for (const [name, bodies] of functionBodies(program2)) functions.set(name, bodies);
|
|
3128
|
+
statements(program2.Stmts ?? [], initial, 0);
|
|
3129
|
+
}
|
|
2866
3130
|
return out;
|
|
2867
3131
|
}
|
|
2868
3132
|
function isAgentInstructionPath(absPath, root) {
|
|
@@ -2978,6 +3242,33 @@ function hits(access, paths, directories, context) {
|
|
|
2978
3242
|
}
|
|
2979
3243
|
return false;
|
|
2980
3244
|
}
|
|
3245
|
+
function hitsSystemDir(access, context, dirs = SYSTEM_DIRS) {
|
|
3246
|
+
const home = context.homeDirs?.[0] ?? "~";
|
|
3247
|
+
const candidates2 = access.candidates ?? [
|
|
3248
|
+
normalizeFilePath(access.path, access.cwd ?? context.cwd, home)
|
|
3249
|
+
];
|
|
3250
|
+
for (const p of candidates2) {
|
|
3251
|
+
if (dirs.some((d) => within(p, d)) && !exempt(p)) return true;
|
|
3252
|
+
if (access.recursive && dirs.some((d) => within(d, p))) return true;
|
|
3253
|
+
}
|
|
3254
|
+
if (!access.glob) return false;
|
|
3255
|
+
const pattern = normalizeFilePath(access.glob, access.cwd ?? context.cwd, home);
|
|
3256
|
+
const magic = pattern.search(/[*?[{]/);
|
|
3257
|
+
const literal = magic < 0 ? pattern : pattern.slice(0, pattern.lastIndexOf("/", magic) + 1);
|
|
3258
|
+
if (literal && exempt(literal.replace(/\/$/, "") || "/")) return false;
|
|
3259
|
+
const options = { dot: access.dotGlob === true, nonegate: true };
|
|
3260
|
+
const components = pattern.split("/");
|
|
3261
|
+
for (const root of dirs) {
|
|
3262
|
+
const count = root.split("/").length;
|
|
3263
|
+
if (components.length > count && picomatch(components.slice(0, count).join("/"), options)(root))
|
|
3264
|
+
return true;
|
|
3265
|
+
if (access.recursive && picomatch(pattern, options)(root)) return true;
|
|
3266
|
+
}
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
function uploadDirs(context) {
|
|
3270
|
+
return context.homeDirs?.[0] === "/root" ? UPLOAD_SYSTEM_DIRS.filter((d) => d !== "/root") : UPLOAD_SYSTEM_DIRS;
|
|
3271
|
+
}
|
|
2981
3272
|
function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
2982
3273
|
const homes = [.../* @__PURE__ */ new Set([...context.homeDirs ?? ["~"], "/root"])];
|
|
2983
3274
|
const startup = homes.flatMap(
|
|
@@ -2999,6 +3290,14 @@ function evaluateProtectedFileAccess(accesses, context, settings) {
|
|
|
2999
3290
|
"files.shell-startup",
|
|
3000
3291
|
"review-shell-startup-mutation",
|
|
3001
3292
|
"Changing shell startup files requires approval"
|
|
3293
|
+
] : mutation && hitsSystemDir(access, context) ? [
|
|
3294
|
+
"files.system-dirs",
|
|
3295
|
+
"review-system-dir-mutation",
|
|
3296
|
+
"Changing files in a system directory requires approval"
|
|
3297
|
+
] : !mutation && access.upload && hitsSystemDir(access, context, uploadDirs(context)) ? [
|
|
3298
|
+
"files.system-dirs",
|
|
3299
|
+
"review-system-file-upload",
|
|
3300
|
+
"Uploading a system file requires approval"
|
|
3002
3301
|
] : !mutation && hits(access, PASSWORD_HASH_FILES, [], context) ? [
|
|
3003
3302
|
"files.password-hashes",
|
|
3004
3303
|
"review-password-hash-read",
|
|
@@ -3326,16 +3625,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
|
|
|
3326
3625
|
if (EMAIL_VALUE_RE.test(value)) return false;
|
|
3327
3626
|
if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
|
|
3328
3627
|
if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
|
|
3628
|
+
if (CONCATENATION_RE.test(value)) return false;
|
|
3329
3629
|
if (rejectedByStructuredValidator(value)) return false;
|
|
3330
3630
|
return shannonEntropy(value) >= minEntropy;
|
|
3331
3631
|
}
|
|
3332
3632
|
function nameHoldsCredential(name) {
|
|
3333
3633
|
return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
|
|
3334
3634
|
}
|
|
3635
|
+
function isOtpDocSecret(value) {
|
|
3636
|
+
return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
|
|
3637
|
+
}
|
|
3335
3638
|
function validateQueryCredential(raw) {
|
|
3336
3639
|
const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
|
|
3337
3640
|
if (value === null) return false;
|
|
3338
|
-
if (OTP_SECRET_RE.test(value))
|
|
3641
|
+
if (OTP_SECRET_RE.test(value))
|
|
3642
|
+
return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
|
|
3339
3643
|
if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
|
|
3340
3644
|
return looksGenerated(value, 12, 3);
|
|
3341
3645
|
}
|
|
@@ -3349,13 +3653,40 @@ function keywordEndsAPart(name) {
|
|
|
3349
3653
|
}
|
|
3350
3654
|
function validateAssignedSecret(raw, before = "") {
|
|
3351
3655
|
const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
|
|
3352
|
-
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
|
|
3656
|
+
const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
|
|
3353
3657
|
if (value === null || name === null) return false;
|
|
3354
|
-
if (!keywordEndsAPart(name)
|
|
3355
|
-
|
|
3658
|
+
if (!keywordEndsAPart(name)) return false;
|
|
3659
|
+
if (ARN_CONTEXT_RE.test(before)) return false;
|
|
3660
|
+
const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
|
|
3356
3661
|
if (!nameHoldsCredential(prefix + name)) return false;
|
|
3357
3662
|
return looksGenerated(value, 12, 3.5);
|
|
3358
3663
|
}
|
|
3664
|
+
function validateBasicCredential(raw) {
|
|
3665
|
+
const b64 = capturedValue(raw, BASIC_VALUE_RE);
|
|
3666
|
+
if (b64 === null) return false;
|
|
3667
|
+
const bare = b64.replace(/=+$/, "");
|
|
3668
|
+
if (bare.length % 4 === 1) return false;
|
|
3669
|
+
const bytes = Buffer.from(b64, "base64");
|
|
3670
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
|
|
3671
|
+
const text = bytes.toString("utf8");
|
|
3672
|
+
if (!Buffer.from(text, "utf8").equals(bytes)) return false;
|
|
3673
|
+
for (let i = 0; i < text.length; i++) {
|
|
3674
|
+
const c = text.charCodeAt(i);
|
|
3675
|
+
if (c < 32 || c === 127) return false;
|
|
3676
|
+
}
|
|
3677
|
+
const pair = BASIC_PAIR_RE.exec(text);
|
|
3678
|
+
if (!pair) return false;
|
|
3679
|
+
const password = pair[2];
|
|
3680
|
+
if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
|
|
3681
|
+
const lower2 = password.toLowerCase();
|
|
3682
|
+
if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
|
|
3683
|
+
return shannonEntropy(password) >= 3;
|
|
3684
|
+
}
|
|
3685
|
+
function validateOtpSecret(raw) {
|
|
3686
|
+
const secret = capturedValue(raw, OTP_CAPTURE_RE);
|
|
3687
|
+
if (secret === null || isOtpDocSecret(secret)) return false;
|
|
3688
|
+
return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
|
|
3689
|
+
}
|
|
3359
3690
|
function matchSensitivePath2(resolvedPath, originalPath, options = {}) {
|
|
3360
3691
|
const normalised = resolvedPath.replace(/\\/g, "/");
|
|
3361
3692
|
if (!options.systemAccessHandled && isLegacySystemPath(normalised))
|
|
@@ -3664,6 +3995,156 @@ function parsePySpec(raw) {
|
|
|
3664
3995
|
const exact = PY_EXACT_RE.exec(m[2].trim());
|
|
3665
3996
|
return { name, version: exact ? exact[1] : void 0 };
|
|
3666
3997
|
}
|
|
3998
|
+
function exactVersion(v) {
|
|
3999
|
+
if (!v) return void 0;
|
|
4000
|
+
const m = EXACT_VERSION_RE.exec(v.trim());
|
|
4001
|
+
return m ? m[1] : void 0;
|
|
4002
|
+
}
|
|
4003
|
+
function push(out, ecosystem, manager, name, version2, raw) {
|
|
4004
|
+
out.push({ ecosystem, manager, name, ...version2 ? { version: version2 } : {}, raw });
|
|
4005
|
+
}
|
|
4006
|
+
function gemInstall(words, from, out) {
|
|
4007
|
+
const dash = words.indexOf("--", from);
|
|
4008
|
+
const own = dash < 0 ? words : words.slice(0, dash);
|
|
4009
|
+
const pinned = exactVersion(flagValues(own, from, ["-v", "--version"])[0]);
|
|
4010
|
+
for (const raw of operands(own, from, GEM_VALUE_FLAGS)) {
|
|
4011
|
+
if (NON_REGISTRY_RE.test(raw) || /\.gem$/i.test(raw)) continue;
|
|
4012
|
+
const colon = raw.indexOf(":");
|
|
4013
|
+
const name = colon < 0 ? raw : raw.slice(0, colon);
|
|
4014
|
+
if (!GEM_NAME_RE.test(name)) continue;
|
|
4015
|
+
push(
|
|
4016
|
+
out,
|
|
4017
|
+
"RubyGems",
|
|
4018
|
+
"gem",
|
|
4019
|
+
name,
|
|
4020
|
+
colon < 0 ? pinned : exactVersion(raw.slice(colon + 1)),
|
|
4021
|
+
raw
|
|
4022
|
+
);
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
function cargoCrates(words, from, manager, out) {
|
|
4026
|
+
const flags = manager === "cargo add" ? CARGO_ADD_VALUE_FLAGS : CARGO_INSTALL_VALUE_FLAGS;
|
|
4027
|
+
if (flagValues(words, from, ["--git", "--path", "--registry", "--index"]).length > 0) return;
|
|
4028
|
+
if (words.some((w) => w === "--git" || w === "--path")) return;
|
|
4029
|
+
const flagVersion = manager === "cargo install" ? exactVersion(flagValues(words, from, ["--version", "--vers"])[0]) : void 0;
|
|
4030
|
+
for (const raw of operands(words, from, flags)) {
|
|
4031
|
+
const at = raw.indexOf("@");
|
|
4032
|
+
const name = at < 0 ? raw : raw.slice(0, at);
|
|
4033
|
+
if (!CRATE_NAME_RE.test(name)) continue;
|
|
4034
|
+
push(
|
|
4035
|
+
out,
|
|
4036
|
+
"crates.io",
|
|
4037
|
+
manager,
|
|
4038
|
+
name,
|
|
4039
|
+
at < 0 ? flagVersion : exactVersion(raw.slice(at + 1)),
|
|
4040
|
+
raw
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
function goModules(words, from, manager, out) {
|
|
4045
|
+
let specs = operands(words, from, GO_VALUE_FLAGS);
|
|
4046
|
+
if (manager === "go run") specs = specs.slice(0, 1).filter((s) => s.includes("@"));
|
|
4047
|
+
for (const raw of specs) {
|
|
4048
|
+
const at = raw.indexOf("@");
|
|
4049
|
+
const pathPart = (at < 0 ? raw : raw.slice(0, at)).replace(/\/\.\.\.$/, "");
|
|
4050
|
+
const ver = at < 0 ? void 0 : raw.slice(at + 1);
|
|
4051
|
+
if (ver === "none") continue;
|
|
4052
|
+
if (!GO_PATH_RE.test(pathPart) || /\.go$/i.test(pathPart)) continue;
|
|
4053
|
+
const exact = ver && /^v\d/.test(ver) ? exactVersion(ver) : void 0;
|
|
4054
|
+
push(out, "Go", manager, pathPart, exact, raw);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
function dotnetAdd(words, from, out) {
|
|
4058
|
+
let k = -1;
|
|
4059
|
+
for (let i = from; i < Math.min(words.length, from + 2); i++) {
|
|
4060
|
+
if (lower(words[i]) === "package") {
|
|
4061
|
+
k = i;
|
|
4062
|
+
break;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
if (k < 0) return;
|
|
4066
|
+
const name = operands(words, k + 1, DOTNET_ADD_VALUE_FLAGS)[0];
|
|
4067
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4068
|
+
const version2 = exactVersion(flagValues(words, k + 1, ["-v", "--version"])[0]);
|
|
4069
|
+
push(out, "NuGet", "dotnet add package", name, version2, name);
|
|
4070
|
+
}
|
|
4071
|
+
function dotnetTool(words, from, out) {
|
|
4072
|
+
const name = operands(words, from, DOTNET_TOOL_VALUE_FLAGS)[0];
|
|
4073
|
+
if (!name || !NUGET_NAME_RE.test(name)) return;
|
|
4074
|
+
const version2 = exactVersion(flagValues(words, from, ["--version"])[0]);
|
|
4075
|
+
push(out, "NuGet", "dotnet tool", name, version2, name);
|
|
4076
|
+
}
|
|
4077
|
+
function nugetInstall(words, from, out) {
|
|
4078
|
+
const lowered = words.map((w) => w !== null && w.startsWith("-") ? w.toLowerCase() : w);
|
|
4079
|
+
const name = operands(lowered, from, NUGET_VALUE_FLAGS)[0];
|
|
4080
|
+
if (!name || !NUGET_NAME_RE.test(name) || /\.config$/i.test(name)) return;
|
|
4081
|
+
const version2 = exactVersion(flagValues(lowered, from, ["-version"])[0]);
|
|
4082
|
+
push(out, "NuGet", "nuget install", name, version2, name);
|
|
4083
|
+
}
|
|
4084
|
+
function mavenGet(words, from, out) {
|
|
4085
|
+
const goal = words.slice(from).some(
|
|
4086
|
+
(w) => w !== null && /(?:^|:)dependency:get$|^org\.apache\.maven\.plugins:maven-dependency-plugin:(?:[\w.-]+:)?get$/.test(
|
|
4087
|
+
w
|
|
4088
|
+
)
|
|
4089
|
+
);
|
|
4090
|
+
if (!goal) return;
|
|
4091
|
+
const prop = (key) => {
|
|
4092
|
+
for (let i = from; i < words.length; i++) {
|
|
4093
|
+
const w = words[i];
|
|
4094
|
+
if (w === null) continue;
|
|
4095
|
+
if (w.startsWith(`-D${key}=`)) return w.slice(key.length + 3);
|
|
4096
|
+
if (w === "-D" && words[i + 1]?.startsWith(`${key}=`))
|
|
4097
|
+
return words[i + 1].slice(key.length + 1);
|
|
4098
|
+
}
|
|
4099
|
+
return void 0;
|
|
4100
|
+
};
|
|
4101
|
+
const artifact = prop("artifact");
|
|
4102
|
+
let coord;
|
|
4103
|
+
let ver;
|
|
4104
|
+
if (artifact) {
|
|
4105
|
+
const parts = artifact.split(":");
|
|
4106
|
+
if (parts.length < 2) return;
|
|
4107
|
+
coord = `${parts[0]}:${parts[1]}`;
|
|
4108
|
+
ver = parts[2];
|
|
4109
|
+
} else {
|
|
4110
|
+
const g = prop("groupId");
|
|
4111
|
+
const a = prop("artifactId");
|
|
4112
|
+
if (!g || !a) return;
|
|
4113
|
+
coord = `${g}:${a}`;
|
|
4114
|
+
ver = prop("version");
|
|
4115
|
+
}
|
|
4116
|
+
if (!MAVEN_COORD_RE.test(coord)) return;
|
|
4117
|
+
const exact = ver && !/^(?:LATEST|RELEASE)$/i.test(ver) ? exactVersion(ver) : void 0;
|
|
4118
|
+
push(out, "Maven", "mvn dependency:get", coord, exact, artifact ?? coord);
|
|
4119
|
+
}
|
|
4120
|
+
function composerPackages(words, from, manager, out) {
|
|
4121
|
+
const ops = operands(words, from, COMPOSER_VALUE_FLAGS);
|
|
4122
|
+
const specs = manager === "composer create-project" ? ops.slice(0, 1) : ops;
|
|
4123
|
+
for (const raw of specs) {
|
|
4124
|
+
const sep = raw.search(/[:=]/);
|
|
4125
|
+
const name = sep < 0 ? raw : raw.slice(0, sep);
|
|
4126
|
+
if (!COMPOSER_NAME_RE.test(name)) continue;
|
|
4127
|
+
let ver = sep < 0 ? void 0 : raw.slice(sep + 1);
|
|
4128
|
+
if (manager === "composer create-project" && ver === void 0) ver = ops[2] ?? void 0;
|
|
4129
|
+
push(out, "Packagist", manager, name.toLowerCase(), exactVersion(ver), raw);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
function editorExtensions(words, from, out) {
|
|
4133
|
+
for (const raw of flagValues(words, from, ["--install-extension"])) {
|
|
4134
|
+
if (/\.vsix$/i.test(raw) || NON_REGISTRY_RE.test(raw)) continue;
|
|
4135
|
+
const at = raw.lastIndexOf("@");
|
|
4136
|
+
const name = at <= 0 ? raw : raw.slice(0, at);
|
|
4137
|
+
if (!EXTENSION_ID_RE.test(name)) continue;
|
|
4138
|
+
push(
|
|
4139
|
+
out,
|
|
4140
|
+
"VSCode",
|
|
4141
|
+
"code --install-extension",
|
|
4142
|
+
name,
|
|
4143
|
+
at <= 0 ? void 0 : exactVersion(raw.slice(at + 1)),
|
|
4144
|
+
raw
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
3667
4148
|
function basename(w) {
|
|
3668
4149
|
return (w ?? "").toLowerCase().split("/").pop() ?? "";
|
|
3669
4150
|
}
|
|
@@ -3895,7 +4376,63 @@ function fromCall(words, out, depth) {
|
|
|
3895
4376
|
pushPy(out, "pipenv", operands(words, v + 1, PIPENV_VALUE_FLAGS));
|
|
3896
4377
|
return;
|
|
3897
4378
|
}
|
|
4379
|
+
case "gem": {
|
|
4380
|
+
const v = verbIndex(words, start + 1, GEM_VALUE_FLAGS);
|
|
4381
|
+
if (v >= 0 && ["install", "i"].includes(lower(words[v]))) gemInstall(words, v + 1, out);
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
case "bundle": {
|
|
4385
|
+
const v = verbIndex(words, start + 1, BUNDLE_VALUE_FLAGS);
|
|
4386
|
+
if (v < 0 || lower(words[v]) !== "add") return;
|
|
4387
|
+
if (flagValues(words, v + 1, ["--git", "--github", "--path"]).length > 0) return;
|
|
4388
|
+
const version2 = exactVersion(flagValues(words, v + 1, ["-v", "--version"])[0]);
|
|
4389
|
+
for (const raw of operands(words, v + 1, BUNDLE_VALUE_FLAGS))
|
|
4390
|
+
if (GEM_NAME_RE.test(raw)) push(out, "RubyGems", "bundle add", raw, version2, raw);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
case "cargo": {
|
|
4394
|
+
const from = words[start + 1]?.startsWith("+") ? start + 2 : start + 1;
|
|
4395
|
+
const v = verbIndex(words, from, CARGO_ADD_VALUE_FLAGS);
|
|
4396
|
+
const verb = lower(words[v]);
|
|
4397
|
+
if (verb === "add") cargoCrates(words, v + 1, "cargo add", out);
|
|
4398
|
+
else if (verb === "install") cargoCrates(words, v + 1, "cargo install", out);
|
|
4399
|
+
return;
|
|
4400
|
+
}
|
|
4401
|
+
case "go": {
|
|
4402
|
+
const v = verbIndex(words, start + 1, GO_VALUE_FLAGS);
|
|
4403
|
+
const verb = lower(words[v]);
|
|
4404
|
+
if (verb === "get" || verb === "install" || verb === "run")
|
|
4405
|
+
goModules(words, v + 1, `go ${verb}`, out);
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
case "dotnet": {
|
|
4409
|
+
const v = verbIndex(words, start + 1, DOTNET_ADD_VALUE_FLAGS);
|
|
4410
|
+
const verb = lower(words[v]);
|
|
4411
|
+
if (verb === "add") dotnetAdd(words, v + 1, out);
|
|
4412
|
+
else if (verb === "tool" && ["install", "update"].includes(lower(words[v + 1])))
|
|
4413
|
+
dotnetTool(words, v + 2, out);
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
case "nuget": {
|
|
4417
|
+
if (lower(words[start + 1]) === "install") nugetInstall(words, start + 2, out);
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
case "mvn":
|
|
4421
|
+
case "mvnw":
|
|
4422
|
+
mavenGet(words, start + 1, out);
|
|
4423
|
+
return;
|
|
4424
|
+
case "composer": {
|
|
4425
|
+
let v = verbIndex(words, start + 1, COMPOSER_VALUE_FLAGS);
|
|
4426
|
+
if (v >= 0 && lower(words[v]) === "global") v = verbIndex(words, v + 1, COMPOSER_VALUE_FLAGS);
|
|
4427
|
+
const verb = lower(words[v]);
|
|
4428
|
+
if (verb === "require" || verb === "req" || verb === "r")
|
|
4429
|
+
composerPackages(words, v + 1, "composer require", out);
|
|
4430
|
+
else if (verb === "create-project")
|
|
4431
|
+
composerPackages(words, v + 1, "composer create-project", out);
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
3898
4434
|
default:
|
|
4435
|
+
if (EDITOR_HEADS.includes(family)) editorExtensions(words, start + 1, out);
|
|
3899
4436
|
return;
|
|
3900
4437
|
}
|
|
3901
4438
|
}
|
|
@@ -5459,11 +5996,11 @@ function isDecimalFraction(text, index, unseparated) {
|
|
|
5459
5996
|
const a = text[index - 2];
|
|
5460
5997
|
if (sep === ".") {
|
|
5461
5998
|
if (a !== void 0 && a >= "0" && a <= "9") return true;
|
|
5462
|
-
return a === void 0 ||
|
|
5999
|
+
return a === void 0 || (a === "-" || a === "+") && index === 2;
|
|
5463
6000
|
}
|
|
5464
6001
|
if (sep === "," && a === "0") {
|
|
5465
6002
|
const b = text[index - 3];
|
|
5466
|
-
return b === void 0 ||
|
|
6003
|
+
return b === void 0 || b === ";";
|
|
5467
6004
|
}
|
|
5468
6005
|
return false;
|
|
5469
6006
|
}
|
|
@@ -6663,7 +7200,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
6663
7200
|
}
|
|
6664
7201
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
6665
7202
|
}
|
|
6666
|
-
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE,
|
|
7203
|
+
var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, CHECK_GROUPS, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, PROJECT_JAIL_BLOCK_READ, OVERRIDE_PREFIX, LABEL_WRAPPERS, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, MAX_ALTERNATIVES, MAX_CALL_DEPTH2, MAX_CALL_WALKS2, POSITIONAL, MAX_DEPTH, MAX_PAYLOAD_DEPTH, unique, clone, SENDING_STDIN, DOWNLOAD_SHORT_VALUE, DOWNLOAD_LONG_VALUE, DOWNLOAD_OUTPUT_FLAGS, DOWNLOAD_DIR_FLAGS, base, quoteGlob, asWord, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_AUTH_FILES, SYSTEM_AUTH_DIRS, PASSWORD_HASH_FILES, STARTUP_NAMES, SYSTEM_DIRS, SYSTEM_DIRS_EXEMPT, UPLOAD_SYSTEM_DIRS, AGENT_INSTRUCTION_FILES, AGENT_INSTRUCTION_DIRS, LEGACY_SYSTEM_PATH_RE, exempt, RANK, BIP39_ENGLISH, BIP39_PUBLIC_VECTORS, indices, lengths, hash, MAX_MNEMONIC_HASHES, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, MAX_BLOBS, MIN_DECODED, BASE64_RUN_RE, HEX_RUN_RE, HEX_ESCAPE_RUN_RE, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, BATCH_SEPARATOR, syntax3, NPM_INSTALL_VERBS, NPM_EXEC_VERBS, PNPM_INSTALL_VERBS, YARN_INSTALL_VERBS, BUN_INSTALL_VERBS, DLX_VERBS, NON_REGISTRY_RE, NPM_NAME_RE, EXACT_SEMVER_RE, NPM_VALUE_FLAGS, PNPM_VALUE_FLAGS, YARN_VALUE_FLAGS, BUN_VALUE_FLAGS, PIP_VALUE_FLAGS, UV_VALUE_FLAGS, POETRY_VALUE_FLAGS, PIPENV_VALUE_FLAGS, PIPX_VALUE_FLAGS, PY_SPEC_RE, PY_EXACT_RE, EXACT_VERSION_RE, GEM_NAME_RE, GEM_VALUE_FLAGS, BUNDLE_VALUE_FLAGS, CRATE_NAME_RE, CARGO_ADD_VALUE_FLAGS, CARGO_INSTALL_VALUE_FLAGS, GO_VALUE_FLAGS, GO_PATH_RE, NUGET_NAME_RE, DOTNET_ADD_VALUE_FLAGS, DOTNET_TOOL_VALUE_FLAGS, NUGET_VALUE_FLAGS, MAVEN_COORD_RE, COMPOSER_NAME_RE, COMPOSER_VALUE_FLAGS, EDITOR_HEADS, EXTENSION_ID_RE, MANAGER_HEADS, SHELLS, MAX_NESTING, SHELL_VALUE_FLAGS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, DEFAULT_EGRESS_ALLOWLIST, PRIVATE_HOST_SUFFIXES, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, DESTINATION_ARGS, SECRET_VIEW_LIMITS, ARGUMENT_SCAN_LIMITS, RUN_CHAR, runChar, VERDICT_RANK, SQL_DML_KEYWORDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, DESTRUCTIVE_VERB_RE, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, MASKED_KINDS, ENTRY_RE, IBAN_TEXT_RE, EMAIL_TEXT_RE, ACCOUNT_KEY_RE, ACCOUNT_VALUE_RE, MAX_MASK_DEPTH, syntax4, EMPTY, CURL_SEND_FLAGS, WGET_SEND_FLAGS, IWR_SEND_FLAGS, WRITE_METHODS, GH_READ, GH_WRITE, GH_HOSTNAME_FLAGS, GH_REPO_VALUE_FLAGS, GH_API_VALUE_FLAGS, GIT_FETCH, PUBLISH, SHELLS2, OWN_HEADS, MAX_DEPTH3, CWD_CHANGERS, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, MAX_BLAST_PATH, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, ENGINE_VERSION;
|
|
6667
7204
|
var init_dist = __esm({
|
|
6668
7205
|
"packages/policy-engine/dist/index.mjs"() {
|
|
6669
7206
|
"use strict";
|
|
@@ -7464,6 +8001,11 @@ var init_dist = __esm({
|
|
|
7464
8001
|
example: "cat /etc/shadow",
|
|
7465
8002
|
advice: "Keep Review, or choose Block when agents must never read system password hashes."
|
|
7466
8003
|
},
|
|
8004
|
+
"files.system-dirs": {
|
|
8005
|
+
plain: "The agent writes, creates or deletes a file in a system directory, or sends a system file in a request. /usr/local and a container working directory under /usr/src are ordinary project space and are not included.",
|
|
8006
|
+
example: "wget -O /usr/bin/tool https://example.com/tool",
|
|
8007
|
+
advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
|
|
8008
|
+
},
|
|
7467
8009
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
7468
8010
|
"commands.inline-exec": {
|
|
7469
8011
|
plain: "The agent runs a small program written inside the command itself.",
|
|
@@ -7660,7 +8202,7 @@ var init_dist = __esm({
|
|
|
7660
8202
|
advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
|
|
7661
8203
|
},
|
|
7662
8204
|
"loading.malicious-package": {
|
|
7663
|
-
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
|
|
8205
|
+
plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago. Covers npm, PyPI, RubyGems, crates.io, Go, NuGet, Maven, Packagist and VS Code extensions; the publish-age and install-script signals are npm and PyPI only.",
|
|
7664
8206
|
example: "npm install of a package flagged as malware",
|
|
7665
8207
|
advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
|
|
7666
8208
|
}
|
|
@@ -7706,6 +8248,15 @@ var init_dist = __esm({
|
|
|
7706
8248
|
defaultValue: "review",
|
|
7707
8249
|
legacy: ["review-password-hash-read"]
|
|
7708
8250
|
},
|
|
8251
|
+
{
|
|
8252
|
+
id: "files.system-dirs",
|
|
8253
|
+
group: "files",
|
|
8254
|
+
title: "System directories",
|
|
8255
|
+
catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
|
|
8256
|
+
values: ALL,
|
|
8257
|
+
defaultValue: "review",
|
|
8258
|
+
legacy: ["review-system-dir-mutation", "review-system-file-upload"]
|
|
8259
|
+
},
|
|
7709
8260
|
// ── Commands ────────────────────────────────────────────────────────────
|
|
7710
8261
|
{
|
|
7711
8262
|
id: "commands.inline-exec",
|
|
@@ -9003,6 +9554,10 @@ var init_dist = __esm({
|
|
|
9003
9554
|
AND_OP = probeBinOp("a && b");
|
|
9004
9555
|
OR_OP = probeBinOp("a || b");
|
|
9005
9556
|
AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
|
|
9557
|
+
MAX_CALL_DEPTH = 4;
|
|
9558
|
+
MAX_CALL_WALKS = 256;
|
|
9559
|
+
callDepth = 0;
|
|
9560
|
+
callWalks = 0;
|
|
9006
9561
|
FS_OP_CACHE_MAX = 5e3;
|
|
9007
9562
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
9008
9563
|
stripDotSlash = (p) => p.replace(/^\.\//, "");
|
|
@@ -9055,10 +9610,79 @@ var init_dist = __esm({
|
|
|
9055
9610
|
MAX_ARGV_CHARACTERS = 262144;
|
|
9056
9611
|
({ syntax: syntax2 } = mvdanSh2);
|
|
9057
9612
|
MAX_ALTERNATIVES = 64;
|
|
9613
|
+
MAX_CALL_DEPTH2 = 4;
|
|
9614
|
+
MAX_CALL_WALKS2 = 256;
|
|
9615
|
+
POSITIONAL = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"];
|
|
9058
9616
|
MAX_DEPTH = 64;
|
|
9059
9617
|
MAX_PAYLOAD_DEPTH = 24;
|
|
9060
9618
|
unique = (a) => [...new Set(a)].slice(0, MAX_ALTERNATIVES);
|
|
9061
|
-
clone = (s) => ({
|
|
9619
|
+
clone = (s) => ({
|
|
9620
|
+
cwd: [...s.cwd],
|
|
9621
|
+
vars: new Map(s.vars),
|
|
9622
|
+
...s.callDepth ? { callDepth: s.callDepth } : {}
|
|
9623
|
+
});
|
|
9624
|
+
SENDING_STDIN = /* @__PURE__ */ new Set(["nc", "ncat", "netcat", "socat", "telnet", "curl", "wget"]);
|
|
9625
|
+
DOWNLOAD_SHORT_VALUE = {
|
|
9626
|
+
curl: "odDeEFHKmPrtTuUwxXyYzAbcCQ",
|
|
9627
|
+
wget: "OoaPiBetTwQlARDXIUY",
|
|
9628
|
+
aria2c: "odisx"
|
|
9629
|
+
};
|
|
9630
|
+
DOWNLOAD_LONG_VALUE = {
|
|
9631
|
+
curl: [
|
|
9632
|
+
"--output",
|
|
9633
|
+
"--output-dir",
|
|
9634
|
+
"--dump-header",
|
|
9635
|
+
"--cookie-jar",
|
|
9636
|
+
"--data",
|
|
9637
|
+
"--data-binary",
|
|
9638
|
+
"--data-raw",
|
|
9639
|
+
"--data-ascii",
|
|
9640
|
+
"--data-urlencode",
|
|
9641
|
+
"--form",
|
|
9642
|
+
"--header",
|
|
9643
|
+
"--request",
|
|
9644
|
+
"--user",
|
|
9645
|
+
"--user-agent",
|
|
9646
|
+
"--referer",
|
|
9647
|
+
"--cookie",
|
|
9648
|
+
"--config",
|
|
9649
|
+
"--json",
|
|
9650
|
+
"--upload-file",
|
|
9651
|
+
"--max-time",
|
|
9652
|
+
"--proxy",
|
|
9653
|
+
"--write-out",
|
|
9654
|
+
"--url"
|
|
9655
|
+
],
|
|
9656
|
+
wget: [
|
|
9657
|
+
"--output-document",
|
|
9658
|
+
"--output-file",
|
|
9659
|
+
"--append-output",
|
|
9660
|
+
"--directory-prefix",
|
|
9661
|
+
"--input-file",
|
|
9662
|
+
"--post-data",
|
|
9663
|
+
"--post-file",
|
|
9664
|
+
"--body-data",
|
|
9665
|
+
"--body-file",
|
|
9666
|
+
"--method",
|
|
9667
|
+
"--header",
|
|
9668
|
+
"--user-agent",
|
|
9669
|
+
"--user",
|
|
9670
|
+
"--password",
|
|
9671
|
+
"--tries",
|
|
9672
|
+
"--timeout"
|
|
9673
|
+
],
|
|
9674
|
+
aria2c: ["--out", "--dir", "--input-file"]
|
|
9675
|
+
};
|
|
9676
|
+
DOWNLOAD_OUTPUT_FLAGS = {
|
|
9677
|
+
curl: ["-o", "--output", "-D", "--dump-header", "-c", "--cookie-jar"],
|
|
9678
|
+
wget: ["-O", "--output-document", "-o", "--output-file", "-a", "--append-output"],
|
|
9679
|
+
aria2c: ["-o", "--out"]
|
|
9680
|
+
};
|
|
9681
|
+
DOWNLOAD_DIR_FLAGS = {
|
|
9682
|
+
curl: ["--output-dir"],
|
|
9683
|
+
wget: ["-P", "--directory-prefix"],
|
|
9684
|
+
aria2c: ["-d", "--dir"]
|
|
9685
|
+
};
|
|
9062
9686
|
base = (s) => (s ?? "").replace(/\/+$/, "").split("/").pop() ?? "";
|
|
9063
9687
|
quoteGlob = (s) => s.replace(/[\\*?\[\]{}()!+@]/g, "\\$&");
|
|
9064
9688
|
asWord = (p) => ({ value: p.value, ...p.glob ? { pattern: p.pattern } : {} });
|
|
@@ -9083,6 +9707,18 @@ var init_dist = __esm({
|
|
|
9083
9707
|
SYSTEM_AUTH_DIRS = ["/etc/security", "/etc/pam.d", "/etc/sudoers.d"];
|
|
9084
9708
|
PASSWORD_HASH_FILES = ["/etc/shadow", "/etc/gshadow"];
|
|
9085
9709
|
STARTUP_NAMES = [".bashrc", ".zshrc", ".profile", ".bash_profile"];
|
|
9710
|
+
SYSTEM_DIRS = [
|
|
9711
|
+
"/usr",
|
|
9712
|
+
"/etc",
|
|
9713
|
+
"/boot",
|
|
9714
|
+
"/bin",
|
|
9715
|
+
"/sbin",
|
|
9716
|
+
"/lib",
|
|
9717
|
+
"/lib64",
|
|
9718
|
+
"/var/log"
|
|
9719
|
+
];
|
|
9720
|
+
SYSTEM_DIRS_EXEMPT = ["/usr/local", "/usr/src"];
|
|
9721
|
+
UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
|
|
9086
9722
|
AGENT_INSTRUCTION_FILES = [
|
|
9087
9723
|
".gitlab-ci.yml",
|
|
9088
9724
|
"CLAUDE.md",
|
|
@@ -9100,6 +9736,7 @@ var init_dist = __esm({
|
|
|
9100
9736
|
".husky"
|
|
9101
9737
|
];
|
|
9102
9738
|
LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
|
|
9739
|
+
exempt = (p) => SYSTEM_DIRS_EXEMPT.some((d) => within(p, d));
|
|
9103
9740
|
RANK = { allow: 0, review: 1, block: 2 };
|
|
9104
9741
|
BIP39_ENGLISH = `abandon
|
|
9105
9742
|
ability
|
|
@@ -11426,15 +12063,20 @@ zoo`.split("\n");
|
|
|
11426
12063
|
IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
|
|
11427
12064
|
PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
|
|
11428
12065
|
DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
|
|
12066
|
+
CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
|
|
11429
12067
|
EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
|
|
11430
12068
|
FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
|
|
11431
|
-
DERIVATIVE_NAME_RE = /(?:hash|digest|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
|
|
12069
|
+
DERIVATIVE_NAME_RE = /(?:hash|digest|fingerprint|thumbprint|checksum|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
|
|
11432
12070
|
QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
|
|
11433
12071
|
OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
|
|
11434
|
-
|
|
12072
|
+
OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
|
|
12073
|
+
ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
|
|
11435
12074
|
ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
|
|
11436
|
-
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
|
|
12075
|
+
NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|pass)/i;
|
|
11437
12076
|
ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
|
|
12077
|
+
BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
|
|
12078
|
+
BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
|
|
12079
|
+
OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
|
|
11438
12080
|
DLP_PATTERNS = [
|
|
11439
12081
|
// ── AWS ───────────────────────────────────────────────────────────────────
|
|
11440
12082
|
{
|
|
@@ -11772,6 +12414,22 @@ zoo`.split("\n");
|
|
|
11772
12414
|
contextBoost: true,
|
|
11773
12415
|
minEntropy: 3
|
|
11774
12416
|
},
|
|
12417
|
+
// ── HTTP Basic credential ─────────────────────────────────────────────────
|
|
12418
|
+
// `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
|
|
12419
|
+
// config list. The validator decodes the value and accepts only a
|
|
12420
|
+
// `user:password` whose password looks generated (letters and digits, 8+
|
|
12421
|
+
// characters, the query-credential rule), so prose ("Basic authentication")
|
|
12422
|
+
// and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
|
|
12423
|
+
// review: the same weight as a bare Bearer token.
|
|
12424
|
+
{
|
|
12425
|
+
name: "HTTP Basic Credential",
|
|
12426
|
+
regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
|
|
12427
|
+
severity: "review",
|
|
12428
|
+
keywords: ["basic"],
|
|
12429
|
+
validate: validateBasicCredential,
|
|
12430
|
+
redactGroup: 1,
|
|
12431
|
+
argumentsOnly: true
|
|
12432
|
+
},
|
|
11775
12433
|
// ── Resend ────────────────────────────────────────────────────────────────
|
|
11776
12434
|
{
|
|
11777
12435
|
name: "Resend API Key",
|
|
@@ -11913,6 +12571,19 @@ zoo`.split("\n");
|
|
|
11913
12571
|
validate: validateQueryCredential,
|
|
11914
12572
|
argumentsOnly: true
|
|
11915
12573
|
},
|
|
12574
|
+
// ── OTP secret in a constructor ───────────────────────────────────────────
|
|
12575
|
+
// `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
|
|
12576
|
+
// the shared secret of a one-time-password generator written as a literal.
|
|
12577
|
+
// The otpauth URI form is `Credential in URL Query`. review.
|
|
12578
|
+
{
|
|
12579
|
+
name: "OTP Secret",
|
|
12580
|
+
regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
|
|
12581
|
+
severity: "review",
|
|
12582
|
+
keywords: ["otp"],
|
|
12583
|
+
validate: validateOtpSecret,
|
|
12584
|
+
redactGroup: 1,
|
|
12585
|
+
argumentsOnly: true
|
|
12586
|
+
},
|
|
11916
12587
|
// ── Secret assigned to a telling name ─────────────────────────────────────
|
|
11917
12588
|
// `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
|
|
11918
12589
|
// not a finding: most such lines hold a reference (`process.env.X`), a
|
|
@@ -11920,12 +12591,14 @@ zoo`.split("\n");
|
|
|
11920
12591
|
// generated: 12+ characters, not an identifier, Shannon entropy of 3.5
|
|
11921
12592
|
// bits per character or more. review, never block (node9Firewall#348,
|
|
11922
12593
|
// item 4). Tuned on CredData's public split; its look-alike lines are the
|
|
11923
|
-
// negative control.
|
|
12594
|
+
// negative control. A quote may be escaped (`\"access_token\":\"...\"`):
|
|
12595
|
+
// JSON written inside a string literal. The two optional quotes after the
|
|
12596
|
+
// name are written out, not as a counted group, to keep star height 1.
|
|
11924
12597
|
{
|
|
11925
12598
|
name: "Assigned Secret",
|
|
11926
|
-
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]
|
|
12599
|
+
regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|\bpass(?![A-Za-z0-9])|_pass(?![A-Za-z0-9]))[A-Za-z0-9_]{0,40}\\?["'\]]?\\?["'\]]?[ \t]{0,8}(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})/i,
|
|
11927
12600
|
severity: "review",
|
|
11928
|
-
keywords: ["
|
|
12601
|
+
keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
|
|
11929
12602
|
validate: validateAssignedSecret,
|
|
11930
12603
|
argumentsOnly: true
|
|
11931
12604
|
}
|
|
@@ -12193,7 +12866,169 @@ zoo`.split("\n");
|
|
|
12193
12866
|
]);
|
|
12194
12867
|
PY_SPEC_RE = /^([A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?)(?:\[[^\]]*\])?\s*(.*)$/;
|
|
12195
12868
|
PY_EXACT_RE = /^===?\s*v?(\d+(?:\.\d+)*(?:[a-zA-Z0-9.+!-]*))$/;
|
|
12869
|
+
EXACT_VERSION_RE = /^=?v?(\d+(?:\.\d+)*(?:[-+][0-9A-Za-z][0-9A-Za-z.+-]*)?)$/;
|
|
12870
|
+
GEM_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12871
|
+
GEM_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12872
|
+
"-v",
|
|
12873
|
+
"--version",
|
|
12874
|
+
"-i",
|
|
12875
|
+
"--install-dir",
|
|
12876
|
+
"-n",
|
|
12877
|
+
"--bindir",
|
|
12878
|
+
"--document",
|
|
12879
|
+
"--build-root",
|
|
12880
|
+
"-P",
|
|
12881
|
+
"--trust-policy",
|
|
12882
|
+
"-s",
|
|
12883
|
+
"--source",
|
|
12884
|
+
"-p",
|
|
12885
|
+
"--http-proxy",
|
|
12886
|
+
"--platform",
|
|
12887
|
+
"-g",
|
|
12888
|
+
"--file",
|
|
12889
|
+
"--without"
|
|
12890
|
+
]);
|
|
12891
|
+
BUNDLE_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12892
|
+
"-v",
|
|
12893
|
+
"--version",
|
|
12894
|
+
"-g",
|
|
12895
|
+
"--group",
|
|
12896
|
+
"-s",
|
|
12897
|
+
"--source",
|
|
12898
|
+
"-r",
|
|
12899
|
+
"--require",
|
|
12900
|
+
"--path",
|
|
12901
|
+
"--git",
|
|
12902
|
+
"--github",
|
|
12903
|
+
"--branch",
|
|
12904
|
+
"--ref",
|
|
12905
|
+
"--glob"
|
|
12906
|
+
]);
|
|
12907
|
+
CRATE_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*$/u;
|
|
12908
|
+
CARGO_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12909
|
+
"-F",
|
|
12910
|
+
"--features",
|
|
12911
|
+
"--rename",
|
|
12912
|
+
"--registry",
|
|
12913
|
+
"--manifest-path",
|
|
12914
|
+
"-p",
|
|
12915
|
+
"--package",
|
|
12916
|
+
"--git",
|
|
12917
|
+
"--branch",
|
|
12918
|
+
"--tag",
|
|
12919
|
+
"--rev",
|
|
12920
|
+
"--path",
|
|
12921
|
+
"--base",
|
|
12922
|
+
"--target",
|
|
12923
|
+
"--color",
|
|
12924
|
+
"--config",
|
|
12925
|
+
"-Z",
|
|
12926
|
+
"--lockfile-path"
|
|
12927
|
+
]);
|
|
12928
|
+
CARGO_INSTALL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12929
|
+
"--version",
|
|
12930
|
+
"--vers",
|
|
12931
|
+
"--git",
|
|
12932
|
+
"--branch",
|
|
12933
|
+
"--tag",
|
|
12934
|
+
"--rev",
|
|
12935
|
+
"--path",
|
|
12936
|
+
"--root",
|
|
12937
|
+
"--index",
|
|
12938
|
+
"--registry",
|
|
12939
|
+
"-F",
|
|
12940
|
+
"--features",
|
|
12941
|
+
"--profile",
|
|
12942
|
+
"--target",
|
|
12943
|
+
"--target-dir",
|
|
12944
|
+
"-j",
|
|
12945
|
+
"--jobs",
|
|
12946
|
+
"--bin",
|
|
12947
|
+
"--example",
|
|
12948
|
+
"--color",
|
|
12949
|
+
"--config",
|
|
12950
|
+
"-Z"
|
|
12951
|
+
]);
|
|
12952
|
+
GO_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12953
|
+
"-C",
|
|
12954
|
+
"-tags",
|
|
12955
|
+
"-ldflags",
|
|
12956
|
+
"-gcflags",
|
|
12957
|
+
"-asmflags",
|
|
12958
|
+
"-mod",
|
|
12959
|
+
"-modfile",
|
|
12960
|
+
"-o",
|
|
12961
|
+
"-p",
|
|
12962
|
+
"-pkgdir",
|
|
12963
|
+
"-toolexec",
|
|
12964
|
+
"-exec",
|
|
12965
|
+
"-overlay",
|
|
12966
|
+
"-pgo",
|
|
12967
|
+
"-buildmode",
|
|
12968
|
+
"-compiler",
|
|
12969
|
+
"-gccgoflags",
|
|
12970
|
+
"-installsuffix",
|
|
12971
|
+
"-covermode",
|
|
12972
|
+
"-coverpkg"
|
|
12973
|
+
]);
|
|
12974
|
+
GO_PATH_RE = /^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)+(?:\/[\p{L}\p{N}_.~+-]+)*$/u;
|
|
12975
|
+
NUGET_NAME_RE = /^[\p{L}\p{N}_][\p{L}\p{N}_.-]*$/u;
|
|
12976
|
+
DOTNET_ADD_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12977
|
+
"-v",
|
|
12978
|
+
"--version",
|
|
12979
|
+
"-s",
|
|
12980
|
+
"--source",
|
|
12981
|
+
"-f",
|
|
12982
|
+
"--framework",
|
|
12983
|
+
"--package-directory"
|
|
12984
|
+
]);
|
|
12985
|
+
DOTNET_TOOL_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12986
|
+
"--version",
|
|
12987
|
+
"--add-source",
|
|
12988
|
+
"--configfile",
|
|
12989
|
+
"--framework",
|
|
12990
|
+
"--tool-path",
|
|
12991
|
+
"-a",
|
|
12992
|
+
"--arch",
|
|
12993
|
+
"--tool-manifest",
|
|
12994
|
+
"-v",
|
|
12995
|
+
"--verbosity"
|
|
12996
|
+
]);
|
|
12997
|
+
NUGET_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
12998
|
+
"-version",
|
|
12999
|
+
"-outputdirectory",
|
|
13000
|
+
"-o",
|
|
13001
|
+
"-source",
|
|
13002
|
+
"-configfile",
|
|
13003
|
+
"-fallbacksource",
|
|
13004
|
+
"-framework",
|
|
13005
|
+
"-packagesavemode",
|
|
13006
|
+
"-dependencyversion",
|
|
13007
|
+
"-solutiondirectory",
|
|
13008
|
+
"-verbosity"
|
|
13009
|
+
]);
|
|
13010
|
+
MAVEN_COORD_RE = /^[\p{L}\p{N}_.-]+:[\p{L}\p{N}_.-]+$/u;
|
|
13011
|
+
COMPOSER_NAME_RE = /^[\p{L}\p{N}][\p{L}\p{N}_.-]*\/[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
13012
|
+
COMPOSER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
13013
|
+
"-d",
|
|
13014
|
+
"--working-dir",
|
|
13015
|
+
"--stability",
|
|
13016
|
+
"--repository",
|
|
13017
|
+
"--add-repository"
|
|
13018
|
+
]);
|
|
13019
|
+
EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
|
|
13020
|
+
EXTENSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}_-]*\.[\p{L}\p{N}][\p{L}\p{N}_.-]*$/u;
|
|
12196
13021
|
MANAGER_HEADS = /* @__PURE__ */ new Set([
|
|
13022
|
+
"gem",
|
|
13023
|
+
"bundle",
|
|
13024
|
+
"cargo",
|
|
13025
|
+
"go",
|
|
13026
|
+
"dotnet",
|
|
13027
|
+
"nuget",
|
|
13028
|
+
"mvn",
|
|
13029
|
+
"mvnw",
|
|
13030
|
+
"composer",
|
|
13031
|
+
...EDITOR_HEADS,
|
|
12197
13032
|
"npm",
|
|
12198
13033
|
"npx",
|
|
12199
13034
|
"pnpm",
|
|
@@ -12272,6 +13107,7 @@ zoo`.split("\n");
|
|
|
12272
13107
|
MAP_GOVERNED = /* @__PURE__ */ new Set([
|
|
12273
13108
|
"files.shell-startup",
|
|
12274
13109
|
"files.password-hashes",
|
|
13110
|
+
"files.system-dirs",
|
|
12275
13111
|
"commands.inline-exec",
|
|
12276
13112
|
"commands.eval-dynamic",
|
|
12277
13113
|
"commands.curl-pipe-shell",
|
|
@@ -12540,7 +13376,6 @@ zoo`.split("\n");
|
|
|
12540
13376
|
PII_CC16_RE = /\b(?:4\d{3}|5[1-5]\d{2}|6\d{3})[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/;
|
|
12541
13377
|
PII_CC15_RE = /\b3[47]\d{2}[-\s]?\d{6}[-\s]?\d{5}\b/;
|
|
12542
13378
|
PII_IBAN_RE = /\b[A-Z]{2}\d{2}(?:[A-Z0-9]|[ -][A-Z0-9]){11,30}\b/;
|
|
12543
|
-
NUMBER_LEAD_RE = /[\s:=;([{"'+-]/;
|
|
12544
13379
|
REALTIME_PII_PATTERNS = ["SSN", "Credit Card", "IBAN"];
|
|
12545
13380
|
MAX_PII_SCAN_BYTES = 1e5;
|
|
12546
13381
|
MASKED_KINDS = /* @__PURE__ */ new Set(["email", "iban", "account"]);
|
|
@@ -12762,7 +13597,7 @@ zoo`.split("\n");
|
|
|
12762
13597
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
12763
13598
|
];
|
|
12764
13599
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
12765
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
13600
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v28";
|
|
12766
13601
|
DEDUPE_PREVIEW_LEN = 120;
|
|
12767
13602
|
ENGINE_VERSION = "1.4.0";
|
|
12768
13603
|
}
|
|
@@ -12927,6 +13762,9 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
12927
13762
|
...transcriptPathField,
|
|
12928
13763
|
...taintFields,
|
|
12929
13764
|
...exposureFields,
|
|
13765
|
+
...meta?.decidedBy && { decidedBy: meta.decidedBy },
|
|
13766
|
+
...meta?.channel && { channel: meta.channel },
|
|
13767
|
+
...meta?.cardId && { cardId: meta.cardId },
|
|
12930
13768
|
...testRun,
|
|
12931
13769
|
agent: meta?.agent,
|
|
12932
13770
|
mcpServer: meta?.mcpServer,
|
|
@@ -16014,8 +16852,204 @@ var init_state = __esm({
|
|
|
16014
16852
|
}
|
|
16015
16853
|
});
|
|
16016
16854
|
|
|
16855
|
+
// src/auth/decision-record.ts
|
|
16856
|
+
function personRecord(channel, decision, extra = {}, now = Date.now()) {
|
|
16857
|
+
return {
|
|
16858
|
+
decision,
|
|
16859
|
+
decidedBy: "person",
|
|
16860
|
+
channel,
|
|
16861
|
+
label: extra.redirect ? REDIRECT_LABEL : PERSON_LABELS[channel],
|
|
16862
|
+
...extra,
|
|
16863
|
+
decidedAt: now
|
|
16864
|
+
};
|
|
16865
|
+
}
|
|
16866
|
+
function timerRecord(timeoutMs, now = Date.now()) {
|
|
16867
|
+
return {
|
|
16868
|
+
decision: "deny",
|
|
16869
|
+
decidedBy: "timer",
|
|
16870
|
+
label: TIMER_LABEL,
|
|
16871
|
+
reason: `No response within ${Math.round(timeoutMs / 1e3)}s: auto-denied by the approval timeout.`,
|
|
16872
|
+
decidedAt: now
|
|
16873
|
+
};
|
|
16874
|
+
}
|
|
16875
|
+
function abandonedRecord(now = Date.now()) {
|
|
16876
|
+
return {
|
|
16877
|
+
decision: "deny",
|
|
16878
|
+
decidedBy: "abandoned",
|
|
16879
|
+
label: ABANDONED_LABEL,
|
|
16880
|
+
reason: "No approver was left to answer: node9 tail was closed.",
|
|
16881
|
+
decidedAt: now
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
function boundedString(v, max) {
|
|
16885
|
+
return typeof v === "string" && v.length > 0 ? v.slice(0, max) : void 0;
|
|
16886
|
+
}
|
|
16887
|
+
function parseDecisionRecord(x) {
|
|
16888
|
+
if (!x || typeof x !== "object") return null;
|
|
16889
|
+
const r = x;
|
|
16890
|
+
if (r.decision !== "allow" && r.decision !== "deny") return null;
|
|
16891
|
+
if (typeof r.decidedBy !== "string" || !DECIDED_BY.has(r.decidedBy)) return null;
|
|
16892
|
+
const label3 = boundedString(r.label, 200);
|
|
16893
|
+
if (!label3) return null;
|
|
16894
|
+
const channel = typeof r.channel === "string" && CHANNELS.includes(r.channel) ? r.channel : void 0;
|
|
16895
|
+
if (r.decidedBy === "person" && (!channel || !PERSON_CHANNELS.has(channel))) return null;
|
|
16896
|
+
const out = {
|
|
16897
|
+
decision: r.decision,
|
|
16898
|
+
decidedBy: r.decidedBy,
|
|
16899
|
+
label: label3,
|
|
16900
|
+
decidedAt: typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt) ? r.decidedAt : 0
|
|
16901
|
+
};
|
|
16902
|
+
if (channel) out.channel = channel;
|
|
16903
|
+
const redirect = boundedString(r.redirect, 2e3);
|
|
16904
|
+
if (redirect) out.redirect = redirect;
|
|
16905
|
+
const reason = boundedString(r.reason, 2e3);
|
|
16906
|
+
if (reason) out.reason = reason;
|
|
16907
|
+
const ruleName = boundedString(r.ruleName, 256);
|
|
16908
|
+
if (ruleName) out.ruleName = ruleName;
|
|
16909
|
+
if (typeof r.standing === "string" && STANDING.has(r.standing))
|
|
16910
|
+
out.standing = r.standing;
|
|
16911
|
+
const trustDuration = boundedString(r.trustDuration, 8);
|
|
16912
|
+
if (trustDuration) out.trustDuration = trustDuration;
|
|
16913
|
+
return out;
|
|
16914
|
+
}
|
|
16915
|
+
function legacyWaitFields(record2) {
|
|
16916
|
+
switch (record2.decidedBy) {
|
|
16917
|
+
case "person": {
|
|
16918
|
+
const source = record2.redirect ? "terminal-redirect" : record2.channel === "native" ? "native" : "terminal";
|
|
16919
|
+
const reason = record2.redirect ?? record2.reason;
|
|
16920
|
+
return { decision: record2.decision, source, ...reason && { reason } };
|
|
16921
|
+
}
|
|
16922
|
+
case "timer":
|
|
16923
|
+
return {
|
|
16924
|
+
decision: record2.decision,
|
|
16925
|
+
source: "timeout",
|
|
16926
|
+
...record2.reason && { reason: record2.reason }
|
|
16927
|
+
};
|
|
16928
|
+
case "cloud":
|
|
16929
|
+
case "policy":
|
|
16930
|
+
return {
|
|
16931
|
+
decision: record2.decision,
|
|
16932
|
+
source: record2.decidedBy,
|
|
16933
|
+
...record2.reason && { reason: record2.reason }
|
|
16934
|
+
};
|
|
16935
|
+
case "abandoned":
|
|
16936
|
+
return { decision: "abandoned" };
|
|
16937
|
+
}
|
|
16938
|
+
}
|
|
16939
|
+
function recordFromLegacyWait(body, now = Date.now()) {
|
|
16940
|
+
if (body.decision === "abandoned") return abandonedRecord(now);
|
|
16941
|
+
const decision = body.decision;
|
|
16942
|
+
switch (body.source) {
|
|
16943
|
+
case "terminal":
|
|
16944
|
+
case "browser":
|
|
16945
|
+
return personRecord("terminal", decision, body.reason ? { reason: body.reason } : {}, now);
|
|
16946
|
+
case "native":
|
|
16947
|
+
return personRecord("native", decision, {}, now);
|
|
16948
|
+
case "terminal-redirect":
|
|
16949
|
+
return personRecord("terminal", decision, body.reason ? { redirect: body.reason } : {}, now);
|
|
16950
|
+
case "timeout":
|
|
16951
|
+
return { ...timerRecord(0, now), ...body.reason && { reason: body.reason } };
|
|
16952
|
+
case "cloud":
|
|
16953
|
+
return {
|
|
16954
|
+
decision,
|
|
16955
|
+
decidedBy: "cloud",
|
|
16956
|
+
label: CLOUD_LABEL,
|
|
16957
|
+
...body.reason && { reason: body.reason },
|
|
16958
|
+
decidedAt: now
|
|
16959
|
+
};
|
|
16960
|
+
case "policy":
|
|
16961
|
+
return {
|
|
16962
|
+
decision,
|
|
16963
|
+
decidedBy: "policy",
|
|
16964
|
+
label: "Policy",
|
|
16965
|
+
...body.reason && { reason: body.reason },
|
|
16966
|
+
decidedAt: now
|
|
16967
|
+
};
|
|
16968
|
+
default:
|
|
16969
|
+
return null;
|
|
16970
|
+
}
|
|
16971
|
+
}
|
|
16972
|
+
function recordFromVerdict(v, now = Date.now()) {
|
|
16973
|
+
const decidedBy = v.checkedBy === "cloud" || v.blockedBy === "team-policy" ? "cloud" : v.blockedBy === "timeout" ? "timer" : "policy";
|
|
16974
|
+
return {
|
|
16975
|
+
decision: v.approved ? "allow" : "deny",
|
|
16976
|
+
decidedBy,
|
|
16977
|
+
label: v.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : v.approved ? "Allowed by policy" : "Blocked by policy"),
|
|
16978
|
+
...v.reason && { reason: v.reason },
|
|
16979
|
+
...v.ruleHit && { ruleName: v.ruleHit },
|
|
16980
|
+
decidedAt: now
|
|
16981
|
+
};
|
|
16982
|
+
}
|
|
16983
|
+
function channelFromDecisionSource(source) {
|
|
16984
|
+
switch (source) {
|
|
16985
|
+
case void 0:
|
|
16986
|
+
case "terminal":
|
|
16987
|
+
case "browser":
|
|
16988
|
+
return { channel: "terminal", redirect: false };
|
|
16989
|
+
case "terminal-redirect":
|
|
16990
|
+
return { channel: "terminal", redirect: true };
|
|
16991
|
+
case "monitor":
|
|
16992
|
+
case "dashboard":
|
|
16993
|
+
return { channel: "monitor", redirect: false };
|
|
16994
|
+
case "native":
|
|
16995
|
+
return { channel: "native", redirect: false };
|
|
16996
|
+
default:
|
|
16997
|
+
return null;
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
function recordFromLegacyResolve(decision, source, now = Date.now()) {
|
|
17001
|
+
const person = source === void 0 ? null : channelFromDecisionSource(source);
|
|
17002
|
+
if (person) return personRecord(person.channel, decision, {}, now);
|
|
17003
|
+
if (decision === "allow")
|
|
17004
|
+
return { decision, decidedBy: "cloud", label: CLOUD_LABEL, decidedAt: now };
|
|
17005
|
+
return {
|
|
17006
|
+
decision,
|
|
17007
|
+
decidedBy: "timer",
|
|
17008
|
+
label: "Resolved by the hook",
|
|
17009
|
+
reason: "An older node9 hook ended the approval without naming who decided.",
|
|
17010
|
+
decidedAt: now
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
var CHANNELS, LOCAL_UI_CHANNELS, PERSON_CHANNELS, CARD_PROTOCOL, PERSON_LABELS, REDIRECT_LABEL, TIMER_LABEL, CLOUD_LABEL, ABANDONED_LABEL, DECIDED_BY, STANDING;
|
|
17014
|
+
var init_decision_record = __esm({
|
|
17015
|
+
"src/auth/decision-record.ts"() {
|
|
17016
|
+
"use strict";
|
|
17017
|
+
CHANNELS = ["terminal", "monitor", "native", "cloud"];
|
|
17018
|
+
LOCAL_UI_CHANNELS = /* @__PURE__ */ new Set(["terminal", "monitor"]);
|
|
17019
|
+
PERSON_CHANNELS = /* @__PURE__ */ new Set([
|
|
17020
|
+
"terminal",
|
|
17021
|
+
"monitor",
|
|
17022
|
+
"native"
|
|
17023
|
+
]);
|
|
17024
|
+
CARD_PROTOCOL = 2;
|
|
17025
|
+
PERSON_LABELS = {
|
|
17026
|
+
terminal: "User Decision (Terminal (node9 tail))",
|
|
17027
|
+
monitor: "User Decision (node9 monitor)",
|
|
17028
|
+
native: "User Decision (Native)"
|
|
17029
|
+
};
|
|
17030
|
+
REDIRECT_LABEL = "Steered Redirect (Terminal)";
|
|
17031
|
+
TIMER_LABEL = "Approval Timeout";
|
|
17032
|
+
CLOUD_LABEL = "Organization Policy (SaaS)";
|
|
17033
|
+
ABANDONED_LABEL = "No Approver (node9 tail closed)";
|
|
17034
|
+
DECIDED_BY = /* @__PURE__ */ new Set([
|
|
17035
|
+
"person",
|
|
17036
|
+
"timer",
|
|
17037
|
+
"cloud",
|
|
17038
|
+
"policy",
|
|
17039
|
+
"abandoned"
|
|
17040
|
+
]);
|
|
17041
|
+
STANDING = /* @__PURE__ */ new Set([
|
|
17042
|
+
"always-allow",
|
|
17043
|
+
"always-deny",
|
|
17044
|
+
"trust",
|
|
17045
|
+
"trust-recipients"
|
|
17046
|
+
]);
|
|
17047
|
+
}
|
|
17048
|
+
});
|
|
17049
|
+
|
|
16017
17050
|
// src/auth/daemon.ts
|
|
16018
17051
|
import fs11 from "fs";
|
|
17052
|
+
import http from "http";
|
|
16019
17053
|
import net from "net";
|
|
16020
17054
|
import path11 from "path";
|
|
16021
17055
|
import os9 from "os";
|
|
@@ -16134,7 +17168,7 @@ async function daemonHasInteractiveApprover(timeoutMs = 400) {
|
|
|
16134
17168
|
return false;
|
|
16135
17169
|
}
|
|
16136
17170
|
}
|
|
16137
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId) {
|
|
17171
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent, sessionId, card) {
|
|
16138
17172
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16139
17173
|
const ctrl = new AbortController();
|
|
16140
17174
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -16160,19 +17194,43 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
16160
17194
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
16161
17195
|
...viewOnly && { viewOnly: true },
|
|
16162
17196
|
...localSmartRuleMatched && { localSmartRuleMatched: true },
|
|
16163
|
-
...sessionId && { sessionId }
|
|
17197
|
+
...sessionId && { sessionId },
|
|
17198
|
+
...cardFields(card)
|
|
16164
17199
|
}),
|
|
16165
17200
|
signal: ctrl.signal
|
|
16166
17201
|
});
|
|
16167
17202
|
if (!res.ok) throw new Error("Daemon fail");
|
|
16168
|
-
|
|
16169
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
17203
|
+
return registeredCard(await res.json());
|
|
16170
17204
|
} finally {
|
|
16171
17205
|
clearTimeout(timer);
|
|
16172
17206
|
}
|
|
16173
17207
|
}
|
|
17208
|
+
function cardFields(card) {
|
|
17209
|
+
return {
|
|
17210
|
+
// A review card: the hook already decided this call needs a person; the
|
|
17211
|
+
// daemon never checks it again (approval-card design, defect 1).
|
|
17212
|
+
kind: "review",
|
|
17213
|
+
// Protocol 2 makes the card's /wait require the internal token: claim it
|
|
17214
|
+
// only when this process can read the token (a daemon under another uid
|
|
17215
|
+
// is running but its pid file probe fails with EPERM), or every wait
|
|
17216
|
+
// would get 403 and the review would be denied unseen.
|
|
17217
|
+
...getInternalToken() && { protocol: CARD_PROTOCOL },
|
|
17218
|
+
...card?.channels && { channels: card.channels },
|
|
17219
|
+
...typeof card?.timeoutMs === "number" && { timeoutMs: card.timeoutMs }
|
|
17220
|
+
};
|
|
17221
|
+
}
|
|
17222
|
+
function registeredCard(body) {
|
|
17223
|
+
const b = body;
|
|
17224
|
+
return {
|
|
17225
|
+
id: b.id,
|
|
17226
|
+
allowCount: b.allowCount ?? 1,
|
|
17227
|
+
...b.protocol === CARD_PROTOCOL && {
|
|
17228
|
+
protocol: CARD_PROTOCOL,
|
|
17229
|
+
deadline: typeof b.deadline === "number" ? b.deadline : null
|
|
17230
|
+
}
|
|
17231
|
+
};
|
|
17232
|
+
}
|
|
16174
17233
|
async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
16175
|
-
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16176
17234
|
const waitCtrl = new AbortController();
|
|
16177
17235
|
let capped = false;
|
|
16178
17236
|
const waitTimer = maxWaitMs > 0 ? setTimeout(() => {
|
|
@@ -16182,14 +17240,18 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
16182
17240
|
const onAbort = () => waitCtrl.abort();
|
|
16183
17241
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
16184
17242
|
try {
|
|
16185
|
-
const
|
|
17243
|
+
const { status, body } = await daemonGet(`/wait/${id}`, waitCtrl.signal).catch(
|
|
16186
17244
|
(err2) => {
|
|
16187
17245
|
if (capped && !signal?.aborted) throw new Error("node9 daemon did not answer in time");
|
|
16188
17246
|
throw err2;
|
|
16189
17247
|
}
|
|
16190
17248
|
);
|
|
16191
|
-
if (
|
|
16192
|
-
|
|
17249
|
+
if (status === 409) throw new Error("node9 card was withdrawn");
|
|
17250
|
+
if (status !== 200) return { decision: "deny" };
|
|
17251
|
+
const parsed = JSON.parse(body);
|
|
17252
|
+
const record2 = parseDecisionRecord(parsed.record);
|
|
17253
|
+
if (record2) return { ...legacyWaitFields(record2), record: record2 };
|
|
17254
|
+
const { decision, source, reason } = parsed;
|
|
16193
17255
|
if (decision === "allow") return { decision: "allow", source };
|
|
16194
17256
|
if (decision === "abandoned") return { decision: "abandoned", source };
|
|
16195
17257
|
return { decision: "deny", source, reason };
|
|
@@ -16198,7 +17260,29 @@ async function waitForDaemonDecision(id, signal, maxWaitMs = 12e4) {
|
|
|
16198
17260
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
16199
17261
|
}
|
|
16200
17262
|
}
|
|
16201
|
-
|
|
17263
|
+
function daemonGet(urlPath, signal, token = getInternalToken()) {
|
|
17264
|
+
return new Promise((resolve2, reject) => {
|
|
17265
|
+
const req = http.get(
|
|
17266
|
+
{
|
|
17267
|
+
host: DAEMON_HOST,
|
|
17268
|
+
port: DAEMON_PORT,
|
|
17269
|
+
path: urlPath,
|
|
17270
|
+
signal,
|
|
17271
|
+
agent: false,
|
|
17272
|
+
headers: token ? { "X-Node9-Internal": token } : {}
|
|
17273
|
+
},
|
|
17274
|
+
(res) => {
|
|
17275
|
+
let body = "";
|
|
17276
|
+
res.setEncoding("utf8");
|
|
17277
|
+
res.on("data", (chunk2) => body += chunk2);
|
|
17278
|
+
res.on("end", () => resolve2({ status: res.statusCode ?? 0, body }));
|
|
17279
|
+
res.on("error", reject);
|
|
17280
|
+
}
|
|
17281
|
+
);
|
|
17282
|
+
req.on("error", reject);
|
|
17283
|
+
});
|
|
17284
|
+
}
|
|
17285
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent, extra) {
|
|
16202
17286
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16203
17287
|
const res = await fetch(`${base2}/check`, {
|
|
16204
17288
|
method: "POST",
|
|
@@ -16214,13 +17298,16 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId
|
|
|
16214
17298
|
// event via socket. Same contract as registerDaemonEntry — without
|
|
16215
17299
|
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
16216
17300
|
fromCLI: socketActivitySent !== false,
|
|
16217
|
-
activityId
|
|
17301
|
+
activityId,
|
|
17302
|
+
...extra?.cwd && { cwd: extra.cwd },
|
|
17303
|
+
...extra?.recoveryCommand && { recoveryCommand: extra.recoveryCommand },
|
|
17304
|
+
...extra?.sessionId && { sessionId: extra.sessionId },
|
|
17305
|
+
...cardFields(extra?.card)
|
|
16218
17306
|
}),
|
|
16219
17307
|
signal: AbortSignal.timeout(3e3)
|
|
16220
17308
|
});
|
|
16221
17309
|
if (!res.ok) throw new Error("Daemon unreachable");
|
|
16222
|
-
|
|
16223
|
-
return { id, allowCount: allowCount ?? 1 };
|
|
17310
|
+
return registeredCard(await res.json());
|
|
16224
17311
|
}
|
|
16225
17312
|
async function notifyTaint(filePath, source, fromEid) {
|
|
16226
17313
|
if (!isDaemonRunning()) return;
|
|
@@ -16404,19 +17491,24 @@ async function clearSessionExposure(opts) {
|
|
|
16404
17491
|
return { ok: false, cleared: 0, daemonUnavailable: true };
|
|
16405
17492
|
}
|
|
16406
17493
|
}
|
|
16407
|
-
async function resolveViaDaemon(id, decision, internalToken, source) {
|
|
17494
|
+
async function resolveViaDaemon(id, decision, internalToken, source, record2) {
|
|
16408
17495
|
const base2 = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
16409
|
-
await fetch(`${base2}/resolve/${id}`, {
|
|
17496
|
+
const res = await fetch(`${base2}/resolve/${id}`, {
|
|
16410
17497
|
method: "POST",
|
|
16411
17498
|
headers: { "Content-Type": "application/json", "X-Node9-Internal": internalToken },
|
|
16412
|
-
body: JSON.stringify({ decision, ...source && { source } }),
|
|
17499
|
+
body: JSON.stringify({ decision, ...source && { source }, ...record2 && { record: record2 } }),
|
|
16413
17500
|
signal: AbortSignal.timeout(3e3)
|
|
16414
17501
|
});
|
|
17502
|
+
if (!res.ok) return null;
|
|
17503
|
+
const body = await res.json().catch(() => null);
|
|
17504
|
+
const winner = parseDecisionRecord(body?.record);
|
|
17505
|
+
return winner && typeof body?.won === "boolean" ? { won: body.won, record: winner } : null;
|
|
16415
17506
|
}
|
|
16416
17507
|
var ACTIVITY_SOCKET_PATH, DEFAULT_DAEMON_PORT, DAEMON_PORT, DAEMON_HOST;
|
|
16417
17508
|
var init_daemon = __esm({
|
|
16418
17509
|
"src/auth/daemon.ts"() {
|
|
16419
17510
|
"use strict";
|
|
17511
|
+
init_decision_record();
|
|
16420
17512
|
ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path11.join(os9.tmpdir(), "node9-activity.sock");
|
|
16421
17513
|
DEFAULT_DAEMON_PORT = 7391;
|
|
16422
17514
|
DAEMON_PORT = resolveDaemonPort();
|
|
@@ -18122,7 +19214,20 @@ function ecoDir(eco) {
|
|
|
18122
19214
|
return path24.join(osvRoot(), eco);
|
|
18123
19215
|
}
|
|
18124
19216
|
function indexKey(eco, name) {
|
|
18125
|
-
|
|
19217
|
+
switch (eco) {
|
|
19218
|
+
case "PyPI":
|
|
19219
|
+
return normalizePyPiName(name);
|
|
19220
|
+
case "crates.io":
|
|
19221
|
+
return name.toLowerCase().replace(/_/g, "-");
|
|
19222
|
+
case "Go":
|
|
19223
|
+
case "Maven":
|
|
19224
|
+
return name;
|
|
19225
|
+
default:
|
|
19226
|
+
return name.toLowerCase();
|
|
19227
|
+
}
|
|
19228
|
+
}
|
|
19229
|
+
function recordEcosystemMatches(eco, recordEco) {
|
|
19230
|
+
return typeof recordEco === "string" && (recordEco === eco || recordEco.startsWith(`${eco}:`));
|
|
18126
19231
|
}
|
|
18127
19232
|
function shardOf(key) {
|
|
18128
19233
|
return createHash5("sha1").update(key).digest("hex").slice(0, 2);
|
|
@@ -18168,7 +19273,8 @@ function entriesFromRecord(eco, record2) {
|
|
|
18168
19273
|
const modifiedMs = typeof r.modified === "string" ? Date.parse(r.modified) || 0 : 0;
|
|
18169
19274
|
const rows = /* @__PURE__ */ new Map();
|
|
18170
19275
|
for (const a of r.affected ?? []) {
|
|
18171
|
-
if (a?.package?.ecosystem
|
|
19276
|
+
if (!recordEcosystemMatches(eco, a?.package?.ecosystem) || typeof a?.package?.name !== "string")
|
|
19277
|
+
continue;
|
|
18172
19278
|
const key = indexKey(eco, a.package.name);
|
|
18173
19279
|
const entry = rows.get(key) ?? { id: r.id };
|
|
18174
19280
|
if (Array.isArray(a.versions)) {
|
|
@@ -18408,15 +19514,18 @@ async function pypiInfo(name, version2) {
|
|
|
18408
19514
|
};
|
|
18409
19515
|
}
|
|
18410
19516
|
async function registryInfo(eco, name, version2, freshWindowMs, raw) {
|
|
18411
|
-
|
|
19517
|
+
if (eco === "npm") return npmInfo(name, version2, freshWindowMs, raw);
|
|
19518
|
+
if (eco === "PyPI") return pypiInfo(name, version2);
|
|
19519
|
+
return null;
|
|
18412
19520
|
}
|
|
18413
|
-
var TIMEOUT_MS2, MAX_BYTES;
|
|
19521
|
+
var TIMEOUT_MS2, MAX_BYTES, HAS_REGISTRY_CLIENT;
|
|
18414
19522
|
var init_registry2 = __esm({
|
|
18415
19523
|
"src/supply-chain/registry.ts"() {
|
|
18416
19524
|
"use strict";
|
|
18417
19525
|
init_net();
|
|
18418
19526
|
TIMEOUT_MS2 = 1500;
|
|
18419
19527
|
MAX_BYTES = 2 * 1024 * 1024;
|
|
19528
|
+
HAS_REGISTRY_CLIENT = /* @__PURE__ */ new Set(["npm", "PyPI"]);
|
|
18420
19529
|
}
|
|
18421
19530
|
});
|
|
18422
19531
|
|
|
@@ -18463,25 +19572,39 @@ function commandOf(toolName, args) {
|
|
|
18463
19572
|
return typeof a.command === "string" ? a.command : typeof a.cmd === "string" ? a.cmd : null;
|
|
18464
19573
|
}
|
|
18465
19574
|
function label(p, version2) {
|
|
18466
|
-
return `${p.ecosystem
|
|
19575
|
+
return `${KIND[p.ecosystem]} "${p.name}${version2 ? `@${version2}` : ""}"`;
|
|
19576
|
+
}
|
|
19577
|
+
function osvNames(p) {
|
|
19578
|
+
if (p.ecosystem !== "Go") return [p.name];
|
|
19579
|
+
const parts = p.name.split("/");
|
|
19580
|
+
const out = [];
|
|
19581
|
+
for (let n = parts.length; n >= 2 && out.length < 6; n--) out.push(parts.slice(0, n).join("/"));
|
|
19582
|
+
return out;
|
|
18467
19583
|
}
|
|
18468
19584
|
async function maliciousFor(p, version2, cfg, misses, networkCapped) {
|
|
18469
|
-
const
|
|
18470
|
-
const
|
|
18471
|
-
|
|
19585
|
+
const names = osvNames(p);
|
|
19586
|
+
const local = names.map((n) => lookupIndex(p.ecosystem, n));
|
|
19587
|
+
const hits2 = local.flatMap((l) => l.status === "hit" ? l.entries : []);
|
|
19588
|
+
const entries = hits2.length > 0 ? hits2 : null;
|
|
19589
|
+
const freshClean = local.every((l) => l.status === "clean" && l.fresh);
|
|
19590
|
+
const status = local.some((l) => l.status === "unavailable") ? "unavailable" : local[0].status;
|
|
19591
|
+
if (!entries && !freshClean) {
|
|
18472
19592
|
if (!cfg.onlineFallback || networkCapped) {
|
|
18473
19593
|
misses.push(
|
|
18474
|
-
`${label(p, version2)}: local index ${
|
|
19594
|
+
`${label(p, version2)}: local index ${status}, no online lookup (${networkCapped ? "over the per-command network cap" : "online fallback off"})`
|
|
18475
19595
|
);
|
|
18476
19596
|
return null;
|
|
18477
19597
|
}
|
|
18478
|
-
let
|
|
19598
|
+
let perName = null;
|
|
18479
19599
|
try {
|
|
18480
|
-
|
|
19600
|
+
perName = await queryOsvMalicious(
|
|
19601
|
+
names.map((name) => ({ ecosystem: p.ecosystem, name, version: version2 }))
|
|
19602
|
+
);
|
|
18481
19603
|
} catch (err2) {
|
|
18482
19604
|
misses.push(`${label(p, version2)}: OSV online ${err2.message}`);
|
|
18483
19605
|
return null;
|
|
18484
19606
|
}
|
|
19607
|
+
const ids = perName ? [[...new Set(perName.flat())]] : null;
|
|
18485
19608
|
if (!ids) {
|
|
18486
19609
|
misses.push(`${label(p, version2)}: OSV online unavailable`);
|
|
18487
19610
|
return null;
|
|
@@ -18551,7 +19674,7 @@ async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
|
18551
19674
|
}
|
|
18552
19675
|
const findings = [];
|
|
18553
19676
|
let info = null;
|
|
18554
|
-
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
19677
|
+
const wantsRegistry = (cfg.newPackage !== "off" || cfg.installScript !== "off") && HAS_REGISTRY_CLIENT.has(p.ecosystem);
|
|
18555
19678
|
if (wantsRegistry && !networkCapped) {
|
|
18556
19679
|
try {
|
|
18557
19680
|
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
@@ -18631,7 +19754,7 @@ async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
|
18631
19754
|
return { ...ALLOW, misses: [`package check error: ${err2?.message ?? err2}`] };
|
|
18632
19755
|
}
|
|
18633
19756
|
}
|
|
18634
|
-
var MAX_NETWORK_PACKAGES, ALLOW;
|
|
19757
|
+
var MAX_NETWORK_PACKAGES, ALLOW, KIND;
|
|
18635
19758
|
var init_check = __esm({
|
|
18636
19759
|
"src/supply-chain/check.ts"() {
|
|
18637
19760
|
"use strict";
|
|
@@ -18642,11 +19765,29 @@ var init_check = __esm({
|
|
|
18642
19765
|
init_local_resolve();
|
|
18643
19766
|
MAX_NETWORK_PACKAGES = 10;
|
|
18644
19767
|
ALLOW = { verdict: "allow", findings: [], misses: [], checked: 0 };
|
|
19768
|
+
KIND = {
|
|
19769
|
+
npm: "npm package",
|
|
19770
|
+
PyPI: "PyPI package",
|
|
19771
|
+
RubyGems: "RubyGems package",
|
|
19772
|
+
"crates.io": "crates.io package",
|
|
19773
|
+
Go: "Go module",
|
|
19774
|
+
NuGet: "NuGet package",
|
|
19775
|
+
Maven: "Maven artifact",
|
|
19776
|
+
Packagist: "Packagist package",
|
|
19777
|
+
VSCode: "VS Code extension"
|
|
19778
|
+
};
|
|
18645
19779
|
}
|
|
18646
19780
|
});
|
|
18647
19781
|
|
|
18648
19782
|
// src/policy/negotiation.ts
|
|
18649
19783
|
function buildNegotiationMessage(blockedByLabel, isHumanDecision2, humanReason, recoveryCommand, context) {
|
|
19784
|
+
if (isHumanDecision2 && context?.redirect && humanReason) {
|
|
19785
|
+
return `NODE9: The user stopped this action and gave you an instruction.
|
|
19786
|
+
INSTRUCTION FROM THE USER: ${humanReason}
|
|
19787
|
+
INSTRUCTIONS:
|
|
19788
|
+
- Follow the user's instruction above.
|
|
19789
|
+
- Retry the original action only if the instruction says you may.`;
|
|
19790
|
+
}
|
|
18650
19791
|
if (isHumanDecision2) {
|
|
18651
19792
|
return `NODE9: The human user rejected this action.
|
|
18652
19793
|
REASON: ${humanReason || "No specific reason provided."}
|
|
@@ -18737,7 +19878,8 @@ function buildReviewMessage(blockedByLabel, ruleDescription, reason) {
|
|
|
18737
19878
|
const sentence2 = /[.!?\u2026]$/.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
18738
19879
|
return `Node9 flagged this for your review: ${sentence2} Approve to proceed, or deny to cancel.`;
|
|
18739
19880
|
}
|
|
18740
|
-
function isHumanDecision(label3, blockedBy) {
|
|
19881
|
+
function isHumanDecision(label3, blockedBy, decidedBy) {
|
|
19882
|
+
if (decidedBy !== void 0) return decidedBy === "person";
|
|
18741
19883
|
if (blockedBy !== void 0 && blockedBy !== "local-decision") return false;
|
|
18742
19884
|
const l = label3.toLowerCase();
|
|
18743
19885
|
return l.includes("user") || l.includes("daemon") || l.includes("decision");
|
|
@@ -18769,26 +19911,103 @@ var init_negotiation = __esm({
|
|
|
18769
19911
|
}
|
|
18770
19912
|
});
|
|
18771
19913
|
|
|
18772
|
-
// src/auth/
|
|
18773
|
-
function
|
|
18774
|
-
|
|
18775
|
-
|
|
19914
|
+
// src/auth/card-result.ts
|
|
19915
|
+
function authResultFromRecord(r, ctx = {}) {
|
|
19916
|
+
const who = { decidedBy: r.decidedBy, ...r.channel && { channel: r.channel } };
|
|
19917
|
+
switch (r.decidedBy) {
|
|
19918
|
+
case "person": {
|
|
19919
|
+
const channel = r.channel ?? "terminal";
|
|
19920
|
+
const decisionSource = channel === "native" ? "native" : "terminal";
|
|
19921
|
+
if (r.decision === "allow") {
|
|
19922
|
+
return { approved: true, checkedBy: "daemon", decisionSource, ...who };
|
|
19923
|
+
}
|
|
19924
|
+
return {
|
|
19925
|
+
approved: false,
|
|
19926
|
+
reason: r.redirect || `The human user rejected this action via the Node9 ${CHANNEL_NAMES[channel]}.`,
|
|
19927
|
+
blockedBy: "local-decision",
|
|
19928
|
+
blockedByLabel: r.label,
|
|
19929
|
+
decisionSource,
|
|
19930
|
+
...who,
|
|
19931
|
+
...r.redirect && { redirect: r.redirect }
|
|
19932
|
+
};
|
|
19933
|
+
}
|
|
19934
|
+
case "timer":
|
|
19935
|
+
if (r.decision === "deny" && ctx.downgradedBlock)
|
|
19936
|
+
return { ...ctx.downgradedBlock, blockedBy: "timeout", ...who };
|
|
19937
|
+
return r.decision === "allow" ? { approved: true, ...who } : {
|
|
19938
|
+
approved: false,
|
|
19939
|
+
reason: ctx.timeoutReason || r.reason || "No response: auto-denied after the approval timeout.",
|
|
19940
|
+
blockedBy: "timeout",
|
|
19941
|
+
blockedByLabel: TIMER_LABEL,
|
|
19942
|
+
...who
|
|
19943
|
+
};
|
|
19944
|
+
case "cloud":
|
|
19945
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "cloud", decisionSource: "cloud", ...who } : {
|
|
19946
|
+
approved: false,
|
|
19947
|
+
reason: r.reason || "Action rejected by organization administrator via Slack.",
|
|
19948
|
+
blockedBy: "team-policy",
|
|
19949
|
+
blockedByLabel: CLOUD_LABEL,
|
|
19950
|
+
decisionSource: "cloud",
|
|
19951
|
+
...who
|
|
19952
|
+
};
|
|
19953
|
+
case "policy":
|
|
19954
|
+
return r.decision === "allow" ? { approved: true, checkedBy: "local-policy", ...who } : {
|
|
19955
|
+
approved: false,
|
|
19956
|
+
reason: r.reason || "Action blocked by policy.",
|
|
19957
|
+
blockedBy: "local-config",
|
|
19958
|
+
blockedByLabel: r.label,
|
|
19959
|
+
...r.ruleName && { ruleHit: r.ruleName },
|
|
19960
|
+
...who
|
|
19961
|
+
};
|
|
19962
|
+
case "abandoned":
|
|
19963
|
+
return {
|
|
19964
|
+
approved: false,
|
|
19965
|
+
reason: r.reason || "No approver was left to answer.",
|
|
19966
|
+
blockedBy: "no-approval-mechanism",
|
|
19967
|
+
blockedByLabel: ABANDONED_LABEL,
|
|
19968
|
+
...who
|
|
19969
|
+
};
|
|
19970
|
+
}
|
|
19971
|
+
}
|
|
19972
|
+
function approvalUnavailable(reason) {
|
|
19973
|
+
return {
|
|
19974
|
+
approved: false,
|
|
19975
|
+
reason: reason || "node9 could not get an approval decision for this call.",
|
|
19976
|
+
blockedBy: "no-approval-mechanism",
|
|
19977
|
+
blockedByLabel: "Approval Unavailable"
|
|
19978
|
+
};
|
|
19979
|
+
}
|
|
19980
|
+
function decidedByOf(res) {
|
|
19981
|
+
if (res.decidedBy) return res.decidedBy;
|
|
19982
|
+
if (res.blockedBy === "timeout") return "timer";
|
|
19983
|
+
if (res.checkedBy === "cloud" || res.blockedBy === "team-policy") return "cloud";
|
|
18776
19984
|
return "policy";
|
|
18777
19985
|
}
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
19986
|
+
function recordFromAuthResult(res, now = Date.now()) {
|
|
19987
|
+
const decidedBy = decidedByOf(res);
|
|
19988
|
+
const channel = res.channel ?? (decidedBy === "cloud" ? "cloud" : decidedBy === "person" ? "native" : void 0);
|
|
19989
|
+
return {
|
|
19990
|
+
decision: res.approved ? "allow" : "deny",
|
|
19991
|
+
decidedBy,
|
|
19992
|
+
...channel && { channel },
|
|
19993
|
+
label: res.blockedByLabel || (decidedBy === "cloud" ? CLOUD_LABEL : decidedBy === "timer" ? TIMER_LABEL : res.approved ? "Allowed" : "Blocked"),
|
|
19994
|
+
...res.reason && { reason: res.reason.slice(0, 2e3) },
|
|
19995
|
+
...res.ruleHit && { ruleName: res.ruleHit },
|
|
19996
|
+
...res.redirect && { redirect: res.redirect },
|
|
19997
|
+
decidedAt: now
|
|
19998
|
+
};
|
|
19999
|
+
}
|
|
20000
|
+
var CHANNEL_NAMES;
|
|
20001
|
+
var init_card_result = __esm({
|
|
20002
|
+
"src/auth/card-result.ts"() {
|
|
18781
20003
|
"use strict";
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
"
|
|
18785
|
-
"
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
|
|
18789
|
-
...HUMAN_DECISION_SOURCES,
|
|
18790
|
-
REDIRECT_SOURCE
|
|
18791
|
-
]);
|
|
20004
|
+
init_decision_record();
|
|
20005
|
+
CHANNEL_NAMES = {
|
|
20006
|
+
terminal: "Terminal (node9 tail)",
|
|
20007
|
+
monitor: "node9 monitor",
|
|
20008
|
+
native: "system dialog window",
|
|
20009
|
+
cloud: "organization"
|
|
20010
|
+
};
|
|
18792
20011
|
}
|
|
18793
20012
|
});
|
|
18794
20013
|
|
|
@@ -19819,28 +21038,22 @@ ${packageReview}`;
|
|
|
19819
21038
|
const { signal } = abortController;
|
|
19820
21039
|
const racePromises = [];
|
|
19821
21040
|
const approvalTimeoutMs = config.settings.approvalTimeoutMs ?? 0;
|
|
19822
|
-
if (approvalTimeoutMs > 0) {
|
|
19823
|
-
racePromises.push(
|
|
19824
|
-
new Promise((resolve2, reject) => {
|
|
19825
|
-
const timer = setTimeout(() => {
|
|
19826
|
-
resolve2({
|
|
19827
|
-
approved: false,
|
|
19828
|
-
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
19829
|
-
blockedBy: "timeout",
|
|
19830
|
-
blockedByLabel: "Approval Timeout"
|
|
19831
|
-
});
|
|
19832
|
-
}, approvalTimeoutMs);
|
|
19833
|
-
signal.addEventListener("abort", () => {
|
|
19834
|
-
clearTimeout(timer);
|
|
19835
|
-
reject(new Error("Aborted"));
|
|
19836
|
-
});
|
|
19837
|
-
})
|
|
19838
|
-
);
|
|
19839
|
-
}
|
|
19840
21041
|
let viewerId = null;
|
|
19841
21042
|
const internalToken = getInternalToken();
|
|
19842
21043
|
let daemonEntryId = null;
|
|
19843
21044
|
let daemonAllowCount = 1;
|
|
21045
|
+
let cardDeadline;
|
|
21046
|
+
const cardOptions = {
|
|
21047
|
+
channels: [
|
|
21048
|
+
"terminal",
|
|
21049
|
+
"monitor",
|
|
21050
|
+
// Only when the popup can show: a card listing a channel nobody can
|
|
21051
|
+
// use is never abandoned when node9 tail closes, and waits unanswered.
|
|
21052
|
+
...approvers.native && !isManual && !options?.calledFromDaemon && (process.platform === "darwin" || process.platform === "win32" || !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY)) ? ["native"] : [],
|
|
21053
|
+
...cloudEnforced && cloudRequestId && cloudMayVote ? ["cloud"] : []
|
|
21054
|
+
],
|
|
21055
|
+
timeoutMs: approvalTimeoutMs
|
|
21056
|
+
};
|
|
19844
21057
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
19845
21058
|
if (cloudEnforced && cloudRequestId) {
|
|
19846
21059
|
const viewer = await notifyDaemonViewer(
|
|
@@ -19849,11 +21062,20 @@ ${packageReview}`;
|
|
|
19849
21062
|
meta,
|
|
19850
21063
|
riskMetadata,
|
|
19851
21064
|
options?.activityId,
|
|
19852
|
-
options?.socketActivitySent
|
|
21065
|
+
options?.socketActivitySent,
|
|
21066
|
+
{
|
|
21067
|
+
cwd: options?.cwd,
|
|
21068
|
+
recoveryCommand: statefulRecoveryCommand,
|
|
21069
|
+
sessionId: meta?.sessionId,
|
|
21070
|
+
card: cardOptions
|
|
21071
|
+
}
|
|
19853
21072
|
).catch(() => null);
|
|
19854
21073
|
viewerId = viewer?.id ?? null;
|
|
19855
21074
|
daemonEntryId = viewerId;
|
|
19856
|
-
if (viewer)
|
|
21075
|
+
if (viewer) {
|
|
21076
|
+
daemonAllowCount = viewer.allowCount;
|
|
21077
|
+
cardDeadline = viewer.deadline;
|
|
21078
|
+
}
|
|
19857
21079
|
} else {
|
|
19858
21080
|
try {
|
|
19859
21081
|
const entry = await registerDaemonEntry(
|
|
@@ -19877,14 +21099,35 @@ ${packageReview}`;
|
|
|
19877
21099
|
void 0,
|
|
19878
21100
|
localSmartRuleMatched || options?.localSmartRuleMatched,
|
|
19879
21101
|
options?.socketActivitySent,
|
|
19880
|
-
meta?.sessionId
|
|
21102
|
+
meta?.sessionId,
|
|
21103
|
+
cardOptions
|
|
19881
21104
|
);
|
|
19882
21105
|
daemonEntryId = entry.id;
|
|
19883
21106
|
daemonAllowCount = entry.allowCount;
|
|
21107
|
+
cardDeadline = entry.deadline;
|
|
19884
21108
|
} catch {
|
|
19885
21109
|
}
|
|
19886
21110
|
}
|
|
19887
21111
|
}
|
|
21112
|
+
if (approvalTimeoutMs > 0 && cardDeadline === void 0) {
|
|
21113
|
+
racePromises.push(
|
|
21114
|
+
new Promise((resolve2, reject) => {
|
|
21115
|
+
const timer = setTimeout(() => {
|
|
21116
|
+
resolve2({
|
|
21117
|
+
approved: false,
|
|
21118
|
+
reason: approvalTimeoutReason(approvalTimeoutMs),
|
|
21119
|
+
blockedBy: "timeout",
|
|
21120
|
+
blockedByLabel: "Approval Timeout",
|
|
21121
|
+
decidedBy: "timer"
|
|
21122
|
+
});
|
|
21123
|
+
}, approvalTimeoutMs);
|
|
21124
|
+
signal.addEventListener("abort", () => {
|
|
21125
|
+
clearTimeout(timer);
|
|
21126
|
+
reject(new Error("Aborted"));
|
|
21127
|
+
});
|
|
21128
|
+
})
|
|
21129
|
+
);
|
|
21130
|
+
}
|
|
19888
21131
|
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
19889
21132
|
racePromises.push(
|
|
19890
21133
|
(async () => {
|
|
@@ -19895,7 +21138,9 @@ ${packageReview}`;
|
|
|
19895
21138
|
reason: cloudResult.approved ? void 0 : cloudResult.reason || "Action rejected by organization administrator via Slack.",
|
|
19896
21139
|
checkedBy: cloudResult.approved ? "cloud" : void 0,
|
|
19897
21140
|
blockedBy: cloudResult.approved ? void 0 : "team-policy",
|
|
19898
|
-
blockedByLabel: "Organization Policy (SaaS)"
|
|
21141
|
+
blockedByLabel: "Organization Policy (SaaS)",
|
|
21142
|
+
decidedBy: "cloud",
|
|
21143
|
+
channel: "cloud"
|
|
19899
21144
|
};
|
|
19900
21145
|
} catch (err2) {
|
|
19901
21146
|
const error = err2;
|
|
@@ -19925,14 +21170,31 @@ ${packageReview}`;
|
|
|
19925
21170
|
if (config.policySource !== "workspace") {
|
|
19926
21171
|
for (const d of exposureDestinations) addTrustedRecipient(d, "approval");
|
|
19927
21172
|
}
|
|
19928
|
-
return {
|
|
21173
|
+
return {
|
|
21174
|
+
approved: true,
|
|
21175
|
+
checkedBy: "daemon",
|
|
21176
|
+
decisionSource: "native",
|
|
21177
|
+
decidedBy: "person",
|
|
21178
|
+
channel: "native"
|
|
21179
|
+
};
|
|
19929
21180
|
}
|
|
19930
21181
|
if (decision === "always_allow") {
|
|
19931
21182
|
if (standingTrustApplies()) {
|
|
19932
21183
|
writeTrustSession(toolName, 36e5, args);
|
|
19933
|
-
return {
|
|
21184
|
+
return {
|
|
21185
|
+
approved: true,
|
|
21186
|
+
checkedBy: "trust",
|
|
21187
|
+
decidedBy: "person",
|
|
21188
|
+
channel: "native"
|
|
21189
|
+
};
|
|
19934
21190
|
}
|
|
19935
|
-
return {
|
|
21191
|
+
return {
|
|
21192
|
+
approved: true,
|
|
21193
|
+
checkedBy: "daemon",
|
|
21194
|
+
decisionSource: "native",
|
|
21195
|
+
decidedBy: "person",
|
|
21196
|
+
channel: "native"
|
|
21197
|
+
};
|
|
19936
21198
|
}
|
|
19937
21199
|
const isApproved = decision === "allow";
|
|
19938
21200
|
return {
|
|
@@ -19941,75 +21203,32 @@ ${packageReview}`;
|
|
|
19941
21203
|
checkedBy: isApproved ? "daemon" : void 0,
|
|
19942
21204
|
blockedBy: isApproved ? void 0 : "local-decision",
|
|
19943
21205
|
blockedByLabel: "User Decision (Native)",
|
|
19944
|
-
decisionSource: "native"
|
|
21206
|
+
decisionSource: "native",
|
|
21207
|
+
decidedBy: "person",
|
|
21208
|
+
channel: "native"
|
|
19945
21209
|
};
|
|
19946
21210
|
})()
|
|
19947
21211
|
);
|
|
19948
21212
|
}
|
|
21213
|
+
const fromCard = /* @__PURE__ */ new WeakSet();
|
|
21214
|
+
const timeoutReason = approvalTimeoutMs > 0 ? approvalTimeoutReason(approvalTimeoutMs) : void 0;
|
|
19949
21215
|
if (daemonEntryId && approvers.terminal) {
|
|
19950
21216
|
racePromises.push(
|
|
19951
21217
|
(async () => {
|
|
19952
|
-
const
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
if (
|
|
19962
|
-
|
|
19963
|
-
const
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
return {
|
|
19967
|
-
approved: false,
|
|
19968
|
-
reason: daemonReason || "Action rejected by organization administrator via Slack.",
|
|
19969
|
-
blockedBy: "team-policy",
|
|
19970
|
-
blockedByLabel: "Organization Policy (SaaS)",
|
|
19971
|
-
decisionSource: "cloud"
|
|
19972
|
-
};
|
|
19973
|
-
const timedOut = decisionSource === "timeout";
|
|
19974
|
-
if (downgradedBlock)
|
|
19975
|
-
return {
|
|
19976
|
-
...downgradedBlock,
|
|
19977
|
-
blockedBy: timedOut ? "timeout" : "local-config"
|
|
19978
|
-
};
|
|
19979
|
-
if (timedOut)
|
|
19980
|
-
return {
|
|
19981
|
-
approved: false,
|
|
19982
|
-
reason: daemonReason || "No response: auto-denied after the approval timeout.",
|
|
19983
|
-
blockedBy: "timeout",
|
|
19984
|
-
blockedByLabel: "Approval Timeout"
|
|
19985
|
-
};
|
|
19986
|
-
if (decisionSource === "policy")
|
|
19987
|
-
return {
|
|
19988
|
-
approved: false,
|
|
19989
|
-
reason: daemonReason || "Action blocked by policy.",
|
|
19990
|
-
blockedBy: "local-config",
|
|
19991
|
-
blockedByLabel: explainableLabel
|
|
19992
|
-
};
|
|
19993
|
-
return {
|
|
19994
|
-
approved: false,
|
|
19995
|
-
reason: daemonReason || "node9 could not get an approval decision for this call.",
|
|
19996
|
-
blockedBy: "no-approval-mechanism",
|
|
19997
|
-
blockedByLabel: "Approval Unavailable"
|
|
19998
|
-
};
|
|
19999
|
-
}
|
|
20000
|
-
if (isApproved && decisionSource === "cloud")
|
|
20001
|
-
return { approved: true, checkedBy: "cloud", decisionSource: "cloud" };
|
|
20002
|
-
if (isApproved && decisionSource === "policy")
|
|
20003
|
-
return { approved: true, checkedBy: "local-policy" };
|
|
20004
|
-
const via = "Terminal (node9 tail)";
|
|
20005
|
-
return {
|
|
20006
|
-
approved: isApproved,
|
|
20007
|
-
reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
|
|
20008
|
-
checkedBy: isApproved ? "daemon" : void 0,
|
|
20009
|
-
blockedBy: isApproved ? void 0 : "local-decision",
|
|
20010
|
-
blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
|
|
20011
|
-
decisionSource: "terminal"
|
|
20012
|
-
};
|
|
21218
|
+
const cap = cardDeadline === void 0 ? daemonWaitCapMs(config.settings.approvalTimeoutMs) : cardDeadline === null ? 0 : Math.max(cardDeadline - Date.now(), 0) + 5e3;
|
|
21219
|
+
const outcome = await waitForDaemonDecision(daemonEntryId, signal, cap);
|
|
21220
|
+
const legacy = !outcome.record;
|
|
21221
|
+
const record2 = outcome.record ?? recordFromLegacyWait(outcome);
|
|
21222
|
+
if (downgradedBlock && (!record2 || legacy && record2.decidedBy === "policy" && record2.decision === "deny")) {
|
|
21223
|
+
const softened = { ...downgradedBlock, blockedBy: "local-config" };
|
|
21224
|
+
fromCard.add(softened);
|
|
21225
|
+
return softened;
|
|
21226
|
+
}
|
|
21227
|
+
if (!record2) return approvalUnavailable(outcome.reason);
|
|
21228
|
+
if (legacy && record2.decidedBy === "policy") record2.label = explainableLabel;
|
|
21229
|
+
const result = authResultFromRecord(record2, { downgradedBlock, timeoutReason });
|
|
21230
|
+
fromCard.add(result);
|
|
21231
|
+
return result;
|
|
20013
21232
|
})()
|
|
20014
21233
|
);
|
|
20015
21234
|
}
|
|
@@ -20041,7 +21260,7 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20041
21260
|
blockedByLabel: explainableLabel
|
|
20042
21261
|
};
|
|
20043
21262
|
}
|
|
20044
|
-
const
|
|
21263
|
+
const raceResult = await new Promise((resolve2) => {
|
|
20045
21264
|
let resolved = false;
|
|
20046
21265
|
let failures = 0;
|
|
20047
21266
|
const total = racePromises.length;
|
|
@@ -20049,14 +21268,6 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20049
21268
|
if (!resolved) {
|
|
20050
21269
|
resolved = true;
|
|
20051
21270
|
abortController.abort();
|
|
20052
|
-
if (daemonEntryId && internalToken) {
|
|
20053
|
-
resolveViaDaemon(
|
|
20054
|
-
daemonEntryId,
|
|
20055
|
-
res.approved ? "allow" : "deny",
|
|
20056
|
-
internalToken,
|
|
20057
|
-
res.decisionSource
|
|
20058
|
-
).catch(() => null);
|
|
20059
|
-
}
|
|
20060
21271
|
resolve2(res);
|
|
20061
21272
|
}
|
|
20062
21273
|
};
|
|
@@ -20064,26 +21275,29 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20064
21275
|
p.then(finish).catch((err2) => {
|
|
20065
21276
|
if (err2.name === "AbortError" || err2.message?.includes("canceled") || err2.message?.includes("Aborted"))
|
|
20066
21277
|
return;
|
|
20067
|
-
if (err2.message === "Abandoned") {
|
|
20068
|
-
finish({
|
|
20069
|
-
approved: false,
|
|
20070
|
-
reason: "Browser dashboard closed without making a decision.",
|
|
20071
|
-
blockedBy: "local-decision",
|
|
20072
|
-
blockedByLabel: "Browser Dashboard (Abandoned)"
|
|
20073
|
-
});
|
|
20074
|
-
return;
|
|
20075
|
-
}
|
|
20076
21278
|
failures++;
|
|
20077
21279
|
if (failures === total && !resolved) {
|
|
20078
|
-
finish(
|
|
21280
|
+
finish(approvalUnavailable("All approval channels failed or disconnected."));
|
|
20079
21281
|
}
|
|
20080
21282
|
});
|
|
20081
21283
|
}
|
|
20082
21284
|
});
|
|
21285
|
+
let finalResult = raceResult;
|
|
21286
|
+
if (daemonEntryId && internalToken && !fromCard.has(raceResult)) {
|
|
21287
|
+
const reply = await resolveViaDaemon(
|
|
21288
|
+
daemonEntryId,
|
|
21289
|
+
raceResult.approved ? "allow" : "deny",
|
|
21290
|
+
internalToken,
|
|
21291
|
+
raceResult.decisionSource,
|
|
21292
|
+
recordFromAuthResult(raceResult)
|
|
21293
|
+
).catch(() => null);
|
|
21294
|
+
if (reply && !reply.won)
|
|
21295
|
+
finalResult = authResultFromRecord(reply.record, { downgradedBlock, timeoutReason });
|
|
21296
|
+
}
|
|
20083
21297
|
if (finalResult.approved && exposureKeys.length > 0 && meta?.sessionId && ucPolicy?.afterExposure === "ask-once") {
|
|
20084
21298
|
await approveSessionExposure(meta.sessionId, exposureKeys);
|
|
20085
21299
|
}
|
|
20086
|
-
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
21300
|
+
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud" && finalResult.decidedBy !== "cloud") {
|
|
20087
21301
|
await resolveNode9SaaS(
|
|
20088
21302
|
cloudRequestId,
|
|
20089
21303
|
creds,
|
|
@@ -20107,7 +21321,12 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
20107
21321
|
{
|
|
20108
21322
|
...ownRowMeta(finalResult.approved),
|
|
20109
21323
|
...cloudRequestId && { cloudRequestId },
|
|
20110
|
-
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` }
|
|
21324
|
+
...appPermReview && { ruleName: `app-permission:${appPermReviewTool}` },
|
|
21325
|
+
// Who decided, from the decision record, and the card: the daemon
|
|
21326
|
+
// writes its own row for the same card, and the report counts one.
|
|
21327
|
+
...finalResult.decidedBy && { decidedBy: finalResult.decidedBy },
|
|
21328
|
+
...finalResult.channel && { channel: finalResult.channel },
|
|
21329
|
+
...daemonEntryId && { cardId: daemonEntryId }
|
|
20111
21330
|
},
|
|
20112
21331
|
hashAuditArgs
|
|
20113
21332
|
);
|
|
@@ -20159,7 +21378,8 @@ var init_orchestrator = __esm({
|
|
|
20159
21378
|
init_jail();
|
|
20160
21379
|
init_safe_text();
|
|
20161
21380
|
init_negotiation();
|
|
20162
|
-
|
|
21381
|
+
init_decision_record();
|
|
21382
|
+
init_card_result();
|
|
20163
21383
|
SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
|
|
20164
21384
|
WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
20165
21385
|
"write",
|
|
@@ -29796,12 +31016,206 @@ var init_session_history = __esm({
|
|
|
29796
31016
|
}
|
|
29797
31017
|
});
|
|
29798
31018
|
|
|
31019
|
+
// src/daemon/cards.ts
|
|
31020
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
31021
|
+
var CARD_RETENTION_MS, MAX_CARD_TIMEOUT_MS, CardStore;
|
|
31022
|
+
var init_cards = __esm({
|
|
31023
|
+
"src/daemon/cards.ts"() {
|
|
31024
|
+
"use strict";
|
|
31025
|
+
init_decision_record();
|
|
31026
|
+
CARD_RETENTION_MS = 6e4;
|
|
31027
|
+
MAX_CARD_TIMEOUT_MS = 24 * 60 * 6e4;
|
|
31028
|
+
CardStore = class {
|
|
31029
|
+
slots = /* @__PURE__ */ new Map();
|
|
31030
|
+
now;
|
|
31031
|
+
setTimer;
|
|
31032
|
+
clearTimer;
|
|
31033
|
+
deps;
|
|
31034
|
+
constructor(deps = {}) {
|
|
31035
|
+
this.deps = deps;
|
|
31036
|
+
this.now = deps.now ?? Date.now;
|
|
31037
|
+
this.setTimer = deps.setTimer ?? ((fn, ms) => {
|
|
31038
|
+
const t = setTimeout(fn, ms);
|
|
31039
|
+
t.unref?.();
|
|
31040
|
+
return t;
|
|
31041
|
+
});
|
|
31042
|
+
this.clearTimer = deps.clearTimer ?? ((h) => clearTimeout(h));
|
|
31043
|
+
}
|
|
31044
|
+
register(request2, opts) {
|
|
31045
|
+
const id = opts.id && !this.slots.has(opts.id) ? opts.id : randomUUID5();
|
|
31046
|
+
const createdAt = this.now();
|
|
31047
|
+
const timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? Math.min(opts.timeoutMs, MAX_CARD_TIMEOUT_MS) : null;
|
|
31048
|
+
const card = {
|
|
31049
|
+
id,
|
|
31050
|
+
kind: opts.kind,
|
|
31051
|
+
state: "open",
|
|
31052
|
+
request: request2,
|
|
31053
|
+
channels: [...opts.channels],
|
|
31054
|
+
createdAt,
|
|
31055
|
+
deadline: timeoutMs === null ? null : createdAt + timeoutMs
|
|
31056
|
+
};
|
|
31057
|
+
const slot = { card, waiters: /* @__PURE__ */ new Set(), everWaited: false };
|
|
31058
|
+
if (timeoutMs !== null) {
|
|
31059
|
+
slot.timer = this.setTimer(() => {
|
|
31060
|
+
slot.timer = void 0;
|
|
31061
|
+
this.decide(id, timerRecord(timeoutMs, this.now()));
|
|
31062
|
+
}, timeoutMs);
|
|
31063
|
+
}
|
|
31064
|
+
this.slots.set(id, slot);
|
|
31065
|
+
return card;
|
|
31066
|
+
}
|
|
31067
|
+
get(id) {
|
|
31068
|
+
return this.slots.get(id)?.card;
|
|
31069
|
+
}
|
|
31070
|
+
/** Cards still waiting for a decision, oldest first. */
|
|
31071
|
+
open() {
|
|
31072
|
+
return [...this.slots.values()].filter((s) => s.card.state === "open").map((s) => s.card);
|
|
31073
|
+
}
|
|
31074
|
+
/**
|
|
31075
|
+
* The ONLY way a card gets a decision. The first call wins; a later call
|
|
31076
|
+
* gets the winning record back with `won: false`. A withdrawn card can
|
|
31077
|
+
* still be decided (its asker, an older hook, may report its own winner
|
|
31078
|
+
* after closing its wait). Null when the card does not exist.
|
|
31079
|
+
*/
|
|
31080
|
+
decide(id, record2) {
|
|
31081
|
+
const slot = this.slots.get(id);
|
|
31082
|
+
if (!slot) return null;
|
|
31083
|
+
const { card } = slot;
|
|
31084
|
+
if (card.state === "decided") return { record: card.record, won: false };
|
|
31085
|
+
if (slot.timer !== void 0) {
|
|
31086
|
+
this.clearTimer(slot.timer);
|
|
31087
|
+
slot.timer = void 0;
|
|
31088
|
+
}
|
|
31089
|
+
card.state = "decided";
|
|
31090
|
+
card.record = record2;
|
|
31091
|
+
card.closedAt = this.now();
|
|
31092
|
+
this.deps.onDecided?.(card);
|
|
31093
|
+
if (slot.waiters.size > 0) {
|
|
31094
|
+
const waiters = [...slot.waiters];
|
|
31095
|
+
slot.waiters.clear();
|
|
31096
|
+
for (const w of waiters) w({ kind: "decided", record: record2 });
|
|
31097
|
+
this.slots.delete(id);
|
|
31098
|
+
}
|
|
31099
|
+
return { record: record2, won: true };
|
|
31100
|
+
}
|
|
31101
|
+
/** The asker left before any decision. False when the card was not open. */
|
|
31102
|
+
withdraw(id) {
|
|
31103
|
+
const slot = this.slots.get(id);
|
|
31104
|
+
if (!slot || slot.card.state !== "open") return false;
|
|
31105
|
+
if (slot.timer !== void 0) {
|
|
31106
|
+
this.clearTimer(slot.timer);
|
|
31107
|
+
slot.timer = void 0;
|
|
31108
|
+
}
|
|
31109
|
+
slot.card.state = "withdrawn";
|
|
31110
|
+
slot.card.closedAt = this.now();
|
|
31111
|
+
const waiters = [...slot.waiters];
|
|
31112
|
+
slot.waiters.clear();
|
|
31113
|
+
for (const w of waiters) w({ kind: "withdrawn" });
|
|
31114
|
+
this.deps.onWithdrawn?.(slot.card);
|
|
31115
|
+
return true;
|
|
31116
|
+
}
|
|
31117
|
+
/**
|
|
31118
|
+
* Wait for the card's decision. Any number of waiters. A decided card
|
|
31119
|
+
* answers at once, and is removed once read. When the last waiter of an
|
|
31120
|
+
* open card goes away (its signal aborts), the card is withdrawn: nobody is
|
|
31121
|
+
* left to act on a decision.
|
|
31122
|
+
*/
|
|
31123
|
+
wait(id, signal) {
|
|
31124
|
+
const slot = this.slots.get(id);
|
|
31125
|
+
if (!slot) return Promise.resolve({ kind: "missing" });
|
|
31126
|
+
const { card } = slot;
|
|
31127
|
+
if (card.state === "decided") {
|
|
31128
|
+
this.slots.delete(id);
|
|
31129
|
+
return Promise.resolve({ kind: "decided", record: card.record });
|
|
31130
|
+
}
|
|
31131
|
+
if (card.state === "withdrawn") return Promise.resolve({ kind: "withdrawn" });
|
|
31132
|
+
slot.everWaited = true;
|
|
31133
|
+
return new Promise((resolve2) => {
|
|
31134
|
+
const waiter = (o) => {
|
|
31135
|
+
signal?.removeEventListener("abort", onAbort);
|
|
31136
|
+
resolve2(o);
|
|
31137
|
+
};
|
|
31138
|
+
const onAbort = () => {
|
|
31139
|
+
if (!slot.waiters.delete(waiter)) return;
|
|
31140
|
+
resolve2({ kind: "withdrawn" });
|
|
31141
|
+
if (slot.waiters.size === 0 && card.state === "open") this.withdraw(id);
|
|
31142
|
+
};
|
|
31143
|
+
slot.waiters.add(waiter);
|
|
31144
|
+
if (signal) {
|
|
31145
|
+
if (signal.aborted) onAbort();
|
|
31146
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
31147
|
+
}
|
|
31148
|
+
});
|
|
31149
|
+
}
|
|
31150
|
+
/**
|
|
31151
|
+
* The daemon's local UIs all went away. Open cards that only they could
|
|
31152
|
+
* answer are decided as abandoned; a card that Slack or the native popup
|
|
31153
|
+
* can still answer stays open. Returns how many were abandoned.
|
|
31154
|
+
*/
|
|
31155
|
+
abandonLocalOnly() {
|
|
31156
|
+
let n = 0;
|
|
31157
|
+
for (const card of this.open()) {
|
|
31158
|
+
const localOnly = card.channels.length > 0 && card.channels.every((c) => LOCAL_UI_CHANNELS.has(c));
|
|
31159
|
+
if (!localOnly) continue;
|
|
31160
|
+
if (this.decide(card.id, abandonedRecord(this.now()))?.won) n++;
|
|
31161
|
+
}
|
|
31162
|
+
return n;
|
|
31163
|
+
}
|
|
31164
|
+
/**
|
|
31165
|
+
* Housekeeping, run every few seconds:
|
|
31166
|
+
* - a decided card nobody read and a withdrawn card are removed after the
|
|
31167
|
+
* retention;
|
|
31168
|
+
* - an asked card that nobody ever waited on (its asker died between
|
|
31169
|
+
* registering and waiting) is withdrawn after the retention.
|
|
31170
|
+
*/
|
|
31171
|
+
sweep() {
|
|
31172
|
+
const now = this.now();
|
|
31173
|
+
for (const [id, slot] of this.slots) {
|
|
31174
|
+
const { card } = slot;
|
|
31175
|
+
if (card.state !== "open") {
|
|
31176
|
+
if (now - (card.closedAt ?? card.createdAt) >= CARD_RETENTION_MS) this.slots.delete(id);
|
|
31177
|
+
continue;
|
|
31178
|
+
}
|
|
31179
|
+
if (card.kind !== "discovery" && !slot.everWaited && now - card.createdAt >= CARD_RETENTION_MS)
|
|
31180
|
+
this.withdraw(id);
|
|
31181
|
+
}
|
|
31182
|
+
}
|
|
31183
|
+
/** Cards in any state (tests and diagnostics). */
|
|
31184
|
+
size() {
|
|
31185
|
+
return this.slots.size;
|
|
31186
|
+
}
|
|
31187
|
+
};
|
|
31188
|
+
}
|
|
31189
|
+
});
|
|
31190
|
+
|
|
29799
31191
|
// src/daemon/state.ts
|
|
29800
31192
|
import net2 from "net";
|
|
29801
31193
|
import fs49 from "fs";
|
|
29802
31194
|
import path49 from "path";
|
|
29803
31195
|
import os40 from "os";
|
|
29804
|
-
import { randomUUID as
|
|
31196
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
31197
|
+
function onCardDecided(card) {
|
|
31198
|
+
const r = card.record;
|
|
31199
|
+
broadcast("remove", { id: card.id, decision: r.decision, decidedBy: r.decidedBy });
|
|
31200
|
+
if (card.kind === "discovery") return;
|
|
31201
|
+
appendAuditLog({
|
|
31202
|
+
toolName: card.request.toolName,
|
|
31203
|
+
args: card.request.args,
|
|
31204
|
+
decision: r.decidedBy === "timer" ? "auto-deny" : r.standing === "trust" && r.trustDuration ? `trust:${r.trustDuration}` : r.decision,
|
|
31205
|
+
decidedBy: r.decidedBy,
|
|
31206
|
+
...r.channel && { channel: r.channel },
|
|
31207
|
+
cardId: card.id
|
|
31208
|
+
});
|
|
31209
|
+
if (r.decidedBy !== "person") return;
|
|
31210
|
+
const tool = card.request.toolName;
|
|
31211
|
+
if (r.decision === "allow" && r.standing !== "always-allow" && r.standing !== "trust") {
|
|
31212
|
+
insightCounts.set(tool, (insightCounts.get(tool) ?? 0) + 1);
|
|
31213
|
+
saveInsightCounts();
|
|
31214
|
+
} else if (r.decision === "deny") {
|
|
31215
|
+
insightCounts.delete(tool);
|
|
31216
|
+
saveInsightCounts();
|
|
31217
|
+
}
|
|
31218
|
+
}
|
|
29805
31219
|
function loadInsightCounts() {
|
|
29806
31220
|
try {
|
|
29807
31221
|
if (!fs49.existsSync(INSIGHT_COUNTS_FILE)) return;
|
|
@@ -29868,7 +31282,10 @@ function appendAuditLog(data) {
|
|
|
29868
31282
|
tool: data.toolName,
|
|
29869
31283
|
args: maskPersonalArgs(redactArgs(data.args)),
|
|
29870
31284
|
decision: data.decision,
|
|
29871
|
-
source: "daemon"
|
|
31285
|
+
source: "daemon",
|
|
31286
|
+
...data.decidedBy && { decidedBy: data.decidedBy },
|
|
31287
|
+
...data.channel && { channel: data.channel },
|
|
31288
|
+
...data.cardId && { cardId: data.cardId }
|
|
29872
31289
|
};
|
|
29873
31290
|
const dir2 = path49.dirname(AUDIT_LOG_FILE);
|
|
29874
31291
|
if (!fs49.existsSync(dir2)) fs49.mkdirSync(dir2, { recursive: true });
|
|
@@ -30001,7 +31418,7 @@ function broadcastForensic(finding) {
|
|
|
30001
31418
|
const severity = CRITICAL_FORENSIC_CATEGORIES.has(finding.type) ? "critical" : "warning";
|
|
30002
31419
|
const event = {
|
|
30003
31420
|
type: "forensic",
|
|
30004
|
-
id: `fnd_${
|
|
31421
|
+
id: `fnd_${randomUUID6()}`,
|
|
30005
31422
|
ts: Date.now(),
|
|
30006
31423
|
sessionId: finding.sessionId,
|
|
30007
31424
|
category: finding.type,
|
|
@@ -30012,14 +31429,8 @@ function broadcastForensic(finding) {
|
|
|
30012
31429
|
}
|
|
30013
31430
|
function abandonPending() {
|
|
30014
31431
|
setAbandonTimer(null);
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
if (entry.waiter) entry.waiter("abandoned");
|
|
30018
|
-
else entry.earlyDecision = "abandoned";
|
|
30019
|
-
pending.delete(id);
|
|
30020
|
-
broadcast("remove", { id });
|
|
30021
|
-
});
|
|
30022
|
-
if (autoStarted) {
|
|
31432
|
+
cards.abandonLocalOnly();
|
|
31433
|
+
if (autoStarted && cards.open().length === 0) {
|
|
30023
31434
|
try {
|
|
30024
31435
|
fs49.unlinkSync(DAEMON_PID_FILE);
|
|
30025
31436
|
} catch {
|
|
@@ -30183,7 +31594,7 @@ function bindActivitySocket() {
|
|
|
30183
31594
|
});
|
|
30184
31595
|
activitySocketServer = unixServer;
|
|
30185
31596
|
}
|
|
30186
|
-
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE,
|
|
31597
|
+
var homeDir, DAEMON_PID_FILE, DECISIONS_FILE, AUDIT_LOG_FILE, TRUST_FILE2, GLOBAL_CONFIG_FILE, CREDENTIALS_FILE, INSIGHT_COUNTS_FILE, sseClients, cards, suggestionTracker, taintStore, sessionTaintStore, sessionExposureStore, insightCounts, _abandonTimer, _hadBrowserClient, _daemonServer, daemonRejectionHandlerRegistered, AUTO_DENY_MS, TRUST_DURATIONS, autoStarted, ACTIVITY_SOCKET_PATH2, ACTIVITY_RING_SIZE, activityRing, LARGE_RESPONSE_RING_SIZE, largeResponseRing, cachedScanResult, cachedScanTs, SCAN_CACHE_TTL_MS, SECRET_KEY_RE, INPUT_PRICE_PER_1M, OUTPUT_PRICE_PER_1M, BYTES_PER_TOKEN, CRITICAL_FORENSIC_CATEGORIES, WRITE_TOOL_NAMES, ACTIVITY_REBIND_MAX_ATTEMPTS, ACTIVITY_REBIND_WINDOW_MS, ACTIVITY_HEALTH_PROBE_MS, activitySocketServer, activityHealthInterval, activityRebindAttempts, activityCircuitTripped;
|
|
30187
31598
|
var init_state2 = __esm({
|
|
30188
31599
|
"src/daemon/state.ts"() {
|
|
30189
31600
|
"use strict";
|
|
@@ -30196,6 +31607,7 @@ var init_state2 = __esm({
|
|
|
30196
31607
|
init_session_counters();
|
|
30197
31608
|
init_session_history();
|
|
30198
31609
|
init_atomic_write();
|
|
31610
|
+
init_cards();
|
|
30199
31611
|
homeDir = os40.homedir();
|
|
30200
31612
|
DAEMON_PID_FILE = path49.join(homeDir, ".node9", "daemon.pid");
|
|
30201
31613
|
DECISIONS_FILE = path49.join(homeDir, ".node9", "decisions.json");
|
|
@@ -30204,8 +31616,11 @@ var init_state2 = __esm({
|
|
|
30204
31616
|
GLOBAL_CONFIG_FILE = path49.join(homeDir, ".node9", "config.json");
|
|
30205
31617
|
CREDENTIALS_FILE = path49.join(homeDir, ".node9", "credentials.json");
|
|
30206
31618
|
INSIGHT_COUNTS_FILE = path49.join(homeDir, ".node9", "insight-counts.json");
|
|
30207
|
-
pending = /* @__PURE__ */ new Map();
|
|
30208
31619
|
sseClients = /* @__PURE__ */ new Set();
|
|
31620
|
+
cards = new CardStore({
|
|
31621
|
+
onDecided: onCardDecided,
|
|
31622
|
+
onWithdrawn: (card) => broadcast("remove", { id: card.id })
|
|
31623
|
+
});
|
|
30209
31624
|
suggestionTracker = new SuggestionTracker(3);
|
|
30210
31625
|
taintStore = new TaintStore();
|
|
30211
31626
|
sessionTaintStore = new SessionTaintStore();
|
|
@@ -31518,7 +32933,7 @@ var init_posture = __esm({
|
|
|
31518
32933
|
});
|
|
31519
32934
|
|
|
31520
32935
|
// src/posture/ship.ts
|
|
31521
|
-
import
|
|
32936
|
+
import http2 from "http";
|
|
31522
32937
|
import https2 from "https";
|
|
31523
32938
|
import { URL as URL2 } from "url";
|
|
31524
32939
|
function buildShipBody(result) {
|
|
@@ -31559,7 +32974,7 @@ async function shipPosture(result, creds) {
|
|
|
31559
32974
|
if (!url) return false;
|
|
31560
32975
|
const body = JSON.stringify(buildShipBody(result));
|
|
31561
32976
|
const parsed = new URL2(url);
|
|
31562
|
-
const transport = parsed.protocol === "http:" ?
|
|
32977
|
+
const transport = parsed.protocol === "http:" ? http2 : https2;
|
|
31563
32978
|
return new Promise((resolve2) => {
|
|
31564
32979
|
const req = transport.request(
|
|
31565
32980
|
{
|
|
@@ -31758,7 +33173,7 @@ var init_mcp_status = __esm({
|
|
|
31758
33173
|
});
|
|
31759
33174
|
|
|
31760
33175
|
// src/policy-snapshot/ship.ts
|
|
31761
|
-
import
|
|
33176
|
+
import http3 from "http";
|
|
31762
33177
|
import https3 from "https";
|
|
31763
33178
|
import { URL as URL3 } from "url";
|
|
31764
33179
|
function policySnapshotUrlFrom(apiUrl) {
|
|
@@ -31769,7 +33184,7 @@ async function shipPolicySnapshot(body, creds) {
|
|
|
31769
33184
|
if (!url) return false;
|
|
31770
33185
|
const payload = JSON.stringify(body);
|
|
31771
33186
|
const parsed = new URL3(url);
|
|
31772
|
-
const transport = parsed.protocol === "http:" ?
|
|
33187
|
+
const transport = parsed.protocol === "http:" ? http3 : https3;
|
|
31773
33188
|
return new Promise((resolve2) => {
|
|
31774
33189
|
const req = transport.request(
|
|
31775
33190
|
{
|
|
@@ -31837,17 +33252,17 @@ var init_machine_id = __esm({
|
|
|
31837
33252
|
// src/daemon/sync-trigger.ts
|
|
31838
33253
|
function createSyncTrigger(sync) {
|
|
31839
33254
|
let running2;
|
|
31840
|
-
let
|
|
33255
|
+
let pending = false;
|
|
31841
33256
|
return (invalidate = true) => {
|
|
31842
33257
|
if (running2) {
|
|
31843
|
-
if (invalidate)
|
|
33258
|
+
if (invalidate) pending = true;
|
|
31844
33259
|
return running2;
|
|
31845
33260
|
}
|
|
31846
33261
|
running2 = (async () => {
|
|
31847
33262
|
do {
|
|
31848
|
-
|
|
33263
|
+
pending = false;
|
|
31849
33264
|
await sync();
|
|
31850
|
-
} while (
|
|
33265
|
+
} while (pending);
|
|
31851
33266
|
})().finally(() => {
|
|
31852
33267
|
running2 = void 0;
|
|
31853
33268
|
});
|
|
@@ -32755,7 +34170,11 @@ function buildWireRows(chunk2) {
|
|
|
32755
34170
|
...typeof parsed.taintFromEid === "string" ? { taintFromEid: parsed.taintFromEid } : {},
|
|
32756
34171
|
...typeof parsed.taintSource === "string" ? { taintSource: parsed.taintSource } : {},
|
|
32757
34172
|
...exposureOf(parsed.exposure) ? { exposure: exposureOf(parsed.exposure) } : {},
|
|
32758
|
-
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {}
|
|
34173
|
+
...typeof parsed.exposureFromEid === "string" ? { exposureFromEid: parsed.exposureFromEid } : {},
|
|
34174
|
+
// Approval cards. Only the known values: the ingest's schema is a
|
|
34175
|
+
// closed vocabulary and rejects the whole batch for anything else.
|
|
34176
|
+
...typeof parsed.decidedBy === "string" && DECIDED_BY_VALUES.has(parsed.decidedBy) ? { decidedBy: parsed.decidedBy } : {},
|
|
34177
|
+
...typeof parsed.channel === "string" && CHANNELS.includes(parsed.channel) ? { channel: parsed.channel } : {}
|
|
32759
34178
|
});
|
|
32760
34179
|
}
|
|
32761
34180
|
return { rows, consumed: lastNl + 1 };
|
|
@@ -32907,15 +34326,23 @@ function outboxBacklog(auditLogPath = LOCAL_AUDIT_LOG, watermarkPath = AUDIT_SHI
|
|
|
32907
34326
|
};
|
|
32908
34327
|
}
|
|
32909
34328
|
}
|
|
32910
|
-
var AUDIT_SHIP_WATERMARK, DEFAULT_INTERVAL_MS, MAX_BATCH, MAX_CHUNK_BYTES, MAX_CHUNKS_PER_TICK, FETCH_TIMEOUT_MS, SKIP_CHECKED_BY, shipperStarted;
|
|
34329
|
+
var DECIDED_BY_VALUES, AUDIT_SHIP_WATERMARK, DEFAULT_INTERVAL_MS, MAX_BATCH, MAX_CHUNK_BYTES, MAX_CHUNKS_PER_TICK, FETCH_TIMEOUT_MS, SKIP_CHECKED_BY, shipperStarted;
|
|
32911
34330
|
var init_audit_shipper = __esm({
|
|
32912
34331
|
"src/daemon/audit-shipper.ts"() {
|
|
32913
34332
|
"use strict";
|
|
32914
34333
|
init_audit();
|
|
34334
|
+
init_decision_record();
|
|
32915
34335
|
init_config();
|
|
32916
34336
|
init_sync();
|
|
32917
34337
|
init_cloud();
|
|
32918
34338
|
init_dist();
|
|
34339
|
+
DECIDED_BY_VALUES = /* @__PURE__ */ new Set([
|
|
34340
|
+
"person",
|
|
34341
|
+
"timer",
|
|
34342
|
+
"cloud",
|
|
34343
|
+
"policy",
|
|
34344
|
+
"abandoned"
|
|
34345
|
+
]);
|
|
32919
34346
|
AUDIT_SHIP_WATERMARK = path55.join(os47.homedir(), ".node9", "audit-ship.json");
|
|
32920
34347
|
DEFAULT_INTERVAL_MS = 2e4;
|
|
32921
34348
|
MAX_BATCH = 500;
|
|
@@ -32940,6 +34367,18 @@ function classifyDecision(a, b) {
|
|
|
32940
34367
|
}
|
|
32941
34368
|
if (d === "dlp") return { outcome: "info", label: "Finding", raw };
|
|
32942
34369
|
if (d === "mcp-discovered") return { outcome: "info", label: "Info", raw };
|
|
34370
|
+
const decidedBy = isRow ? a.decidedBy : void 0;
|
|
34371
|
+
if (typeof decidedBy === "string") {
|
|
34372
|
+
const allowed = d === "allow" || d === "allowed" || d.startsWith("trust:");
|
|
34373
|
+
const denied = d === "deny" || d === "auto-deny" || d === "block";
|
|
34374
|
+
if (allowed || denied) {
|
|
34375
|
+
if (decidedBy === "person")
|
|
34376
|
+
return allowed ? { outcome: "allow", label: "Approved", raw } : { outcome: "deny", label: "Denied", raw };
|
|
34377
|
+
if (decidedBy === "timer" || decidedBy === "abandoned")
|
|
34378
|
+
return { outcome: "deny", label: "Timed out", raw };
|
|
34379
|
+
return allowed ? { outcome: "allow", label: "Auto-allowed", raw } : { outcome: "deny", label: "Blocked", raw };
|
|
34380
|
+
}
|
|
34381
|
+
}
|
|
32943
34382
|
if (d === "allow" || d === "allowed") {
|
|
32944
34383
|
if (src === "post-hook") return { outcome: "allow", label: "Ran", raw };
|
|
32945
34384
|
if (HUMAN_SOURCES.has(src)) {
|
|
@@ -33873,7 +35312,17 @@ var init_osv_sync = __esm({
|
|
|
33873
35312
|
init_zip();
|
|
33874
35313
|
init_osv_index();
|
|
33875
35314
|
init_net();
|
|
33876
|
-
ECOSYSTEMS2 = [
|
|
35315
|
+
ECOSYSTEMS2 = [
|
|
35316
|
+
"npm",
|
|
35317
|
+
"PyPI",
|
|
35318
|
+
"RubyGems",
|
|
35319
|
+
"crates.io",
|
|
35320
|
+
"Go",
|
|
35321
|
+
"NuGet",
|
|
35322
|
+
"Maven",
|
|
35323
|
+
"Packagist",
|
|
35324
|
+
"VSCode"
|
|
35325
|
+
];
|
|
33877
35326
|
SYNC_INTERVAL_MS2 = 6 * 60 * 60 * 1e3;
|
|
33878
35327
|
FIRST_SYNC_DELAY_MS = 2 * 60 * 1e3;
|
|
33879
35328
|
FULL_REBUILD_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -34356,11 +35805,11 @@ var init_heartbeat = __esm({
|
|
|
34356
35805
|
});
|
|
34357
35806
|
|
|
34358
35807
|
// src/daemon/server.ts
|
|
34359
|
-
import
|
|
35808
|
+
import http4 from "http";
|
|
34360
35809
|
import fs63 from "fs";
|
|
34361
35810
|
import path61 from "path";
|
|
34362
35811
|
import os52 from "os";
|
|
34363
|
-
import { randomUUID as
|
|
35812
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
34364
35813
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
34365
35814
|
import chalk7 from "chalk";
|
|
34366
35815
|
function buildDaemonReport(allEntries, period, now) {
|
|
@@ -34433,14 +35882,40 @@ function buildDaemonReport(allEntries, period, now) {
|
|
|
34433
35882
|
byAgent: [...agentMap.values()].sort((a, b) => b.total - a.total)
|
|
34434
35883
|
};
|
|
34435
35884
|
}
|
|
34436
|
-
function
|
|
34437
|
-
const ms =
|
|
34438
|
-
if (ms === 0) return null;
|
|
35885
|
+
function configTimeoutMs(config) {
|
|
35886
|
+
const ms = config.settings.approvalTimeoutMs;
|
|
34439
35887
|
return ms ?? AUTO_DENY_MS;
|
|
34440
35888
|
}
|
|
34441
|
-
function
|
|
34442
|
-
|
|
34443
|
-
|
|
35889
|
+
function parseChannels(v) {
|
|
35890
|
+
if (!Array.isArray(v)) return null;
|
|
35891
|
+
const out = v.filter((c) => CHANNELS.includes(c));
|
|
35892
|
+
return out.length > 0 ? [...new Set(out)] : null;
|
|
35893
|
+
}
|
|
35894
|
+
function inferChannels(kind, slackDelegated) {
|
|
35895
|
+
if (kind === "evaluate") return ["terminal", "monitor"];
|
|
35896
|
+
return slackDelegated ? ["terminal", "monitor", "cloud"] : ["terminal", "monitor", "native", "cloud"];
|
|
35897
|
+
}
|
|
35898
|
+
function cardView(card) {
|
|
35899
|
+
const r = card.request;
|
|
35900
|
+
return {
|
|
35901
|
+
id: card.id,
|
|
35902
|
+
toolName: r.toolName,
|
|
35903
|
+
args: r.args,
|
|
35904
|
+
riskMetadata: r.riskMetadata,
|
|
35905
|
+
...r.recoveryCommand && { recoveryCommand: r.recoveryCommand },
|
|
35906
|
+
...r.viewOnly && { viewOnly: true },
|
|
35907
|
+
slackDelegated: card.channels.includes("cloud") && !card.channels.includes("native"),
|
|
35908
|
+
timestamp: card.createdAt,
|
|
35909
|
+
deadline: card.deadline,
|
|
35910
|
+
agent: r.agent,
|
|
35911
|
+
mcpServer: r.mcpServer,
|
|
35912
|
+
...card.kind === "discovery" && {
|
|
35913
|
+
type: "mcp-discovery",
|
|
35914
|
+
serverKey: r.serverKey,
|
|
35915
|
+
mcpTools: r.mcpTools
|
|
35916
|
+
},
|
|
35917
|
+
allowCount: (insightCounts.get(r.toolName) ?? 0) + 1
|
|
35918
|
+
};
|
|
34444
35919
|
}
|
|
34445
35920
|
function startDaemon() {
|
|
34446
35921
|
try {
|
|
@@ -34462,7 +35937,7 @@ function startDaemon() {
|
|
|
34462
35937
|
recordStartupState("failed", "startup-throw", err2 instanceof Error ? err2.message : String(err2));
|
|
34463
35938
|
process.exit(1);
|
|
34464
35939
|
}
|
|
34465
|
-
const internalToken =
|
|
35940
|
+
const internalToken = randomUUID8();
|
|
34466
35941
|
const applyTrustRecipients = (entry) => {
|
|
34467
35942
|
const dests = entry.riskMetadata?.exposureDestinations ?? [];
|
|
34468
35943
|
if (dests.length === 0) return;
|
|
@@ -34507,8 +35982,9 @@ function startDaemon() {
|
|
|
34507
35982
|
idleTimer.unref();
|
|
34508
35983
|
}
|
|
34509
35984
|
resetIdleTimer();
|
|
35985
|
+
setInterval(() => cards.sweep(), 1e4).unref();
|
|
34510
35986
|
const allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${DAEMON_PORT}`, `localhost:${DAEMON_PORT}`]);
|
|
34511
|
-
const server =
|
|
35987
|
+
const server = http4.createServer(async (req, res) => {
|
|
34512
35988
|
const host2 = req.headers.host ?? "";
|
|
34513
35989
|
if (!allowedHosts.has(host2)) {
|
|
34514
35990
|
res.writeHead(421, { "Content-Type": "text/plain" });
|
|
@@ -34538,21 +36014,8 @@ function startDaemon() {
|
|
|
34538
36014
|
res.write(
|
|
34539
36015
|
`event: init
|
|
34540
36016
|
data: ${JSON.stringify({
|
|
34541
|
-
requests:
|
|
34542
|
-
|
|
34543
|
-
toolName: e.toolName,
|
|
34544
|
-
args: e.args,
|
|
34545
|
-
riskMetadata: e.riskMetadata,
|
|
34546
|
-
...e.recoveryCommand && { recoveryCommand: e.recoveryCommand },
|
|
34547
|
-
...e.viewOnly && { viewOnly: true },
|
|
34548
|
-
slackDelegated: e.slackDelegated,
|
|
34549
|
-
timestamp: e.timestamp,
|
|
34550
|
-
agent: e.agent,
|
|
34551
|
-
mcpServer: e.mcpServer,
|
|
34552
|
-
allowCount: (insightCounts.get(e.toolName) ?? 0) + 1
|
|
34553
|
-
})),
|
|
34554
|
-
orgName: getOrgName(),
|
|
34555
|
-
autoDenyMs: autoDenyMs()
|
|
36017
|
+
requests: cards.open().map(cardView),
|
|
36018
|
+
orgName: getOrgName()
|
|
34556
36019
|
})}
|
|
34557
36020
|
|
|
34558
36021
|
`
|
|
@@ -34573,7 +36036,7 @@ data: ${JSON.stringify(item.data)}
|
|
|
34573
36036
|
}
|
|
34574
36037
|
return req.on("close", () => {
|
|
34575
36038
|
sseClients.delete(sseClient);
|
|
34576
|
-
if (sseClients.size === 0 &&
|
|
36039
|
+
if (sseClients.size === 0 && cards.open().length > 0) {
|
|
34577
36040
|
setAbandonTimer(setTimeout(abandonPending, getHadBrowserClient() ? 1e4 : 15e3));
|
|
34578
36041
|
}
|
|
34579
36042
|
});
|
|
@@ -34584,137 +36047,81 @@ data: ${JSON.stringify(item.data)}
|
|
|
34584
36047
|
_resetConfigCache();
|
|
34585
36048
|
const body = await readBody(req);
|
|
34586
36049
|
if (body.length > 65536) return res.writeHead(413).end();
|
|
34587
|
-
const
|
|
34588
|
-
|
|
34589
|
-
|
|
34590
|
-
|
|
34591
|
-
|
|
34592
|
-
|
|
34593
|
-
|
|
34594
|
-
|
|
34595
|
-
|
|
34596
|
-
|
|
34597
|
-
fromCLI = false,
|
|
34598
|
-
activityId,
|
|
34599
|
-
cwd,
|
|
34600
|
-
localSmartRuleMatched = false,
|
|
34601
|
-
sessionId
|
|
34602
|
-
} = JSON.parse(body);
|
|
34603
|
-
const id = fromCLI && typeof activityId === "string" && activityId || randomUUID7();
|
|
34604
|
-
const entry = {
|
|
34605
|
-
id,
|
|
36050
|
+
const parsed = JSON.parse(body);
|
|
36051
|
+
const { toolName, args, agent, mcpServer, riskMetadata, recoveryCommand, activityId, cwd } = parsed;
|
|
36052
|
+
if (typeof toolName !== "string" || !toolName) return res.writeHead(400).end();
|
|
36053
|
+
const fromCLI = parsed.fromCLI === true;
|
|
36054
|
+
const kind = parsed.kind === "review" || parsed.kind === "evaluate" ? parsed.kind : "fromCLI" in parsed ? "review" : "evaluate";
|
|
36055
|
+
const projectCwd = typeof cwd === "string" && path61.isAbsolute(cwd) ? cwd : void 0;
|
|
36056
|
+
const projectConfig = getConfig(projectCwd);
|
|
36057
|
+
const timeoutMs = typeof parsed.timeoutMs === "number" && Number.isFinite(parsed.timeoutMs) && parsed.timeoutMs >= 0 ? parsed.timeoutMs : configTimeoutMs(projectConfig);
|
|
36058
|
+
const sessionId = parsed.sessionId;
|
|
36059
|
+
const request2 = {
|
|
34606
36060
|
toolName,
|
|
34607
36061
|
args,
|
|
34608
|
-
riskMetadata
|
|
34609
|
-
...typeof sessionId === "string" && sessionId.length <= 256 && { sessionId },
|
|
36062
|
+
...riskMetadata && typeof riskMetadata === "object" ? { riskMetadata } : {},
|
|
34610
36063
|
...typeof recoveryCommand === "string" && recoveryCommand && { recoveryCommand },
|
|
34611
|
-
...viewOnly && { viewOnly: true },
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34616
|
-
|
|
34617
|
-
waiter: null,
|
|
34618
|
-
timer: armAutoDeny(() => {
|
|
34619
|
-
if (pending.has(id)) {
|
|
34620
|
-
const e = pending.get(id);
|
|
34621
|
-
appendAuditLog({
|
|
34622
|
-
toolName: e.toolName,
|
|
34623
|
-
args: e.args,
|
|
34624
|
-
decision: "auto-deny"
|
|
34625
|
-
});
|
|
34626
|
-
e.decisionSource = "timeout";
|
|
34627
|
-
if (e.waiter) e.waiter("deny", "No response \u2014 auto-denied after timeout");
|
|
34628
|
-
else {
|
|
34629
|
-
e.earlyDecision = "deny";
|
|
34630
|
-
e.earlyReason = "No response \u2014 auto-denied after timeout";
|
|
34631
|
-
}
|
|
34632
|
-
pending.delete(id);
|
|
34633
|
-
broadcast("remove", { id, decision: "deny" });
|
|
34634
|
-
}
|
|
34635
|
-
})
|
|
36064
|
+
...parsed.viewOnly === true && { viewOnly: true },
|
|
36065
|
+
...typeof agent === "string" && { agent },
|
|
36066
|
+
...typeof mcpServer === "string" && { mcpServer },
|
|
36067
|
+
...typeof sessionId === "string" && sessionId.length <= 256 && { sessionId },
|
|
36068
|
+
...projectCwd && { cwd: projectCwd },
|
|
36069
|
+
...parsed.protocol === CARD_PROTOCOL && { protocol: CARD_PROTOCOL }
|
|
34636
36070
|
};
|
|
34637
|
-
|
|
36071
|
+
const card = cards.register(request2, {
|
|
36072
|
+
id: fromCLI && typeof activityId === "string" && activityId ? activityId : void 0,
|
|
36073
|
+
kind,
|
|
36074
|
+
channels: parseChannels(parsed.channels) ?? inferChannels(kind, parsed.slackDelegated === true),
|
|
36075
|
+
timeoutMs
|
|
36076
|
+
});
|
|
34638
36077
|
if (!fromCLI) {
|
|
34639
36078
|
broadcast("activity", {
|
|
34640
|
-
id,
|
|
34641
|
-
ts:
|
|
36079
|
+
id: card.id,
|
|
36080
|
+
ts: card.createdAt,
|
|
34642
36081
|
tool: toolName,
|
|
34643
36082
|
args: redactArgs(args),
|
|
34644
36083
|
status: "pending",
|
|
34645
|
-
agent:
|
|
34646
|
-
mcpServer:
|
|
36084
|
+
agent: request2.agent,
|
|
36085
|
+
mcpServer: request2.mcpServer
|
|
34647
36086
|
});
|
|
34648
36087
|
}
|
|
34649
|
-
const projectCwd = typeof cwd === "string" && path61.isAbsolute(cwd) ? cwd : void 0;
|
|
34650
|
-
const projectConfig = getConfig(projectCwd);
|
|
34651
36088
|
const browserEnabled = projectConfig.settings.approvers?.browser !== false;
|
|
34652
36089
|
const terminalEnabled = projectConfig.settings.approvers?.terminal !== false;
|
|
34653
36090
|
if (browserEnabled || terminalEnabled) {
|
|
34654
|
-
broadcast("add", {
|
|
34655
|
-
id,
|
|
34656
|
-
toolName,
|
|
34657
|
-
args,
|
|
34658
|
-
riskMetadata: entry.riskMetadata,
|
|
34659
|
-
...entry.recoveryCommand && { recoveryCommand: entry.recoveryCommand },
|
|
34660
|
-
...entry.viewOnly && { viewOnly: true },
|
|
34661
|
-
slackDelegated: entry.slackDelegated,
|
|
34662
|
-
agent: entry.agent,
|
|
34663
|
-
mcpServer: entry.mcpServer,
|
|
34664
|
-
interactive: terminalEnabled,
|
|
34665
|
-
// allowCount = what this count will be if the user allows.
|
|
34666
|
-
// Terminal uses this to show the 💡 insight line on the Nth consecutive approval.
|
|
34667
|
-
allowCount: (insightCounts.get(toolName) ?? 0) + 1
|
|
34668
|
-
});
|
|
36091
|
+
broadcast("add", { ...cardView(card), interactive: terminalEnabled });
|
|
34669
36092
|
}
|
|
34670
36093
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
34671
|
-
res.end(
|
|
34672
|
-
|
|
36094
|
+
res.end(
|
|
36095
|
+
JSON.stringify({
|
|
36096
|
+
id: card.id,
|
|
36097
|
+
allowCount: (insightCounts.get(toolName) ?? 0) + 1,
|
|
36098
|
+
protocol: CARD_PROTOCOL,
|
|
36099
|
+
deadline: card.deadline
|
|
36100
|
+
})
|
|
36101
|
+
);
|
|
36102
|
+
if (kind !== "evaluate") return;
|
|
34673
36103
|
authorizeHeadless(
|
|
34674
36104
|
toolName,
|
|
34675
36105
|
args,
|
|
36106
|
+
{ agent: request2.agent, mcpServer: request2.mcpServer },
|
|
34676
36107
|
{
|
|
34677
|
-
|
|
34678
|
-
|
|
34679
|
-
|
|
34680
|
-
|
|
36108
|
+
calledFromDaemon: true,
|
|
36109
|
+
...projectCwd && { cwd: projectCwd },
|
|
36110
|
+
localSmartRuleMatched: parsed.localSmartRuleMatched === true
|
|
36111
|
+
}
|
|
34681
36112
|
).then((result) => {
|
|
34682
|
-
|
|
34683
|
-
if (
|
|
34684
|
-
if (result.noApprovalMechanism) return;
|
|
34685
|
-
if (result.checkedBy === "audit") return;
|
|
34686
|
-
if (e.earlyDecision !== null) return;
|
|
36113
|
+
if (cards.get(card.id)?.state !== "open") return;
|
|
36114
|
+
if (result.noApprovalMechanism || result.checkedBy === "audit") return;
|
|
34687
36115
|
broadcast("activity-result", {
|
|
34688
|
-
id,
|
|
36116
|
+
id: card.id,
|
|
34689
36117
|
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
34690
36118
|
label: result.blockedByLabel,
|
|
34691
|
-
agent:
|
|
34692
|
-
mcpServer:
|
|
36119
|
+
agent: request2.agent,
|
|
36120
|
+
mcpServer: request2.mcpServer
|
|
34693
36121
|
});
|
|
34694
|
-
|
|
34695
|
-
const decision = result.approved ? "allow" : "deny";
|
|
34696
|
-
appendAuditLog({ toolName: e.toolName, args: e.args, decision });
|
|
34697
|
-
e.decisionSource = backgroundPassSource(result);
|
|
34698
|
-
if (e.waiter) {
|
|
34699
|
-
e.waiter(decision, result.reason);
|
|
34700
|
-
pending.delete(id);
|
|
34701
|
-
broadcast("remove", { id, decision });
|
|
34702
|
-
} else {
|
|
34703
|
-
e.earlyDecision = decision;
|
|
34704
|
-
e.earlyReason = result.reason;
|
|
34705
|
-
}
|
|
36122
|
+
cards.decide(card.id, recordFromVerdict(result));
|
|
34706
36123
|
}).catch((err2) => {
|
|
34707
|
-
|
|
34708
|
-
if (!e) return;
|
|
34709
|
-
clearTimeout(e.timer);
|
|
34710
|
-
const reason = err2?.reason || "No response \u2014 request timed out";
|
|
34711
|
-
if (e.waiter) e.waiter("deny", reason);
|
|
34712
|
-
else {
|
|
34713
|
-
e.earlyDecision = "deny";
|
|
34714
|
-
e.earlyReason = reason;
|
|
34715
|
-
}
|
|
34716
|
-
pending.delete(id);
|
|
34717
|
-
broadcast("remove", { id, decision: "deny" });
|
|
36124
|
+
console.error(chalk7.red("[node9 daemon] check of an evaluate card failed:"), err2);
|
|
34718
36125
|
});
|
|
34719
36126
|
return;
|
|
34720
36127
|
} catch {
|
|
@@ -34723,36 +36130,33 @@ data: ${JSON.stringify(item.data)}
|
|
|
34723
36130
|
}
|
|
34724
36131
|
if (req.method === "GET" && pathname.startsWith("/wait/")) {
|
|
34725
36132
|
const id = pathname.split("/").pop();
|
|
34726
|
-
const
|
|
34727
|
-
if (!
|
|
34728
|
-
if (
|
|
34729
|
-
|
|
34730
|
-
|
|
34731
|
-
|
|
34732
|
-
|
|
34733
|
-
|
|
34734
|
-
|
|
34735
|
-
|
|
34736
|
-
|
|
34737
|
-
if (
|
|
34738
|
-
|
|
34739
|
-
|
|
34740
|
-
|
|
34741
|
-
|
|
34742
|
-
|
|
34743
|
-
|
|
34744
|
-
|
|
34745
|
-
|
|
34746
|
-
|
|
34747
|
-
|
|
34748
|
-
|
|
34749
|
-
|
|
34750
|
-
|
|
34751
|
-
|
|
34752
|
-
|
|
34753
|
-
broadcast("remove", { id });
|
|
34754
|
-
}
|
|
34755
|
-
}, 200);
|
|
36133
|
+
const card = cards.get(id);
|
|
36134
|
+
if (!card) return res.writeHead(404).end();
|
|
36135
|
+
if (card.request.protocol === CARD_PROTOCOL && !validToken(req))
|
|
36136
|
+
return res.writeHead(403).end();
|
|
36137
|
+
const ctrl = new AbortController();
|
|
36138
|
+
let answered = false;
|
|
36139
|
+
res.on("close", () => {
|
|
36140
|
+
if (!answered) ctrl.abort();
|
|
36141
|
+
});
|
|
36142
|
+
void cards.wait(id, ctrl.signal).then((outcome) => {
|
|
36143
|
+
answered = true;
|
|
36144
|
+
if (res.writableEnded || res.destroyed) return;
|
|
36145
|
+
if (outcome.kind === "decided") {
|
|
36146
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36147
|
+
res.end(
|
|
36148
|
+
JSON.stringify({
|
|
36149
|
+
protocol: CARD_PROTOCOL,
|
|
36150
|
+
record: outcome.record,
|
|
36151
|
+
...legacyWaitFields(outcome.record)
|
|
36152
|
+
})
|
|
36153
|
+
);
|
|
36154
|
+
} else if (outcome.kind === "withdrawn") {
|
|
36155
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
36156
|
+
res.end(JSON.stringify({ protocol: CARD_PROTOCOL, withdrawn: true }));
|
|
36157
|
+
} else {
|
|
36158
|
+
res.writeHead(404).end();
|
|
36159
|
+
}
|
|
34756
36160
|
});
|
|
34757
36161
|
return;
|
|
34758
36162
|
}
|
|
@@ -34760,68 +36164,63 @@ data: ${JSON.stringify(item.data)}
|
|
|
34760
36164
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
34761
36165
|
try {
|
|
34762
36166
|
const id = pathname.split("/").pop();
|
|
34763
|
-
const
|
|
34764
|
-
if (!
|
|
34765
|
-
if (
|
|
36167
|
+
const card = cards.get(id);
|
|
36168
|
+
if (!card) return res.writeHead(404).end();
|
|
36169
|
+
if (card.state === "decided") {
|
|
34766
36170
|
res.writeHead(409, { "Content-Type": "application/json" });
|
|
34767
|
-
return res.end(
|
|
36171
|
+
return res.end(
|
|
36172
|
+
JSON.stringify({ conflict: true, decision: card.record.decision, record: card.record })
|
|
36173
|
+
);
|
|
34768
36174
|
}
|
|
36175
|
+
if (card.request.viewOnly) return res.writeHead(409).end();
|
|
34769
36176
|
const { decision, persist, trustDuration, reason, source, trustRecipients } = JSON.parse(
|
|
34770
36177
|
await readBody(req)
|
|
34771
36178
|
);
|
|
34772
|
-
|
|
34773
|
-
|
|
36179
|
+
const via = channelFromDecisionSource(source);
|
|
36180
|
+
if (!via) return res.writeHead(400).end();
|
|
36181
|
+
const isTrust = decision === "trust";
|
|
36182
|
+
if (decision !== "allow" && decision !== "deny" && !isTrust)
|
|
36183
|
+
return res.writeHead(400).end();
|
|
36184
|
+
if (isTrust && (typeof trustDuration !== "string" || !(trustDuration in TRUST_DURATIONS)))
|
|
36185
|
+
return res.writeHead(400).end();
|
|
36186
|
+
if (persist === true && isTrust) return res.writeHead(400).end();
|
|
36187
|
+
const verdict = isTrust ? "allow" : decision;
|
|
36188
|
+
const redirect = via.redirect && typeof reason === "string" && reason ? reason.slice(0, 2e3) : void 0;
|
|
36189
|
+
const record2 = personRecord(via.channel, verdict, {
|
|
36190
|
+
...redirect && { redirect },
|
|
36191
|
+
...isTrust ? { standing: "trust", trustDuration } : persist === true ? {
|
|
36192
|
+
standing: verdict === "allow" ? "always-allow" : "always-deny"
|
|
36193
|
+
} : verdict === "allow" && trustRecipients === true ? { standing: "trust-recipients" } : {}
|
|
36194
|
+
});
|
|
36195
|
+
const outcome = cards.decide(id, record2);
|
|
36196
|
+
if (!outcome) return res.writeHead(404).end();
|
|
36197
|
+
if (!outcome.won) {
|
|
36198
|
+
res.writeHead(409, { "Content-Type": "application/json" });
|
|
36199
|
+
return res.end(
|
|
36200
|
+
JSON.stringify({
|
|
36201
|
+
conflict: true,
|
|
36202
|
+
decision: outcome.record.decision,
|
|
36203
|
+
record: outcome.record
|
|
36204
|
+
})
|
|
36205
|
+
);
|
|
34774
36206
|
}
|
|
34775
|
-
if (
|
|
34776
|
-
const ms = TRUST_DURATIONS[trustDuration]
|
|
34777
|
-
|
|
34778
|
-
|
|
34779
|
-
|
|
34780
|
-
toolName
|
|
34781
|
-
|
|
34782
|
-
|
|
34783
|
-
|
|
34784
|
-
|
|
34785
|
-
|
|
34786
|
-
entry.waiter("allow");
|
|
34787
|
-
pending.delete(id);
|
|
34788
|
-
broadcast("remove", { id, decision: "allow" });
|
|
34789
|
-
} else {
|
|
34790
|
-
entry.earlyDecision = "allow";
|
|
34791
|
-
broadcast("remove", { id, decision: "allow" });
|
|
34792
|
-
entry.timer = setTimeout(() => pending.delete(id), 3e4);
|
|
34793
|
-
}
|
|
34794
|
-
res.writeHead(200);
|
|
34795
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36207
|
+
if (isTrust) {
|
|
36208
|
+
const ms = TRUST_DURATIONS[trustDuration];
|
|
36209
|
+
writeTrustEntry(
|
|
36210
|
+
card.request.toolName,
|
|
36211
|
+
ms,
|
|
36212
|
+
extractCommandPattern(card.request.toolName, card.request.args)
|
|
36213
|
+
);
|
|
36214
|
+
} else if (persist === true) {
|
|
36215
|
+
writePersistentDecision(card.request.toolName, verdict);
|
|
36216
|
+
} else if (verdict === "allow" && trustRecipients === true) {
|
|
36217
|
+
applyTrustRecipients(card.request);
|
|
34796
36218
|
}
|
|
34797
|
-
|
|
34798
|
-
|
|
34799
|
-
appendAuditLog({
|
|
34800
|
-
toolName: entry.toolName,
|
|
34801
|
-
args: entry.args,
|
|
34802
|
-
decision: resolvedDecision
|
|
34803
|
-
});
|
|
34804
|
-
clearTimeout(entry.timer);
|
|
34805
|
-
if (resolvedDecision === "allow" && !persist) {
|
|
34806
|
-
insightCounts.set(entry.toolName, (insightCounts.get(entry.toolName) ?? 0) + 1);
|
|
34807
|
-
saveInsightCounts();
|
|
34808
|
-
} else if (resolvedDecision === "deny") {
|
|
34809
|
-
insightCounts.delete(entry.toolName);
|
|
34810
|
-
saveInsightCounts();
|
|
34811
|
-
}
|
|
34812
|
-
if (source && DECISION_ROUTE_SOURCES.has(source)) entry.decisionSource = source;
|
|
34813
|
-
if (entry.waiter) {
|
|
34814
|
-
entry.waiter(resolvedDecision, reason);
|
|
34815
|
-
pending.delete(id);
|
|
34816
|
-
broadcast("remove", { id, decision: resolvedDecision });
|
|
34817
|
-
} else {
|
|
34818
|
-
entry.earlyDecision = resolvedDecision;
|
|
34819
|
-
entry.earlyReason = reason;
|
|
34820
|
-
broadcast("remove", { id, decision: resolvedDecision });
|
|
34821
|
-
entry.timer = setTimeout(() => pending.delete(id), 3e4);
|
|
36219
|
+
if (card.kind === "discovery" && verdict === "allow" && card.request.serverKey) {
|
|
36220
|
+
approveServer(card.request.serverKey, []);
|
|
34822
36221
|
}
|
|
34823
|
-
res.writeHead(200);
|
|
34824
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36222
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36223
|
+
return res.end(JSON.stringify({ ok: true, record: outcome.record }));
|
|
34825
36224
|
} catch {
|
|
34826
36225
|
res.writeHead(400).end();
|
|
34827
36226
|
}
|
|
@@ -34926,34 +36325,18 @@ data: ${JSON.stringify(item.data)}
|
|
|
34926
36325
|
}
|
|
34927
36326
|
}
|
|
34928
36327
|
if (req.method === "POST" && pathname.startsWith("/resolve/")) {
|
|
34929
|
-
|
|
34930
|
-
if (internalAuth !== internalToken) return res.writeHead(403).end();
|
|
36328
|
+
if (!validToken(req)) return res.writeHead(403).end();
|
|
34931
36329
|
try {
|
|
34932
36330
|
const id = pathname.split("/").pop();
|
|
34933
|
-
|
|
34934
|
-
|
|
34935
|
-
const
|
|
34936
|
-
const
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
clearTimeout(entry.timer);
|
|
34943
|
-
if (resolvedResolveDecision === "allow") {
|
|
34944
|
-
insightCounts.set(entry.toolName, (insightCounts.get(entry.toolName) ?? 0) + 1);
|
|
34945
|
-
saveInsightCounts();
|
|
34946
|
-
} else {
|
|
34947
|
-
insightCounts.delete(entry.toolName);
|
|
34948
|
-
saveInsightCounts();
|
|
34949
|
-
}
|
|
34950
|
-
if (source && HUMAN_DECISION_SOURCES.has(source)) entry.decisionSource = source;
|
|
34951
|
-
if (entry.waiter) entry.waiter(resolvedResolveDecision);
|
|
34952
|
-
else entry.earlyDecision = resolvedResolveDecision;
|
|
34953
|
-
pending.delete(id);
|
|
34954
|
-
broadcast("remove", { id, decision: resolvedResolveDecision });
|
|
34955
|
-
res.writeHead(200);
|
|
34956
|
-
return res.end(JSON.stringify({ ok: true }));
|
|
36331
|
+
if (!cards.get(id)) return res.writeHead(404).end();
|
|
36332
|
+
const body = JSON.parse(await readBody(req));
|
|
36333
|
+
const record2 = parseDecisionRecord(body.record) ?? recordFromLegacyResolve(body.decision === "allow" ? "allow" : "deny", body.source);
|
|
36334
|
+
const outcome = cards.decide(id, record2);
|
|
36335
|
+
if (!outcome) return res.writeHead(404).end();
|
|
36336
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
36337
|
+
return res.end(
|
|
36338
|
+
JSON.stringify({ protocol: CARD_PROTOCOL, won: outcome.won, record: outcome.record })
|
|
36339
|
+
);
|
|
34957
36340
|
} catch {
|
|
34958
36341
|
res.writeHead(400).end();
|
|
34959
36342
|
}
|
|
@@ -35298,35 +36681,16 @@ data: ${JSON.stringify(item.data)}
|
|
|
35298
36681
|
args: { toolCount: tools.length, status },
|
|
35299
36682
|
decision: "mcp-discovered"
|
|
35300
36683
|
});
|
|
35301
|
-
const
|
|
35302
|
-
|
|
35303
|
-
|
|
35304
|
-
|
|
35305
|
-
|
|
35306
|
-
|
|
35307
|
-
|
|
35308
|
-
|
|
35309
|
-
|
|
35310
|
-
|
|
35311
|
-
timer: setTimeout(() => {
|
|
35312
|
-
if (pending.has(id)) {
|
|
35313
|
-
pending.delete(id);
|
|
35314
|
-
broadcast("remove", { id, decision: "deny" });
|
|
35315
|
-
}
|
|
35316
|
-
}, 6e4),
|
|
35317
|
-
// 60s timeout for discovery
|
|
35318
|
-
waiter: null,
|
|
35319
|
-
earlyDecision: null
|
|
35320
|
-
};
|
|
35321
|
-
pending.set(id, entry);
|
|
35322
|
-
broadcast("add", {
|
|
35323
|
-
id,
|
|
35324
|
-
type: "mcp-discovery",
|
|
35325
|
-
serverKey,
|
|
35326
|
-
mcpTools: tools,
|
|
35327
|
-
toolName: "mcp-discovery",
|
|
35328
|
-
args: { serverKey, toolCount: tools.length }
|
|
35329
|
-
});
|
|
36684
|
+
const card = cards.register(
|
|
36685
|
+
{
|
|
36686
|
+
toolName: "mcp-discovery",
|
|
36687
|
+
args: { serverKey, toolCount: tools.length },
|
|
36688
|
+
serverKey,
|
|
36689
|
+
mcpTools: tools
|
|
36690
|
+
},
|
|
36691
|
+
{ kind: "discovery", channels: ["terminal", "monitor"], timeoutMs: 6e4 }
|
|
36692
|
+
);
|
|
36693
|
+
broadcast("add", cardView(card));
|
|
35330
36694
|
}
|
|
35331
36695
|
res.writeHead(200).end();
|
|
35332
36696
|
return;
|
|
@@ -35359,20 +36723,22 @@ data: ${JSON.stringify(item.data)}
|
|
|
35359
36723
|
if (!validToken(req)) return res.writeHead(403).end();
|
|
35360
36724
|
try {
|
|
35361
36725
|
const { id, serverKey, disabledTools } = JSON.parse(await readBody(req));
|
|
35362
|
-
const
|
|
35363
|
-
if (!
|
|
36726
|
+
const card = typeof id === "string" ? cards.get(id) : void 0;
|
|
36727
|
+
if (!card || card.kind !== "discovery" || card.request.serverKey !== serverKey) {
|
|
35364
36728
|
res.writeHead(404).end();
|
|
35365
36729
|
return;
|
|
35366
36730
|
}
|
|
35367
|
-
|
|
36731
|
+
const outcome = cards.decide(card.id, personRecord("terminal", "allow"));
|
|
36732
|
+
if (!outcome?.won) {
|
|
36733
|
+
res.writeHead(409).end();
|
|
36734
|
+
return;
|
|
36735
|
+
}
|
|
35368
36736
|
approveServer(serverKey, disabledTools);
|
|
35369
36737
|
appendAuditLog({
|
|
35370
36738
|
toolName: `mcp-server:${serverKey}`,
|
|
35371
36739
|
args: { disabledTools },
|
|
35372
36740
|
decision: "allow"
|
|
35373
36741
|
});
|
|
35374
|
-
pending.delete(id);
|
|
35375
|
-
broadcast("remove", { id, decision: "allow" });
|
|
35376
36742
|
res.writeHead(200).end();
|
|
35377
36743
|
return;
|
|
35378
36744
|
} catch {
|
|
@@ -35605,7 +36971,7 @@ var init_server = __esm({
|
|
|
35605
36971
|
"use strict";
|
|
35606
36972
|
init_migrate();
|
|
35607
36973
|
init_trusted_recipients();
|
|
35608
|
-
|
|
36974
|
+
init_decision_record();
|
|
35609
36975
|
init_core();
|
|
35610
36976
|
init_keyed_guard();
|
|
35611
36977
|
init_scan();
|
|
@@ -35864,14 +37230,14 @@ async function onboardMachine(apiKey, opts = {}) {
|
|
|
35864
37230
|
detail: `${sync.rules} cloud rule${sync.rules === 1 ? "" : "s"} active`
|
|
35865
37231
|
} : { name: "policy-sync", ok: false, detail: sync.reason }
|
|
35866
37232
|
);
|
|
35867
|
-
const
|
|
37233
|
+
const push2 = await runPolicyPush();
|
|
35868
37234
|
steps.push(
|
|
35869
|
-
|
|
37235
|
+
push2.ok ? { name: "register", ok: true, detail: "machine visible in the dashboard" } : { name: "register", ok: false, detail: push2.reason }
|
|
35870
37236
|
);
|
|
35871
37237
|
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
35872
37238
|
let detail = healed === "repaired" ? "autostart re-enabled (survives reboot)" : isDaemonRunning() ? "running" : healed === "unsupported" ? "no background service on this platform: starts on agent activity" : "starts on agent activity (make it survive reboots: node9 daemon install)";
|
|
35873
37239
|
let daemonOk = true;
|
|
35874
|
-
if (sync.ok &&
|
|
37240
|
+
if (sync.ok && push2.ok && isInteractive() && mayChangeService({ stdoutTTY: !!process.stdout.isTTY, ci: isCI() }) && autostartState() === "absent") {
|
|
35875
37241
|
try {
|
|
35876
37242
|
if (await confirm2({
|
|
35877
37243
|
message: "Run node9 in the background when this computer starts?",
|
|
@@ -35996,14 +37362,14 @@ var init_open_browser = __esm({
|
|
|
35996
37362
|
});
|
|
35997
37363
|
|
|
35998
37364
|
// src/utils/post-json.ts
|
|
35999
|
-
import * as
|
|
37365
|
+
import * as http5 from "http";
|
|
36000
37366
|
import * as https5 from "https";
|
|
36001
37367
|
import { URL as URL4 } from "url";
|
|
36002
37368
|
function postJson2(url, body, bearer) {
|
|
36003
37369
|
return new Promise((resolve2, reject) => {
|
|
36004
37370
|
const payload = JSON.stringify(body);
|
|
36005
37371
|
const u = new URL4(url);
|
|
36006
|
-
const lib = u.protocol === "http:" ?
|
|
37372
|
+
const lib = u.protocol === "http:" ? http5 : https5;
|
|
36007
37373
|
const req = lib.request(
|
|
36008
37374
|
{
|
|
36009
37375
|
method: "POST",
|
|
@@ -45402,7 +46768,7 @@ var require_client = __commonJS({
|
|
|
45402
46768
|
"use strict";
|
|
45403
46769
|
var assert = __require("assert");
|
|
45404
46770
|
var net3 = __require("net");
|
|
45405
|
-
var
|
|
46771
|
+
var http9 = __require("http");
|
|
45406
46772
|
var util = require_util();
|
|
45407
46773
|
var { ClientStats } = require_stats();
|
|
45408
46774
|
var { channels } = require_diagnostics();
|
|
@@ -45460,7 +46826,7 @@ var require_client = __commonJS({
|
|
|
45460
46826
|
var connectH1 = require_client_h1();
|
|
45461
46827
|
var connectH2 = require_client_h2();
|
|
45462
46828
|
var kClosedResolve = /* @__PURE__ */ Symbol("kClosedResolve");
|
|
45463
|
-
var getDefaultNodeMaxHeaderSize =
|
|
46829
|
+
var getDefaultNodeMaxHeaderSize = http9 && http9.maxHeaderSize && Number.isInteger(http9.maxHeaderSize) && http9.maxHeaderSize > 0 ? () => http9.maxHeaderSize : () => {
|
|
45464
46830
|
throw new InvalidArgumentError("http module not available or http.maxHeaderSize invalid");
|
|
45465
46831
|
};
|
|
45466
46832
|
var noop = () => {
|
|
@@ -46077,8 +47443,8 @@ var require_pool_base = __commonJS({
|
|
|
46077
47443
|
}
|
|
46078
47444
|
get [kPending]() {
|
|
46079
47445
|
let ret = this[kQueued];
|
|
46080
|
-
for (const { [kPending]:
|
|
46081
|
-
ret +=
|
|
47446
|
+
for (const { [kPending]: pending } of this[kClients]) {
|
|
47447
|
+
ret += pending;
|
|
46082
47448
|
}
|
|
46083
47449
|
return ret;
|
|
46084
47450
|
}
|
|
@@ -50507,19 +51873,19 @@ var require_mock_agent = __commonJS({
|
|
|
50507
51873
|
}
|
|
50508
51874
|
pendingInterceptors() {
|
|
50509
51875
|
const mockAgentClients = this[kClients];
|
|
50510
|
-
return Array.from(mockAgentClients.entries()).flatMap(([origin, result]) => result.dispatcher[kDispatches].map((dispatch) => ({ ...dispatch, origin }))).filter(({ pending
|
|
51876
|
+
return Array.from(mockAgentClients.entries()).flatMap(([origin, result]) => result.dispatcher[kDispatches].map((dispatch) => ({ ...dispatch, origin }))).filter(({ pending }) => pending);
|
|
50511
51877
|
}
|
|
50512
51878
|
assertNoPendingInterceptors({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) {
|
|
50513
|
-
const
|
|
50514
|
-
if (
|
|
51879
|
+
const pending = this.pendingInterceptors();
|
|
51880
|
+
if (pending.length === 0) {
|
|
50515
51881
|
return;
|
|
50516
51882
|
}
|
|
50517
51883
|
throw new UndiciError(
|
|
50518
|
-
|
|
51884
|
+
pending.length === 1 ? `1 interceptor is pending:
|
|
50519
51885
|
|
|
50520
|
-
${pendingInterceptorsFormatter.format(
|
|
51886
|
+
${pendingInterceptorsFormatter.format(pending)}`.trim() : `${pending.length} interceptors are pending:
|
|
50521
51887
|
|
|
50522
|
-
${pendingInterceptorsFormatter.format(
|
|
51888
|
+
${pendingInterceptorsFormatter.format(pending)}`.trim()
|
|
50523
51889
|
);
|
|
50524
51890
|
}
|
|
50525
51891
|
};
|
|
@@ -62828,12 +64194,13 @@ var init_first_run = __esm({
|
|
|
62828
64194
|
var tail_exports = {};
|
|
62829
64195
|
__export(tail_exports, {
|
|
62830
64196
|
agentLabel: () => agentLabel,
|
|
64197
|
+
deadlineLine: () => deadlineLine,
|
|
62831
64198
|
eventsUrl: () => eventsUrl,
|
|
62832
64199
|
sessionTag: () => sessionTag,
|
|
62833
64200
|
shortenPathSummary: () => shortenPathSummary,
|
|
62834
64201
|
startTail: () => startTail
|
|
62835
64202
|
});
|
|
62836
|
-
import
|
|
64203
|
+
import http7 from "http";
|
|
62837
64204
|
import chalk47 from "chalk";
|
|
62838
64205
|
import fs97 from "fs";
|
|
62839
64206
|
import os80 from "os";
|
|
@@ -63045,7 +64412,7 @@ function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
|
63045
64412
|
if (opts?.trustRecipients) bodyObj.trustRecipients = true;
|
|
63046
64413
|
if (opts?.reason) bodyObj.reason = opts.reason;
|
|
63047
64414
|
const body = JSON.stringify(bodyObj);
|
|
63048
|
-
const req =
|
|
64415
|
+
const req = http7.request(
|
|
63049
64416
|
{
|
|
63050
64417
|
hostname: "127.0.0.1",
|
|
63051
64418
|
port,
|
|
@@ -63061,9 +64428,21 @@ function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
|
63061
64428
|
}
|
|
63062
64429
|
},
|
|
63063
64430
|
(res) => {
|
|
63064
|
-
|
|
63065
|
-
|
|
63066
|
-
|
|
64431
|
+
let text = "";
|
|
64432
|
+
res.setEncoding("utf8");
|
|
64433
|
+
res.on("data", (c) => text += c);
|
|
64434
|
+
res.on("end", () => {
|
|
64435
|
+
if (res.statusCode === 200) return resolve2(null);
|
|
64436
|
+
if (res.statusCode === 409) {
|
|
64437
|
+
try {
|
|
64438
|
+
const r = JSON.parse(text).record;
|
|
64439
|
+
if (r?.decision && r.label) return resolve2({ decision: r.decision, label: r.label });
|
|
64440
|
+
} catch {
|
|
64441
|
+
}
|
|
64442
|
+
return resolve2(null);
|
|
64443
|
+
}
|
|
64444
|
+
reject(new Error(`POST /decision returned ${res.statusCode}`));
|
|
64445
|
+
});
|
|
63067
64446
|
}
|
|
63068
64447
|
);
|
|
63069
64448
|
req.on("error", reject);
|
|
@@ -63092,6 +64471,15 @@ function cleanBlockedBy(raw) {
|
|
|
63092
64471
|
if (smartMatch) return smartMatch[1];
|
|
63093
64472
|
return raw;
|
|
63094
64473
|
}
|
|
64474
|
+
function deadlineLine(deadline, now = Date.now()) {
|
|
64475
|
+
if (deadline === void 0) return null;
|
|
64476
|
+
if (deadline === null) return "No timeout: waits for your answer";
|
|
64477
|
+
const left = Math.max(0, Math.round((deadline - now) / 1e3));
|
|
64478
|
+
const m = Math.floor(left / 60);
|
|
64479
|
+
const sec = left % 60;
|
|
64480
|
+
const at = new Date(deadline).toLocaleTimeString([], { hour12: false });
|
|
64481
|
+
return `Auto-denies in ${m > 0 ? `${m}m ` : ""}${sec}s (at ${at}) if nobody answers`;
|
|
64482
|
+
}
|
|
63095
64483
|
function buildCardLines(req, localCount = 0) {
|
|
63096
64484
|
if (req.recoveryCommand) {
|
|
63097
64485
|
return buildRecoveryCardLines(req);
|
|
@@ -63131,8 +64519,10 @@ function buildCardLines(req, localCount = 0) {
|
|
|
63131
64519
|
`${CYAN}\u2551${RESET} ${YELLOW}\u{1F4A1}${RESET} Approved ${localCount}\xD7 before \u2014 ${BOLD}[a]${RESET}${YELLOW} creates a permanent rule${RESET}`
|
|
63132
64520
|
);
|
|
63133
64521
|
}
|
|
64522
|
+
const timing = deadlineLine(req.deadline);
|
|
63134
64523
|
lines.push(
|
|
63135
64524
|
`${CYAN}\u255A${RESET}`,
|
|
64525
|
+
...timing ? [` ${GRAY}${timing}${RESET}`] : [],
|
|
63136
64526
|
``,
|
|
63137
64527
|
exposure.length > 0 ? ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${CYAN}[s]${RESET} Allow + trust ${exposure.length === 1 ? "this address" : "these addresses"}` : ` ${BOLD}${GREEN}[\u21B5/y]${RESET} Allow ${BOLD}${RED}[n]${RESET} Deny ${BOLD}${YELLOW}[a]${RESET} Always Allow ${BOLD}${CYAN}[t]${RESET} Trust 30m`,
|
|
63138
64528
|
``
|
|
@@ -63149,7 +64539,7 @@ function buildRecoveryCardLines(req) {
|
|
|
63149
64539
|
` ${BOLD}${YELLOW}[2]${RESET} Redirect AI: "Run '${recoveryCommand}' first, then retry"`,
|
|
63150
64540
|
` ${BOLD}${RED}[3]${RESET} Deny & stop ${GRAY}(hard block)${RESET}`,
|
|
63151
64541
|
``,
|
|
63152
|
-
` ${GRAY}
|
|
64542
|
+
` ${GRAY}${deadlineLine(req.deadline) ?? "Auto-denies at the approval timeout if nobody answers"}${RESET}`,
|
|
63153
64543
|
` Select [1-3]: `
|
|
63154
64544
|
];
|
|
63155
64545
|
return [
|
|
@@ -63196,7 +64586,7 @@ async function startTail(options = {}) {
|
|
|
63196
64586
|
const port = await ensureDaemon();
|
|
63197
64587
|
if (options.clear) {
|
|
63198
64588
|
const result = await new Promise((resolve2) => {
|
|
63199
|
-
const req2 =
|
|
64589
|
+
const req2 = http7.request(
|
|
63200
64590
|
{ method: "POST", hostname: "127.0.0.1", port, path: "/events/clear" },
|
|
63201
64591
|
(res) => {
|
|
63202
64592
|
const status = res.statusCode ?? 0;
|
|
@@ -63359,7 +64749,13 @@ async function startTail(options = {}) {
|
|
|
63359
64749
|
} else {
|
|
63360
64750
|
httpDecision = action;
|
|
63361
64751
|
}
|
|
63362
|
-
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).
|
|
64752
|
+
postDecisionHttp(req2.id, httpDecision, authToken, port, httpOpts).then((won) => {
|
|
64753
|
+
if (won && !cardActive)
|
|
64754
|
+
process.stdout.write(
|
|
64755
|
+
` ${YELLOW}\u21B3 Already decided before your answer: ${won.decision.toUpperCase()} (${safeMessage(won.label, 120)})${RESET}
|
|
64756
|
+
`
|
|
64757
|
+
);
|
|
64758
|
+
}).catch((err2) => {
|
|
63363
64759
|
try {
|
|
63364
64760
|
fs97.appendFileSync(
|
|
63365
64761
|
path94.join(os80.homedir(), ".node9", "hook-debug.log"),
|
|
@@ -63484,7 +64880,7 @@ async function startTail(options = {}) {
|
|
|
63484
64880
|
}, STALL_THRESHOLD_MS / 2);
|
|
63485
64881
|
stallWatchdog.unref();
|
|
63486
64882
|
const sseUrl = eventsUrl(port, canApprove);
|
|
63487
|
-
const req =
|
|
64883
|
+
const req = http7.get(
|
|
63488
64884
|
sseUrl,
|
|
63489
64885
|
{
|
|
63490
64886
|
headers: authToken ? { "X-Node9-Internal": authToken } : {}
|
|
@@ -63702,7 +65098,7 @@ __export(hud_exports, {
|
|
|
63702
65098
|
import fs98 from "fs";
|
|
63703
65099
|
import path95 from "path";
|
|
63704
65100
|
import os81 from "os";
|
|
63705
|
-
import
|
|
65101
|
+
import http8 from "http";
|
|
63706
65102
|
async function readStdin() {
|
|
63707
65103
|
const chunks = [];
|
|
63708
65104
|
for await (const chunk2 of process.stdin) {
|
|
@@ -63720,7 +65116,7 @@ function queryDaemon() {
|
|
|
63720
65116
|
return new Promise((resolve2) => {
|
|
63721
65117
|
const timeout = setTimeout(() => resolve2(null), 50);
|
|
63722
65118
|
try {
|
|
63723
|
-
const req =
|
|
65119
|
+
const req = http8.get(
|
|
63724
65120
|
`http://${DAEMON_HOST}:${DAEMON_PORT}/status`,
|
|
63725
65121
|
{ timeout: 50 },
|
|
63726
65122
|
(res) => {
|
|
@@ -64191,7 +65587,7 @@ async function runProxy(targetCommand) {
|
|
|
64191
65587
|
console.error(chalk9.gray(` Reason: ${result.reason || "Security Policy"}
|
|
64192
65588
|
`));
|
|
64193
65589
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
64194
|
-
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
65590
|
+
const isHuman = isHumanDecision(blockedByLabel, result.blockedBy, result.decidedBy);
|
|
64195
65591
|
const aiInstruction = buildNegotiationMessage(
|
|
64196
65592
|
blockedByLabel,
|
|
64197
65593
|
isHuman,
|
|
@@ -64199,7 +65595,8 @@ async function runProxy(targetCommand) {
|
|
|
64199
65595
|
void 0,
|
|
64200
65596
|
{
|
|
64201
65597
|
kind: result.blockKind,
|
|
64202
|
-
path: result.blockedPath
|
|
65598
|
+
path: result.blockedPath,
|
|
65599
|
+
redirect: !!result.redirect
|
|
64203
65600
|
}
|
|
64204
65601
|
);
|
|
64205
65602
|
const errorResponse = {
|
|
@@ -64873,7 +66270,11 @@ RAW: ${raw}
|
|
|
64873
66270
|
const mcpServer = mcpMatch?.[1];
|
|
64874
66271
|
const sendBlock = (msg, result2) => {
|
|
64875
66272
|
const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || (isKeyedForPolicy() ? "Workspace Policy" : "Local Security Policy");
|
|
64876
|
-
const humanDecision = isHumanDecision(
|
|
66273
|
+
const humanDecision = isHumanDecision(
|
|
66274
|
+
blockedByContext,
|
|
66275
|
+
result2?.blockedBy,
|
|
66276
|
+
result2?.decidedBy
|
|
66277
|
+
);
|
|
64877
66278
|
let ttyFd = null;
|
|
64878
66279
|
try {
|
|
64879
66280
|
ttyFd = fs70.openSync("/dev/tty", "w");
|
|
@@ -64913,7 +66314,7 @@ RAW: ${raw}
|
|
|
64913
66314
|
humanDecision,
|
|
64914
66315
|
msg,
|
|
64915
66316
|
result2?.recoveryCommand,
|
|
64916
|
-
{ kind: result2?.blockKind, path: result2?.blockedPath }
|
|
66317
|
+
{ kind: result2?.blockKind, path: result2?.blockedPath, redirect: !!result2?.redirect }
|
|
64917
66318
|
);
|
|
64918
66319
|
if (agent === "Antigravity") {
|
|
64919
66320
|
process.stdout.write(
|
|
@@ -65318,7 +66719,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65318
66719
|
let size = 0;
|
|
65319
66720
|
let fields = 0;
|
|
65320
66721
|
let truncated = false;
|
|
65321
|
-
const
|
|
66722
|
+
const push2 = (s) => {
|
|
65322
66723
|
if (s.length === 0) return true;
|
|
65323
66724
|
const room = maxBytes - size;
|
|
65324
66725
|
if (s.length > room) {
|
|
@@ -65336,7 +66737,7 @@ function collectToolOutputText(toolResponse, opts = {}) {
|
|
|
65336
66737
|
return true;
|
|
65337
66738
|
};
|
|
65338
66739
|
const walk2 = (v, depth) => {
|
|
65339
|
-
if (typeof v === "string") return
|
|
66740
|
+
if (typeof v === "string") return push2(v);
|
|
65340
66741
|
if (depth >= MAX_DEPTH5 || v === null || typeof v !== "object") return true;
|
|
65341
66742
|
const children = Array.isArray(v) ? v : Object.values(v);
|
|
65342
66743
|
for (const child of children) if (!walk2(child, depth + 1)) return false;
|
|
@@ -67356,11 +68757,15 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
67356
68757
|
let dimAppsBlocked = 0;
|
|
67357
68758
|
let dimPinMismatch = 0;
|
|
67358
68759
|
let dimInjectionBlocked = 0;
|
|
68760
|
+
const hookCardIds = new Set(
|
|
68761
|
+
entries.filter((e) => e.source !== "daemon" && e.cardId).map((e) => e.cardId)
|
|
68762
|
+
);
|
|
67359
68763
|
for (const e of entries) {
|
|
68764
|
+
if (e.source === "daemon" && e.cardId && hookCardIds.has(e.cardId)) continue;
|
|
67360
68765
|
if (superseded.has(supersedeKey(e))) continue;
|
|
67361
68766
|
const view = viewOf(e);
|
|
67362
68767
|
const dateKey = e.ts.slice(0, 10);
|
|
67363
|
-
const userInteracted = e.source === "daemon";
|
|
68768
|
+
const userInteracted = e.decidedBy ? e.decidedBy === "person" : e.source === "daemon";
|
|
67364
68769
|
if (view.observed) {
|
|
67365
68770
|
if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
67366
68771
|
} else if (view.info) {
|
|
@@ -67368,7 +68773,8 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
67368
68773
|
if (view.ran) userApproved++;
|
|
67369
68774
|
else userDenied++;
|
|
67370
68775
|
} else if (view.blocked) {
|
|
67371
|
-
if (e.checkedBy === "timeout")
|
|
68776
|
+
if (e.checkedBy === "timeout" || e.decidedBy === "timer" || e.decidedBy === "abandoned")
|
|
68777
|
+
timedOut++;
|
|
67372
68778
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
67373
68779
|
else if (e.checkedBy === "local-decision") userDenied++;
|
|
67374
68780
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
@@ -68358,7 +69764,7 @@ function registerHealCommand(program2) {
|
|
|
68358
69764
|
init_onboarding();
|
|
68359
69765
|
init_cloud_endpoints();
|
|
68360
69766
|
init_safe_text();
|
|
68361
|
-
import
|
|
69767
|
+
import http6 from "http";
|
|
68362
69768
|
import https8 from "https";
|
|
68363
69769
|
import { URL as URL5 } from "url";
|
|
68364
69770
|
import chalk22 from "chalk";
|
|
@@ -68369,7 +69775,7 @@ function postConnect(url, token) {
|
|
|
68369
69775
|
return new Promise((resolve2, reject) => {
|
|
68370
69776
|
const body = JSON.stringify({ token });
|
|
68371
69777
|
const u = new URL5(url);
|
|
68372
|
-
const lib = u.protocol === "http:" ?
|
|
69778
|
+
const lib = u.protocol === "http:" ? http6 : https8;
|
|
68373
69779
|
const req = lib.request(
|
|
68374
69780
|
{
|
|
68375
69781
|
method: "POST",
|
|
@@ -68508,7 +69914,7 @@ init_safe_text();
|
|
|
68508
69914
|
import readline3 from "readline";
|
|
68509
69915
|
import chalk24 from "chalk";
|
|
68510
69916
|
import { spawn as spawn8 } from "child_process";
|
|
68511
|
-
import { randomUUID as
|
|
69917
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
68512
69918
|
import { execa as execa2 } from "execa";
|
|
68513
69919
|
|
|
68514
69920
|
// src/mcp-gateway/result-scan.ts
|
|
@@ -68774,7 +70180,7 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68774
70180
|
return false;
|
|
68775
70181
|
}
|
|
68776
70182
|
};
|
|
68777
|
-
const gatewaySessionId = `mcp-gateway-${
|
|
70183
|
+
const gatewaySessionId = `mcp-gateway-${randomUUID9()}`;
|
|
68778
70184
|
const readOnlyTools = /* @__PURE__ */ new Set();
|
|
68779
70185
|
let clientName;
|
|
68780
70186
|
const agentIn = readline3.createInterface({ input: process.stdin, terminal: false });
|
|
@@ -68873,13 +70279,13 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
68873
70279
|
console.error(chalk24.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
68874
70280
|
`));
|
|
68875
70281
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
68876
|
-
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy);
|
|
70282
|
+
const humanDecision = isHumanDecision(blockedByLabel, result.blockedBy, result.decidedBy);
|
|
68877
70283
|
const aiInstruction = buildNegotiationMessage(
|
|
68878
70284
|
blockedByLabel,
|
|
68879
70285
|
humanDecision,
|
|
68880
70286
|
result.reason,
|
|
68881
70287
|
void 0,
|
|
68882
|
-
{ kind: result.blockKind, path: result.blockedPath }
|
|
70288
|
+
{ kind: result.blockKind, path: result.blockedPath, redirect: !!result.redirect }
|
|
68883
70289
|
);
|
|
68884
70290
|
const errorResponse = {
|
|
68885
70291
|
jsonrpc: "2.0",
|
|
@@ -70146,7 +71552,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
70146
71552
|
}
|
|
70147
71553
|
const ungoverned = rows.filter((e) => e.connection === "ungoverned").length;
|
|
70148
71554
|
const unlaunchable = rows.filter((e) => e.connection === "unlaunchable").length;
|
|
70149
|
-
const
|
|
71555
|
+
const pending = rows.filter((e) => e.connection === "pending-launch").length;
|
|
70150
71556
|
if (ungoverned > 0) {
|
|
70151
71557
|
console.error(
|
|
70152
71558
|
chalk26.yellow(
|
|
@@ -70155,9 +71561,9 @@ ${ungoverned} ungoverned \u2014 run ` + chalk26.bold("node9 mcp gateway --all")
|
|
|
70155
71561
|
)
|
|
70156
71562
|
);
|
|
70157
71563
|
}
|
|
70158
|
-
if (
|
|
71564
|
+
if (pending > 0) {
|
|
70159
71565
|
console.error(
|
|
70160
|
-
chalk26.cyan(`${
|
|
71566
|
+
chalk26.cyan(`${pending} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
70161
71567
|
);
|
|
70162
71568
|
}
|
|
70163
71569
|
if (unlaunchable > 0) {
|
|
@@ -75963,7 +77369,7 @@ function addHost(list, host2) {
|
|
|
75963
77369
|
if (!cliGuardPolicyWrite(`egress ${list} ${host2}`)) return false;
|
|
75964
77370
|
return guard(() => addEgressHost(list, host2));
|
|
75965
77371
|
}
|
|
75966
|
-
function
|
|
77372
|
+
function exempt2(address) {
|
|
75967
77373
|
if (!cliGuardPolicyWrite(`egress exempt ${address}`)) return false;
|
|
75968
77374
|
return guard(() => addSsrfExemption(address));
|
|
75969
77375
|
}
|
|
@@ -76146,7 +77552,7 @@ function registerEgressCommand(program2) {
|
|
|
76146
77552
|
process.exitCode = 1;
|
|
76147
77553
|
return;
|
|
76148
77554
|
}
|
|
76149
|
-
if (!
|
|
77555
|
+
if (!exempt2(a)) return;
|
|
76150
77556
|
console.log(chalk34.green(`
|
|
76151
77557
|
\u2713 ${a} is exempt from the floor.`));
|
|
76152
77558
|
if (!m)
|
|
@@ -77935,17 +79341,18 @@ function registerSessionExposureCommand(program2) {
|
|
|
77935
79341
|
init_osv_index();
|
|
77936
79342
|
init_osv_sync();
|
|
77937
79343
|
import chalk42 from "chalk";
|
|
79344
|
+
var ECO_WIDTH = Math.max(...ECOSYSTEMS2.map((e) => e.length));
|
|
77938
79345
|
function registerPackageIndexCommand(program2) {
|
|
77939
79346
|
const cmd = program2.command("package-index").description("Local OSV malicious-package index used to check installs before they run");
|
|
77940
79347
|
cmd.command("status").description("Show when each ecosystem was last synced and how many records it holds").action(() => {
|
|
77941
79348
|
for (const eco of ECOSYSTEMS2) {
|
|
77942
79349
|
const meta = readMeta(eco);
|
|
77943
79350
|
if (!meta) {
|
|
77944
|
-
console.log(`${eco.padEnd(
|
|
79351
|
+
console.log(`${eco.padEnd(ECO_WIDTH)} ${chalk42.yellow("not synced")}`);
|
|
77945
79352
|
continue;
|
|
77946
79353
|
}
|
|
77947
79354
|
console.log(
|
|
77948
|
-
`${eco.padEnd(
|
|
79355
|
+
`${eco.padEnd(ECO_WIDTH)} ${meta.records} records synced ${meta.syncedAt} (${meta.mode})`
|
|
77949
79356
|
);
|
|
77950
79357
|
}
|
|
77951
79358
|
});
|
|
@@ -77955,9 +79362,9 @@ function registerPackageIndexCommand(program2) {
|
|
|
77955
79362
|
for (const r of results) {
|
|
77956
79363
|
if (r.error) {
|
|
77957
79364
|
failed = true;
|
|
77958
|
-
console.error(`${r.ecosystem.padEnd(
|
|
79365
|
+
console.error(`${r.ecosystem.padEnd(ECO_WIDTH)} ${chalk42.red("failed")}: ${r.error}`);
|
|
77959
79366
|
} else {
|
|
77960
|
-
console.log(`${r.ecosystem.padEnd(
|
|
79367
|
+
console.log(`${r.ecosystem.padEnd(ECO_WIDTH)} ${r.mode}: ${r.records} records`);
|
|
77961
79368
|
}
|
|
77962
79369
|
}
|
|
77963
79370
|
process.exitCode = failed ? 1 : 0;
|