@node9/policy-engine 2.14.0 → 2.14.2
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/index.d.mts +73 -3
- package/dist/index.d.ts +73 -3
- package/dist/index.js +635 -43
- package/dist/index.mjs +628 -43
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1121,6 +1121,19 @@ function parseShared(command) {
|
|
|
1121
1121
|
astCache.set(command, parsed);
|
|
1122
1122
|
return parsed;
|
|
1123
1123
|
}
|
|
1124
|
+
function byteOffsetToCharIndex(command) {
|
|
1125
|
+
if (!/[^\u0000-\u007F]/.test(command)) return null;
|
|
1126
|
+
const map = /* @__PURE__ */ new Map();
|
|
1127
|
+
let byte = 0;
|
|
1128
|
+
for (let i = 0; i < command.length; ) {
|
|
1129
|
+
map.set(byte, i);
|
|
1130
|
+
const cp = command.codePointAt(i);
|
|
1131
|
+
byte += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
1132
|
+
i += cp > 65535 ? 2 : 1;
|
|
1133
|
+
}
|
|
1134
|
+
map.set(byte, command.length);
|
|
1135
|
+
return (b) => map.get(b) ?? -1;
|
|
1136
|
+
}
|
|
1124
1137
|
function cachedNormalize(command, compute) {
|
|
1125
1138
|
const hit = normalizeCache.get(command);
|
|
1126
1139
|
if (hit !== void 0) {
|
|
@@ -1150,6 +1163,8 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1150
1163
|
const f = parseShared(command);
|
|
1151
1164
|
if (f === PARSE_FAIL) return { posix: command, separator: command };
|
|
1152
1165
|
try {
|
|
1166
|
+
const toCharIndex = byteOffsetToCharIndex(command);
|
|
1167
|
+
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
1153
1168
|
const strips = [];
|
|
1154
1169
|
const rewrites = [];
|
|
1155
1170
|
const quoteOnlyRewrites = [];
|
|
@@ -1170,8 +1185,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1170
1185
|
const quotedNode = nextParts[0];
|
|
1171
1186
|
const nt = syntax.NodeType(quotedNode);
|
|
1172
1187
|
const markStrip = () => {
|
|
1173
|
-
const s = next.Pos().Offset();
|
|
1174
|
-
const e = next.End().Offset();
|
|
1188
|
+
const s = at(next.Pos().Offset());
|
|
1189
|
+
const e = at(next.End().Offset());
|
|
1190
|
+
if (s < 0 || e < 0) return;
|
|
1175
1191
|
strips.push([s, e]);
|
|
1176
1192
|
msgSpans.add(`${s}:${e}`);
|
|
1177
1193
|
};
|
|
@@ -1188,8 +1204,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1188
1204
|
}
|
|
1189
1205
|
}
|
|
1190
1206
|
for (const arg of args) {
|
|
1191
|
-
const s = arg.Pos().Offset();
|
|
1192
|
-
const e = arg.End().Offset();
|
|
1207
|
+
const s = at(arg.Pos().Offset());
|
|
1208
|
+
const e = at(arg.End().Offset());
|
|
1209
|
+
if (s < 0 || e < 0) continue;
|
|
1193
1210
|
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
1194
1211
|
const resolved = resolveWordLiteral(arg);
|
|
1195
1212
|
if (resolved === null) continue;
|
|
@@ -1312,7 +1329,347 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1312
1329
|
"nl",
|
|
1313
1330
|
"dd"
|
|
1314
1331
|
]);
|
|
1332
|
+
var GREP_SHAPE = {
|
|
1333
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
1334
|
+
"-A",
|
|
1335
|
+
"-B",
|
|
1336
|
+
"-C",
|
|
1337
|
+
"-D",
|
|
1338
|
+
"-d",
|
|
1339
|
+
"-e",
|
|
1340
|
+
"-f",
|
|
1341
|
+
"-m",
|
|
1342
|
+
"--after-context",
|
|
1343
|
+
"--before-context",
|
|
1344
|
+
"--binary-files",
|
|
1345
|
+
"--context",
|
|
1346
|
+
"--devices",
|
|
1347
|
+
"--directories",
|
|
1348
|
+
"--exclude",
|
|
1349
|
+
"--exclude-dir",
|
|
1350
|
+
"--exclude-from",
|
|
1351
|
+
"--file",
|
|
1352
|
+
"--include",
|
|
1353
|
+
"--label",
|
|
1354
|
+
"--max-count",
|
|
1355
|
+
"--regexp"
|
|
1356
|
+
]),
|
|
1357
|
+
noValue: /* @__PURE__ */ new Set([
|
|
1358
|
+
"-E",
|
|
1359
|
+
"-F",
|
|
1360
|
+
"-G",
|
|
1361
|
+
"-P",
|
|
1362
|
+
"-i",
|
|
1363
|
+
"-y",
|
|
1364
|
+
"-v",
|
|
1365
|
+
"-V",
|
|
1366
|
+
"-w",
|
|
1367
|
+
"-x",
|
|
1368
|
+
"-c",
|
|
1369
|
+
"-l",
|
|
1370
|
+
"-L",
|
|
1371
|
+
"-o",
|
|
1372
|
+
"-q",
|
|
1373
|
+
"-s",
|
|
1374
|
+
"-b",
|
|
1375
|
+
"-H",
|
|
1376
|
+
"-h",
|
|
1377
|
+
"-n",
|
|
1378
|
+
"-T",
|
|
1379
|
+
"-Z",
|
|
1380
|
+
"-z",
|
|
1381
|
+
"-R",
|
|
1382
|
+
"-r",
|
|
1383
|
+
"-U",
|
|
1384
|
+
"-u",
|
|
1385
|
+
"-I",
|
|
1386
|
+
"-a",
|
|
1387
|
+
"--basic-regexp",
|
|
1388
|
+
"--binary",
|
|
1389
|
+
"--byte-offset",
|
|
1390
|
+
"--color",
|
|
1391
|
+
"--colour",
|
|
1392
|
+
"--count",
|
|
1393
|
+
"--dereference-recursive",
|
|
1394
|
+
"--extended-regexp",
|
|
1395
|
+
"--files-with-matches",
|
|
1396
|
+
"--files-without-match",
|
|
1397
|
+
"--fixed-strings",
|
|
1398
|
+
"--help",
|
|
1399
|
+
"--ignore-case",
|
|
1400
|
+
"--initial-tab",
|
|
1401
|
+
"--invert-match",
|
|
1402
|
+
"--line-buffered",
|
|
1403
|
+
"--line-number",
|
|
1404
|
+
"--line-regexp",
|
|
1405
|
+
"--no-filename",
|
|
1406
|
+
"--no-group-separator",
|
|
1407
|
+
"--no-ignore-case",
|
|
1408
|
+
"--no-messages",
|
|
1409
|
+
"--null",
|
|
1410
|
+
"--null-data",
|
|
1411
|
+
"--only-matching",
|
|
1412
|
+
"--perl-regexp",
|
|
1413
|
+
"--quiet",
|
|
1414
|
+
"--recursive",
|
|
1415
|
+
"--silent",
|
|
1416
|
+
"--text",
|
|
1417
|
+
"--version",
|
|
1418
|
+
"--with-filename",
|
|
1419
|
+
"--word-regexp"
|
|
1420
|
+
]),
|
|
1421
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
1422
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
|
|
1423
|
+
};
|
|
1424
|
+
var PATTERN_VERBS = {
|
|
1425
|
+
grep: GREP_SHAPE,
|
|
1426
|
+
// /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
|
|
1427
|
+
// `grep -E` and `grep -F`. Read in full, not assumed.
|
|
1428
|
+
egrep: GREP_SHAPE,
|
|
1429
|
+
fgrep: GREP_SHAPE,
|
|
1430
|
+
// ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
|
|
1431
|
+
// was not installed on the measuring machine; it is, and that stale claim is why
|
|
1432
|
+
// this table shipped incomplete for two rounds, blocking ordinary searches like
|
|
1433
|
+
// `rg --sort-files .env src` (/code-review round 4).
|
|
1434
|
+
rg: {
|
|
1435
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
1436
|
+
"-A",
|
|
1437
|
+
"-B",
|
|
1438
|
+
"-C",
|
|
1439
|
+
"-d",
|
|
1440
|
+
"-E",
|
|
1441
|
+
"-e",
|
|
1442
|
+
"-f",
|
|
1443
|
+
"-g",
|
|
1444
|
+
"-j",
|
|
1445
|
+
"-M",
|
|
1446
|
+
"-m",
|
|
1447
|
+
"-r",
|
|
1448
|
+
"-t",
|
|
1449
|
+
"-T",
|
|
1450
|
+
"--after-context",
|
|
1451
|
+
"--before-context",
|
|
1452
|
+
"--color",
|
|
1453
|
+
"--colors",
|
|
1454
|
+
"--context",
|
|
1455
|
+
"--context-separator",
|
|
1456
|
+
"--dfa-size-limit",
|
|
1457
|
+
"--encoding",
|
|
1458
|
+
"--engine",
|
|
1459
|
+
"--field-context-separator",
|
|
1460
|
+
"--field-match-separator",
|
|
1461
|
+
"--file",
|
|
1462
|
+
"--generate",
|
|
1463
|
+
"--glob",
|
|
1464
|
+
"--hostname-bin",
|
|
1465
|
+
"--hyperlink-format",
|
|
1466
|
+
"--iglob",
|
|
1467
|
+
"--ignore-file",
|
|
1468
|
+
"--max-columns",
|
|
1469
|
+
"--max-count",
|
|
1470
|
+
"--max-depth",
|
|
1471
|
+
// An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
|
|
1472
|
+
// alias lines as plain switches and swept it into noValue, which hard-blocked
|
|
1473
|
+
// `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
|
|
1474
|
+
"--maxdepth",
|
|
1475
|
+
"--max-filesize",
|
|
1476
|
+
"--path-separator",
|
|
1477
|
+
"--pre",
|
|
1478
|
+
"--pre-glob",
|
|
1479
|
+
"--regexp",
|
|
1480
|
+
"--regex-size-limit",
|
|
1481
|
+
"--replace",
|
|
1482
|
+
"--sort",
|
|
1483
|
+
"--sortr",
|
|
1484
|
+
"--threads",
|
|
1485
|
+
"--type",
|
|
1486
|
+
"--type-add",
|
|
1487
|
+
"--type-clear",
|
|
1488
|
+
"--type-not"
|
|
1489
|
+
]),
|
|
1490
|
+
noValue: /* @__PURE__ */ new Set([
|
|
1491
|
+
"-.",
|
|
1492
|
+
"-0",
|
|
1493
|
+
"-a",
|
|
1494
|
+
"-b",
|
|
1495
|
+
"-c",
|
|
1496
|
+
"-F",
|
|
1497
|
+
"-h",
|
|
1498
|
+
"-H",
|
|
1499
|
+
"-i",
|
|
1500
|
+
"-I",
|
|
1501
|
+
"-l",
|
|
1502
|
+
"-L",
|
|
1503
|
+
"-n",
|
|
1504
|
+
"-N",
|
|
1505
|
+
"-o",
|
|
1506
|
+
"-p",
|
|
1507
|
+
"-P",
|
|
1508
|
+
"-q",
|
|
1509
|
+
"-s",
|
|
1510
|
+
"-S",
|
|
1511
|
+
"-u",
|
|
1512
|
+
"-U",
|
|
1513
|
+
"-v",
|
|
1514
|
+
"-V",
|
|
1515
|
+
"-w",
|
|
1516
|
+
"-x",
|
|
1517
|
+
"-z",
|
|
1518
|
+
"--auto-hybrid-regex",
|
|
1519
|
+
"--binary",
|
|
1520
|
+
"--block-buffered",
|
|
1521
|
+
"--byte-offset",
|
|
1522
|
+
"--case-sensitive",
|
|
1523
|
+
"--column",
|
|
1524
|
+
"--count",
|
|
1525
|
+
"--count-matches",
|
|
1526
|
+
"--crlf",
|
|
1527
|
+
"--debug",
|
|
1528
|
+
"--files",
|
|
1529
|
+
"--files-with-matches",
|
|
1530
|
+
"--files-without-match",
|
|
1531
|
+
"--fixed-strings",
|
|
1532
|
+
"--follow",
|
|
1533
|
+
"--glob-case-insensitive",
|
|
1534
|
+
"--heading",
|
|
1535
|
+
"--help",
|
|
1536
|
+
"--hidden",
|
|
1537
|
+
"--ignore-case",
|
|
1538
|
+
"--ignore-file-case-insensitive",
|
|
1539
|
+
"--include-zero",
|
|
1540
|
+
"--invert-match",
|
|
1541
|
+
"--json",
|
|
1542
|
+
"--line-buffered",
|
|
1543
|
+
"--line-number",
|
|
1544
|
+
"--line-regexp",
|
|
1545
|
+
"--max-columns-preview",
|
|
1546
|
+
"--mmap",
|
|
1547
|
+
"--multiline",
|
|
1548
|
+
"--multiline-dotall",
|
|
1549
|
+
"--no-column",
|
|
1550
|
+
"--no-config",
|
|
1551
|
+
"--no-context-separator",
|
|
1552
|
+
"--no-encoding",
|
|
1553
|
+
"--no-filename",
|
|
1554
|
+
"--no-ignore",
|
|
1555
|
+
"--no-ignore-dot",
|
|
1556
|
+
"--no-ignore-exclude",
|
|
1557
|
+
"--no-ignore-files",
|
|
1558
|
+
"--no-ignore-global",
|
|
1559
|
+
"--no-ignore-messages",
|
|
1560
|
+
"--no-ignore-parent",
|
|
1561
|
+
"--no-ignore-vcs",
|
|
1562
|
+
"--no-line-number",
|
|
1563
|
+
"--no-messages",
|
|
1564
|
+
"--no-pcre2-unicode",
|
|
1565
|
+
"--no-pre",
|
|
1566
|
+
"--no-require-git",
|
|
1567
|
+
"--no-unicode",
|
|
1568
|
+
"--null",
|
|
1569
|
+
"--null-data",
|
|
1570
|
+
"--one-file-system",
|
|
1571
|
+
"--only-matching",
|
|
1572
|
+
"--passthru",
|
|
1573
|
+
"--pcre2",
|
|
1574
|
+
"--pcre2-version",
|
|
1575
|
+
"--pretty",
|
|
1576
|
+
"--print0",
|
|
1577
|
+
"--quiet",
|
|
1578
|
+
"--search-zip",
|
|
1579
|
+
"--smart-case",
|
|
1580
|
+
"--sort-files",
|
|
1581
|
+
"--stats",
|
|
1582
|
+
"--stop-on-nonmatch",
|
|
1583
|
+
"--text",
|
|
1584
|
+
"--trace",
|
|
1585
|
+
"--trim",
|
|
1586
|
+
"--type-list",
|
|
1587
|
+
"--unrestricted",
|
|
1588
|
+
"--version",
|
|
1589
|
+
"--vimgrep",
|
|
1590
|
+
"--with-filename",
|
|
1591
|
+
"--word-regexp",
|
|
1592
|
+
// The COMPLETE remainder of `rg --help`, added after /code-review round 5
|
|
1593
|
+
// found 40 documented switches in neither set. The table is now the whole
|
|
1594
|
+
// option list: `rg --help` yields 149 long options, 35 of them value-taking.
|
|
1595
|
+
"--ignore",
|
|
1596
|
+
"--ignore-dot",
|
|
1597
|
+
"--ignore-exclude",
|
|
1598
|
+
"--ignore-files",
|
|
1599
|
+
"--ignore-global",
|
|
1600
|
+
"--ignore-messages",
|
|
1601
|
+
"--ignore-parent",
|
|
1602
|
+
"--ignore-vcs",
|
|
1603
|
+
"--messages",
|
|
1604
|
+
"--no-auto-hybrid-regex",
|
|
1605
|
+
"--no-binary",
|
|
1606
|
+
"--no-block-buffered",
|
|
1607
|
+
"--no-byte-offset",
|
|
1608
|
+
"--no-crlf",
|
|
1609
|
+
"--no-fixed-strings",
|
|
1610
|
+
"--no-follow",
|
|
1611
|
+
"--no-glob-case-insensitive",
|
|
1612
|
+
"--no-heading",
|
|
1613
|
+
"--no-hidden",
|
|
1614
|
+
"--no-ignore-file-case-insensitive",
|
|
1615
|
+
"--no-include-zero",
|
|
1616
|
+
"--no-invert-match",
|
|
1617
|
+
"--no-json",
|
|
1618
|
+
"--no-line-buffered",
|
|
1619
|
+
"--no-max-columns-preview",
|
|
1620
|
+
"--no-mmap",
|
|
1621
|
+
"--no-multiline",
|
|
1622
|
+
"--no-multiline-dotall",
|
|
1623
|
+
"--no-one-file-system",
|
|
1624
|
+
"--no-pcre2",
|
|
1625
|
+
"--no-search-zip",
|
|
1626
|
+
"--no-sort-files",
|
|
1627
|
+
"--no-stats",
|
|
1628
|
+
"--no-text",
|
|
1629
|
+
"--no-trim",
|
|
1630
|
+
"--passthrough",
|
|
1631
|
+
"--pcre2-unicode",
|
|
1632
|
+
"--require-git",
|
|
1633
|
+
"--unicode"
|
|
1634
|
+
]),
|
|
1635
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
1636
|
+
// `--files` and `--type-list` list or enumerate without a pattern. Founder
|
|
1637
|
+
// decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
|
|
1638
|
+
// by leaving the directory in the judged list.
|
|
1639
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
var PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
|
|
1643
|
+
var patternShapeOf = (verb) => PATTERN_VERBS[verb];
|
|
1644
|
+
var fileOperandFlagsOf = (verb) => FILE_OPERAND_FLAGS[verb];
|
|
1645
|
+
var GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
|
|
1646
|
+
var READER_VALUE_LETTERS = {
|
|
1647
|
+
awk: ["F", "v", "f"],
|
|
1648
|
+
gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
|
|
1649
|
+
sed: ["e", "f", "i", "l"],
|
|
1650
|
+
sort: ["C", "k", "o", "S", "t", "T"]
|
|
1651
|
+
};
|
|
1652
|
+
var FILE_OPERAND_FLAGS = {
|
|
1653
|
+
grep: GREP_FILE_OPERANDS,
|
|
1654
|
+
egrep: GREP_FILE_OPERANDS,
|
|
1655
|
+
fgrep: GREP_FILE_OPERANDS,
|
|
1656
|
+
// rg and gawk are NOT installed on the measuring machine: these two rows are
|
|
1657
|
+
// from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
|
|
1658
|
+
// `-f/--file`) and are marked as such in the spec.
|
|
1659
|
+
// `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
|
|
1660
|
+
// a credential makes rg open it -- the same reason grep's `--include` is here.
|
|
1661
|
+
// Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
|
|
1662
|
+
// and printed its contents.
|
|
1663
|
+
rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
|
|
1664
|
+
sed: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1665
|
+
awk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1666
|
+
gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1667
|
+
sort: /* @__PURE__ */ new Set(["--files0-from"])
|
|
1668
|
+
};
|
|
1315
1669
|
var SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
1670
|
+
var TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
|
|
1671
|
+
var ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
|
|
1672
|
+
var RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
|
|
1316
1673
|
var RSYNC_SKIP = [
|
|
1317
1674
|
"e",
|
|
1318
1675
|
"--rsh",
|
|
@@ -1324,21 +1681,35 @@ var RSYNC_SKIP = [
|
|
|
1324
1681
|
"f",
|
|
1325
1682
|
"--filter"
|
|
1326
1683
|
];
|
|
1684
|
+
var CP_VALUE_LETTERS = ["S", "t"];
|
|
1685
|
+
var INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
|
|
1327
1686
|
var COPY_VERBS = {
|
|
1328
|
-
cp: { source: "allButLast", targetDirFlag: true },
|
|
1329
|
-
mv: { source: "allButLast", targetDirFlag: true },
|
|
1330
|
-
install: { source: "allButLast", targetDirFlag: true },
|
|
1331
|
-
ln: { source: "first", targetDirFlag: true },
|
|
1332
|
-
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
|
|
1333
|
-
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
|
|
1687
|
+
cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1688
|
+
mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1689
|
+
install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
|
|
1690
|
+
ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1691
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
|
|
1692
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
|
|
1334
1693
|
tar: {
|
|
1335
1694
|
source: "archive",
|
|
1336
1695
|
archive: "tar",
|
|
1337
|
-
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
|
|
1696
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
|
|
1697
|
+
valueLetters: TAR_VALUE_LETTERS
|
|
1698
|
+
},
|
|
1699
|
+
zip: {
|
|
1700
|
+
source: "archive",
|
|
1701
|
+
archive: "zip",
|
|
1702
|
+
skipFlags: ["x", "i", "--exclude", "--include"],
|
|
1703
|
+
valueLetters: ZIP_VALUE_LETTERS
|
|
1338
1704
|
},
|
|
1339
|
-
zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
|
|
1340
1705
|
ar: { source: "archive", archive: "ar" },
|
|
1341
|
-
|
|
1706
|
+
// 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
|
|
1707
|
+
// ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
|
|
1708
|
+
// and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
|
|
1709
|
+
// drop the credential as an exclusion operand (/code-review round 8), and the
|
|
1710
|
+
// derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
|
|
1711
|
+
// what keeps the two tables honest about it.
|
|
1712
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
|
|
1342
1713
|
gzip: { source: "all" },
|
|
1343
1714
|
bzip2: { source: "all" },
|
|
1344
1715
|
xz: { source: "all" },
|
|
@@ -2198,7 +2569,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2198
2569
|
return result?.verdict !== "block";
|
|
2199
2570
|
}
|
|
2200
2571
|
if (nodeType !== "CallExpr") return true;
|
|
2201
|
-
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2572
|
+
const { name, flags, paths, words, args } = extractLiteralArgs(n);
|
|
2202
2573
|
if (!name) return true;
|
|
2203
2574
|
if (name === "rm") {
|
|
2204
2575
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -2238,7 +2609,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2238
2609
|
return true;
|
|
2239
2610
|
}
|
|
2240
2611
|
}
|
|
2241
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
2612
|
+
const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
|
|
2242
2613
|
if (readPaths) {
|
|
2243
2614
|
for (const p of readPaths) {
|
|
2244
2615
|
result = stricter(result, matchSensitivePath2(p));
|
|
@@ -2274,9 +2645,22 @@ function flagIs(w, names) {
|
|
|
2274
2645
|
const f = flagInfo(w);
|
|
2275
2646
|
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
2276
2647
|
}
|
|
2277
|
-
function operandOf(a, names) {
|
|
2648
|
+
function operandOf(a, names, valueLetters) {
|
|
2278
2649
|
if (!names || a.afterFlag === null) return false;
|
|
2279
|
-
|
|
2650
|
+
const w = a.afterFlag;
|
|
2651
|
+
if (!w.startsWith("--") && valueLetters) {
|
|
2652
|
+
const letters = w.slice(1);
|
|
2653
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2654
|
+
if (!valueLetters.includes(letters[i])) continue;
|
|
2655
|
+
return i === letters.length - 1 && names.includes(letters[i]);
|
|
2656
|
+
}
|
|
2657
|
+
return false;
|
|
2658
|
+
}
|
|
2659
|
+
if (w.startsWith("--") && !w.includes("=")) {
|
|
2660
|
+
const longs = names.filter((n) => n.startsWith("--"));
|
|
2661
|
+
if (longs.some((n) => n === w || w.length >= 3 && n.startsWith(w))) return true;
|
|
2662
|
+
}
|
|
2663
|
+
return flagIs(w, names) && flagInfo(w).attached === null;
|
|
2280
2664
|
}
|
|
2281
2665
|
function resolveCopyShape(words, h) {
|
|
2282
2666
|
const verb = baseWord(words[h]);
|
|
@@ -2301,7 +2685,7 @@ function findStartPoints(words, h) {
|
|
|
2301
2685
|
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
2302
2686
|
if (k < 0) return { k, starts: [] };
|
|
2303
2687
|
const firstPredicate = words.findIndex(
|
|
2304
|
-
(w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
2688
|
+
(w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
2305
2689
|
);
|
|
2306
2690
|
const end = firstPredicate > h ? firstPredicate : k;
|
|
2307
2691
|
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
@@ -2321,14 +2705,35 @@ function copySourcePaths(words) {
|
|
|
2321
2705
|
const { shape, last } = r;
|
|
2322
2706
|
const args = positionedArgs(words, last + 1);
|
|
2323
2707
|
const tail = words.slice(last + 1);
|
|
2324
|
-
const
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2708
|
+
const copyOptionsEnd = tail.findIndex((w) => w === "--");
|
|
2709
|
+
const copyPastOptions = (a) => copyOptionsEnd >= 0 && a.argv > last + 1 + copyOptionsEnd;
|
|
2710
|
+
const skipped = (a) => !copyPastOptions(a) && operandOf(a, shape.skipFlags, shape.valueLetters);
|
|
2711
|
+
const firstValueLetter = (w) => {
|
|
2712
|
+
const letters = w.slice(1);
|
|
2713
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2714
|
+
if ((shape.valueLetters ?? []).includes(letters[i]))
|
|
2715
|
+
return { letter: letters[i], last: i === letters.length - 1 };
|
|
2716
|
+
}
|
|
2717
|
+
return null;
|
|
2718
|
+
};
|
|
2719
|
+
const isTargetDirFlag = (w) => {
|
|
2720
|
+
if (w.startsWith("--")) {
|
|
2721
|
+
const name = w.includes("=") ? w.slice(0, w.indexOf("=")) : w;
|
|
2722
|
+
return name.length >= 3 && "--target-directory".startsWith(name);
|
|
2723
|
+
}
|
|
2724
|
+
return firstValueLetter(w)?.letter === "t";
|
|
2725
|
+
};
|
|
2726
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && isTargetDirFlag(w));
|
|
2727
|
+
const targetTakesNextWord = (w) => {
|
|
2728
|
+
if (w.startsWith("--"))
|
|
2729
|
+
return !w.includes("=") && w.length >= 3 && "--target-directory".startsWith(w);
|
|
2730
|
+
const f = firstValueLetter(w);
|
|
2731
|
+
return f !== null && f.letter === "t" && f.last;
|
|
2732
|
+
};
|
|
2733
|
+
const targetOperand = (a) => targetDir && a.afterFlag !== null && !copyPastOptions(a) && targetTakesNextWord(a.afterFlag);
|
|
2327
2734
|
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
2328
2735
|
const dynamicDest = lastOperand === null;
|
|
2329
2736
|
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
2330
|
-
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
|
|
2331
|
-
return [];
|
|
2332
2737
|
let src;
|
|
2333
2738
|
switch (shape.source) {
|
|
2334
2739
|
case "all":
|
|
@@ -2338,11 +2743,17 @@ function copySourcePaths(words) {
|
|
|
2338
2743
|
src = targetDir ? args : args.slice(0, 1);
|
|
2339
2744
|
break;
|
|
2340
2745
|
case "flagOperand": {
|
|
2341
|
-
const
|
|
2746
|
+
const namesSource = (f) => {
|
|
2747
|
+
const names = shape.sourceFlags ?? [];
|
|
2748
|
+
if (f.long !== null)
|
|
2749
|
+
return names.some(
|
|
2750
|
+
(n) => n.startsWith("--") && (n === f.long || f.long.length >= 3 && n.startsWith(f.long))
|
|
2751
|
+
);
|
|
2752
|
+
return f.letter !== null && names.includes(f.letter);
|
|
2753
|
+
};
|
|
2754
|
+
const inline = tail.filter((w) => w !== null && w.startsWith("-")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && namesSource(f)).map((f) => f.attached);
|
|
2342
2755
|
return [
|
|
2343
|
-
...args.filter(
|
|
2344
|
-
(a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
|
|
2345
|
-
).map((a) => a.value),
|
|
2756
|
+
...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
|
|
2346
2757
|
...inline
|
|
2347
2758
|
];
|
|
2348
2759
|
}
|
|
@@ -2353,7 +2764,14 @@ function copySourcePaths(words) {
|
|
|
2353
2764
|
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
2354
2765
|
break;
|
|
2355
2766
|
}
|
|
2356
|
-
|
|
2767
|
+
const sources = src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
2768
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand)) {
|
|
2769
|
+
const jailedSources = sources.filter((p) => matchSensitivePath2(p));
|
|
2770
|
+
const dirOf = (p) => /[\\/]/.test(p) ? p.replace(/[\\/][^\\/]*$/, "") : "";
|
|
2771
|
+
if (jailedSources.length === 0) return [];
|
|
2772
|
+
if (jailedSources.every((p) => dirOf(p) === dirOf(lastOperand))) return [];
|
|
2773
|
+
}
|
|
2774
|
+
return sources;
|
|
2357
2775
|
}
|
|
2358
2776
|
function archiveInputs(kind, args, tail) {
|
|
2359
2777
|
const first = args[0];
|
|
@@ -2365,13 +2783,17 @@ function archiveInputs(kind, args, tail) {
|
|
|
2365
2783
|
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
2366
2784
|
if (extracting && !writing) return [];
|
|
2367
2785
|
void mode;
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
if (
|
|
2786
|
+
if (!bareKey) return args;
|
|
2787
|
+
let i = 1;
|
|
2788
|
+
const fromDirs = [];
|
|
2789
|
+
for (const ch of first.value) {
|
|
2790
|
+
if (!TAR_VALUE_LETTERS.includes(ch)) continue;
|
|
2791
|
+
const operand = args[i];
|
|
2792
|
+
if (!operand || operand.afterFlag !== null) break;
|
|
2793
|
+
i += 1;
|
|
2794
|
+
if (ch === "C") fromDirs.push(operand);
|
|
2373
2795
|
}
|
|
2374
|
-
return args.slice(i);
|
|
2796
|
+
return [...fromDirs, ...args.slice(i)];
|
|
2375
2797
|
}
|
|
2376
2798
|
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
2377
2799
|
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
@@ -2423,6 +2845,140 @@ function baseWord(w) {
|
|
|
2423
2845
|
}
|
|
2424
2846
|
var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
2425
2847
|
var positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
2848
|
+
function namesFlag(name, candidates, known) {
|
|
2849
|
+
if (candidates.has(name)) return true;
|
|
2850
|
+
if (!name.startsWith("--") || name.length < 3) return false;
|
|
2851
|
+
if (known?.has(name)) return false;
|
|
2852
|
+
for (const c of candidates) if (c.startsWith(name)) return true;
|
|
2853
|
+
return false;
|
|
2854
|
+
}
|
|
2855
|
+
function knownLongFlags(verb) {
|
|
2856
|
+
const out = /* @__PURE__ */ new Set();
|
|
2857
|
+
const shape = PATTERN_VERBS[verb];
|
|
2858
|
+
if (shape) {
|
|
2859
|
+
for (const set of [shape.takesValue, shape.noValue, shape.patternFlags, shape.noPatternFlags])
|
|
2860
|
+
for (const f of set) if (f.startsWith("--")) out.add(f);
|
|
2861
|
+
}
|
|
2862
|
+
for (const f of FILE_OPERAND_FLAGS[verb] ?? []) if (f.startsWith("--")) out.add(f);
|
|
2863
|
+
return out;
|
|
2864
|
+
}
|
|
2865
|
+
function flagNamesOf(token, shape) {
|
|
2866
|
+
if (token.startsWith("--")) {
|
|
2867
|
+
const eq = token.indexOf("=");
|
|
2868
|
+
return [eq > 0 ? token.slice(0, eq) : token];
|
|
2869
|
+
}
|
|
2870
|
+
const out = [];
|
|
2871
|
+
for (const c of token.slice(1)) {
|
|
2872
|
+
const name = `-${c}`;
|
|
2873
|
+
out.push(name);
|
|
2874
|
+
if (shape?.takesValue.has(name)) break;
|
|
2875
|
+
}
|
|
2876
|
+
return out;
|
|
2877
|
+
}
|
|
2878
|
+
var NONE = { kind: "none" };
|
|
2879
|
+
var UNKNOWN = { kind: "unknown" };
|
|
2880
|
+
function flagEffect(token, shape, known) {
|
|
2881
|
+
if (token === "--") return NONE;
|
|
2882
|
+
if (/^-+$/.test(token)) return UNKNOWN;
|
|
2883
|
+
if (/^-\d+$/.test(token)) return NONE;
|
|
2884
|
+
if (token.startsWith("--")) {
|
|
2885
|
+
if (token.includes("=")) return NONE;
|
|
2886
|
+
const takes = namesFlag(token, shape.takesValue, known);
|
|
2887
|
+
const none = namesFlag(token, shape.noValue, known);
|
|
2888
|
+
if (takes && none) return UNKNOWN;
|
|
2889
|
+
if (takes) return { kind: "takes", flag: token };
|
|
2890
|
+
if (none) return NONE;
|
|
2891
|
+
return UNKNOWN;
|
|
2892
|
+
}
|
|
2893
|
+
const letters = token.slice(1);
|
|
2894
|
+
if (!letters) return NONE;
|
|
2895
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2896
|
+
const name = `-${letters[i]}`;
|
|
2897
|
+
if (shape.takesValue.has(name)) {
|
|
2898
|
+
return i === letters.length - 1 ? { kind: "takes", flag: name } : NONE;
|
|
2899
|
+
}
|
|
2900
|
+
if (!shape.noValue.has(name)) return UNKNOWN;
|
|
2901
|
+
}
|
|
2902
|
+
return NONE;
|
|
2903
|
+
}
|
|
2904
|
+
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
2905
|
+
const shape = PATTERN_VERBS[verb];
|
|
2906
|
+
if (!shape) return args.map((a) => a.value);
|
|
2907
|
+
const known = knownLongFlags(verb);
|
|
2908
|
+
const names = flags.flatMap((f) => flagNamesOf(f, shape));
|
|
2909
|
+
const patternElsewhere = names.some(
|
|
2910
|
+
(n) => namesFlag(n, shape.patternFlags, known) || namesFlag(n, shape.noPatternFlags, known)
|
|
2911
|
+
);
|
|
2912
|
+
const excused = /* @__PURE__ */ new Set();
|
|
2913
|
+
const fileFlags = FILE_OPERAND_FLAGS[verb];
|
|
2914
|
+
const optionsEnd = words.findIndex((w, i) => i >= from && w === "--");
|
|
2915
|
+
const pastOptions = (a) => optionsEnd >= 0 && a.argv > optionsEnd;
|
|
2916
|
+
for (const a of args) {
|
|
2917
|
+
if (a.afterFlag === null || pastOptions(a)) continue;
|
|
2918
|
+
const e = flagEffect(a.afterFlag, shape, known);
|
|
2919
|
+
if (e.kind !== "takes") continue;
|
|
2920
|
+
if (fileFlags && namesFlag(e.flag, fileFlags, known)) continue;
|
|
2921
|
+
excused.add(a);
|
|
2922
|
+
}
|
|
2923
|
+
const patternArgv = (() => {
|
|
2924
|
+
for (let i = from; i < words.length; i++) {
|
|
2925
|
+
const w = words[i];
|
|
2926
|
+
if (w === null) return -1;
|
|
2927
|
+
if (w === "--") return i + 1;
|
|
2928
|
+
if (w.startsWith("-")) {
|
|
2929
|
+
const e = flagEffect(w, shape, known);
|
|
2930
|
+
if (e.kind === "takes") i += 1;
|
|
2931
|
+
else if (e.kind === "unknown") return -1;
|
|
2932
|
+
continue;
|
|
2933
|
+
}
|
|
2934
|
+
return i;
|
|
2935
|
+
}
|
|
2936
|
+
return -1;
|
|
2937
|
+
})();
|
|
2938
|
+
if (!patternElsewhere && patternArgv >= 0) {
|
|
2939
|
+
const a = args.find((x) => x.argv === patternArgv);
|
|
2940
|
+
if (a) excused.add(a);
|
|
2941
|
+
}
|
|
2942
|
+
return args.filter((a) => !excused.has(a)).map((a) => a.value);
|
|
2943
|
+
}
|
|
2944
|
+
function flagOperandFiles(verb, words, from) {
|
|
2945
|
+
const flags = FILE_OPERAND_FLAGS[verb];
|
|
2946
|
+
if (!flags) return [];
|
|
2947
|
+
const known = knownLongFlags(verb);
|
|
2948
|
+
const out = [];
|
|
2949
|
+
for (let i = from; i < words.length; i++) {
|
|
2950
|
+
const w = words[i];
|
|
2951
|
+
if (w === null || !w.startsWith("-") || w === "--") continue;
|
|
2952
|
+
if (w.startsWith("--")) {
|
|
2953
|
+
const eq = w.indexOf("=");
|
|
2954
|
+
if (eq <= 0) continue;
|
|
2955
|
+
const name = w.slice(0, eq);
|
|
2956
|
+
const value = w.slice(eq + 1);
|
|
2957
|
+
if (!value) continue;
|
|
2958
|
+
if (namesFlag(name, flags, known)) {
|
|
2959
|
+
out.push(value);
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
const shape2 = PATTERN_VERBS[verb];
|
|
2963
|
+
if (!shape2) continue;
|
|
2964
|
+
const recognised = namesFlag(name, shape2.takesValue, known) || namesFlag(name, shape2.noValue, known) || namesFlag(name, shape2.patternFlags, known) || namesFlag(name, shape2.noPatternFlags, known);
|
|
2965
|
+
if (!recognised) out.push(value);
|
|
2966
|
+
continue;
|
|
2967
|
+
}
|
|
2968
|
+
const shape = PATTERN_VERBS[verb];
|
|
2969
|
+
const letters = w.slice(1);
|
|
2970
|
+
for (let j = 0; j < letters.length; j++) {
|
|
2971
|
+
const name = `-${letters[j]}`;
|
|
2972
|
+
const isFileFlag = flags.has(name);
|
|
2973
|
+
const argTaking = isFileFlag || (shape?.takesValue.has(name) ?? false) || (READER_VALUE_LETTERS[verb] ?? []).includes(letters[j]);
|
|
2974
|
+
if (!argTaking) continue;
|
|
2975
|
+
const attached = letters.slice(j + 1);
|
|
2976
|
+
if (isFileFlag && attached) out.push(attached);
|
|
2977
|
+
break;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return out;
|
|
2981
|
+
}
|
|
2426
2982
|
function wrappedReadPaths(words, name) {
|
|
2427
2983
|
if (name === "find") {
|
|
2428
2984
|
const { k, starts } = findStartPoints(words, 0);
|
|
@@ -2430,7 +2986,14 @@ function wrappedReadPaths(words, name) {
|
|
|
2430
2986
|
}
|
|
2431
2987
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
2432
2988
|
const h = unwrapCommandHead(words);
|
|
2433
|
-
|
|
2989
|
+
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
2990
|
+
const head = baseWord(words[h]);
|
|
2991
|
+
const rest = words.slice(h + 1);
|
|
2992
|
+
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
2993
|
+
return [
|
|
2994
|
+
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
2995
|
+
...flagOperandFiles(head, words, h + 1)
|
|
2996
|
+
];
|
|
2434
2997
|
}
|
|
2435
2998
|
function literalShellPayload(words, name) {
|
|
2436
2999
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
@@ -3580,6 +4143,21 @@ function isIgnoredTool(toolName, config) {
|
|
|
3580
4143
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
3581
4144
|
}
|
|
3582
4145
|
|
|
4146
|
+
// src/utils/safe-text.ts
|
|
4147
|
+
var TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
4148
|
+
var CONTROL_CHAR_RE = /[\x00-\x1F\x7F]/g;
|
|
4149
|
+
function stripTerminalEscapes(s) {
|
|
4150
|
+
return s.replace(TERMINAL_ESCAPE_RE, "");
|
|
4151
|
+
}
|
|
4152
|
+
function stripControlChars(s) {
|
|
4153
|
+
return s.replace(CONTROL_CHAR_RE, "");
|
|
4154
|
+
}
|
|
4155
|
+
function safeMessage(value, max = 300) {
|
|
4156
|
+
const raw = typeof value === "string" ? value : value instanceof Error ? value.message : String(value ?? "");
|
|
4157
|
+
const s = stripTerminalEscapes(raw).replace(/\s+/g, " ").trim();
|
|
4158
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
4159
|
+
}
|
|
4160
|
+
|
|
3583
4161
|
// src/shields/index.ts
|
|
3584
4162
|
import safeRegex3 from "safe-regex2";
|
|
3585
4163
|
|
|
@@ -4661,9 +5239,13 @@ function computeAgentDeviceScore(opts) {
|
|
|
4661
5239
|
}
|
|
4662
5240
|
|
|
4663
5241
|
// src/blast/index.ts
|
|
5242
|
+
var MAX_BLAST_PATH = 4096;
|
|
4664
5243
|
function truncateBlastPath(full) {
|
|
4665
5244
|
if (!full) return "";
|
|
4666
|
-
|
|
5245
|
+
if (full.length > MAX_BLAST_PATH) full = full.slice(-MAX_BLAST_PATH);
|
|
5246
|
+
let end = full.length;
|
|
5247
|
+
while (end > 0 && (full[end - 1] === "/" || full[end - 1] === "\\")) end--;
|
|
5248
|
+
const cleaned = full.slice(0, end);
|
|
4667
5249
|
const parts = cleaned.split(/[/\\]+/).filter((p) => p.length > 0);
|
|
4668
5250
|
if (parts.length <= 2) {
|
|
4669
5251
|
return cleaned.startsWith("~") && !cleaned.startsWith("~/") ? cleaned : cleaned.startsWith("~/") ? cleaned : parts.join("/");
|
|
@@ -4950,8 +5532,8 @@ function matchCanaryArgs(args, values) {
|
|
|
4950
5532
|
|
|
4951
5533
|
// src/scan/canonical.ts
|
|
4952
5534
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
4953
|
-
var CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
4954
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
5535
|
+
var CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
|
|
5536
|
+
var CANONICAL_EXTRACTOR_HASH = "2823cd6a54a1fca7";
|
|
4955
5537
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
4956
5538
|
function extractCanonicalFindings(call, ctx) {
|
|
4957
5539
|
const out = [];
|
|
@@ -5270,13 +5852,9 @@ function toScanFinding(c) {
|
|
|
5270
5852
|
lineIndex: c.lineIndex
|
|
5271
5853
|
};
|
|
5272
5854
|
}
|
|
5273
|
-
var TERMINAL_ESCAPE_RE = (
|
|
5274
|
-
// eslint-disable-next-line no-control-regex
|
|
5275
|
-
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g
|
|
5276
|
-
);
|
|
5277
5855
|
function previewArgs(input, max) {
|
|
5278
5856
|
const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
5279
|
-
const s = String(cmd)
|
|
5857
|
+
const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
|
|
5280
5858
|
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
5281
5859
|
}
|
|
5282
5860
|
function makeFinding(args) {
|
|
@@ -5341,7 +5919,9 @@ export {
|
|
|
5341
5919
|
LONG_OUTPUT_THRESHOLD_BYTES,
|
|
5342
5920
|
LOOP_MAX_RECORDS,
|
|
5343
5921
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
5922
|
+
MAX_BLAST_PATH,
|
|
5344
5923
|
NET_BINARIES,
|
|
5924
|
+
PATTERN_VERB_NAMES,
|
|
5345
5925
|
PRIVILEGE_ESCALATION_RE,
|
|
5346
5926
|
REALTIME_PII_PATTERNS,
|
|
5347
5927
|
SCAN_SIGNAL_WEIGHTS,
|
|
@@ -5378,6 +5958,7 @@ export {
|
|
|
5378
5958
|
extractSessionLevelFindings,
|
|
5379
5959
|
extractShellDestTokens,
|
|
5380
5960
|
extractShellDestinations,
|
|
5961
|
+
fileOperandFlagsOf,
|
|
5381
5962
|
getCompiledRegex,
|
|
5382
5963
|
getNestedValue,
|
|
5383
5964
|
hostMatches,
|
|
@@ -5397,10 +5978,12 @@ export {
|
|
|
5397
5978
|
normalizeIpLiteral,
|
|
5398
5979
|
parseAllSshHostsFromCommand,
|
|
5399
5980
|
parseDestHost,
|
|
5981
|
+
patternShapeOf,
|
|
5400
5982
|
positionedArgs,
|
|
5401
5983
|
previewArgs,
|
|
5402
5984
|
redactText,
|
|
5403
5985
|
resolvePinned,
|
|
5986
|
+
safeMessage,
|
|
5404
5987
|
sampleCopyCommand,
|
|
5405
5988
|
scanArgs,
|
|
5406
5989
|
scanInjection,
|
|
@@ -5409,6 +5992,8 @@ export {
|
|
|
5409
5992
|
ssrfDestinationFloor,
|
|
5410
5993
|
ssrfFloor,
|
|
5411
5994
|
ssrfReason,
|
|
5995
|
+
stripControlChars,
|
|
5996
|
+
stripTerminalEscapes,
|
|
5412
5997
|
summarizeBlast,
|
|
5413
5998
|
summarizeScan,
|
|
5414
5999
|
toScanFinding,
|