@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.js
CHANGED
|
@@ -51,7 +51,9 @@ __export(src_exports, {
|
|
|
51
51
|
LONG_OUTPUT_THRESHOLD_BYTES: () => LONG_OUTPUT_THRESHOLD_BYTES,
|
|
52
52
|
LOOP_MAX_RECORDS: () => LOOP_MAX_RECORDS,
|
|
53
53
|
LOOP_THRESHOLD_FOR_WASTE: () => LOOP_THRESHOLD_FOR_WASTE,
|
|
54
|
+
MAX_BLAST_PATH: () => MAX_BLAST_PATH,
|
|
54
55
|
NET_BINARIES: () => NET_BINARIES,
|
|
56
|
+
PATTERN_VERB_NAMES: () => PATTERN_VERB_NAMES,
|
|
55
57
|
PRIVILEGE_ESCALATION_RE: () => PRIVILEGE_ESCALATION_RE,
|
|
56
58
|
REALTIME_PII_PATTERNS: () => REALTIME_PII_PATTERNS,
|
|
57
59
|
SCAN_SIGNAL_WEIGHTS: () => SCAN_SIGNAL_WEIGHTS,
|
|
@@ -88,6 +90,7 @@ __export(src_exports, {
|
|
|
88
90
|
extractSessionLevelFindings: () => extractSessionLevelFindings,
|
|
89
91
|
extractShellDestTokens: () => extractShellDestTokens,
|
|
90
92
|
extractShellDestinations: () => extractShellDestinations,
|
|
93
|
+
fileOperandFlagsOf: () => fileOperandFlagsOf,
|
|
91
94
|
getCompiledRegex: () => getCompiledRegex,
|
|
92
95
|
getNestedValue: () => getNestedValue,
|
|
93
96
|
hostMatches: () => hostMatches,
|
|
@@ -107,10 +110,12 @@ __export(src_exports, {
|
|
|
107
110
|
normalizeIpLiteral: () => normalizeIpLiteral,
|
|
108
111
|
parseAllSshHostsFromCommand: () => parseAllSshHostsFromCommand,
|
|
109
112
|
parseDestHost: () => parseDestHost,
|
|
113
|
+
patternShapeOf: () => patternShapeOf,
|
|
110
114
|
positionedArgs: () => positionedArgs,
|
|
111
115
|
previewArgs: () => previewArgs,
|
|
112
116
|
redactText: () => redactText,
|
|
113
117
|
resolvePinned: () => resolvePinned,
|
|
118
|
+
safeMessage: () => safeMessage,
|
|
114
119
|
sampleCopyCommand: () => sampleCopyCommand,
|
|
115
120
|
scanArgs: () => scanArgs,
|
|
116
121
|
scanInjection: () => scanInjection,
|
|
@@ -119,6 +124,8 @@ __export(src_exports, {
|
|
|
119
124
|
ssrfDestinationFloor: () => ssrfDestinationFloor,
|
|
120
125
|
ssrfFloor: () => ssrfFloor,
|
|
121
126
|
ssrfReason: () => ssrfReason,
|
|
127
|
+
stripControlChars: () => stripControlChars,
|
|
128
|
+
stripTerminalEscapes: () => stripTerminalEscapes,
|
|
122
129
|
summarizeBlast: () => summarizeBlast,
|
|
123
130
|
summarizeScan: () => summarizeScan,
|
|
124
131
|
toScanFinding: () => toScanFinding,
|
|
@@ -1254,6 +1261,19 @@ function parseShared(command) {
|
|
|
1254
1261
|
astCache.set(command, parsed);
|
|
1255
1262
|
return parsed;
|
|
1256
1263
|
}
|
|
1264
|
+
function byteOffsetToCharIndex(command) {
|
|
1265
|
+
if (!/[^\u0000-\u007F]/.test(command)) return null;
|
|
1266
|
+
const map = /* @__PURE__ */ new Map();
|
|
1267
|
+
let byte = 0;
|
|
1268
|
+
for (let i = 0; i < command.length; ) {
|
|
1269
|
+
map.set(byte, i);
|
|
1270
|
+
const cp = command.codePointAt(i);
|
|
1271
|
+
byte += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
1272
|
+
i += cp > 65535 ? 2 : 1;
|
|
1273
|
+
}
|
|
1274
|
+
map.set(byte, command.length);
|
|
1275
|
+
return (b) => map.get(b) ?? -1;
|
|
1276
|
+
}
|
|
1257
1277
|
function cachedNormalize(command, compute) {
|
|
1258
1278
|
const hit = normalizeCache.get(command);
|
|
1259
1279
|
if (hit !== void 0) {
|
|
@@ -1283,6 +1303,8 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1283
1303
|
const f = parseShared(command);
|
|
1284
1304
|
if (f === PARSE_FAIL) return { posix: command, separator: command };
|
|
1285
1305
|
try {
|
|
1306
|
+
const toCharIndex = byteOffsetToCharIndex(command);
|
|
1307
|
+
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
1286
1308
|
const strips = [];
|
|
1287
1309
|
const rewrites = [];
|
|
1288
1310
|
const quoteOnlyRewrites = [];
|
|
@@ -1303,8 +1325,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1303
1325
|
const quotedNode = nextParts[0];
|
|
1304
1326
|
const nt = syntax.NodeType(quotedNode);
|
|
1305
1327
|
const markStrip = () => {
|
|
1306
|
-
const s = next.Pos().Offset();
|
|
1307
|
-
const e = next.End().Offset();
|
|
1328
|
+
const s = at(next.Pos().Offset());
|
|
1329
|
+
const e = at(next.End().Offset());
|
|
1330
|
+
if (s < 0 || e < 0) return;
|
|
1308
1331
|
strips.push([s, e]);
|
|
1309
1332
|
msgSpans.add(`${s}:${e}`);
|
|
1310
1333
|
};
|
|
@@ -1321,8 +1344,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
1321
1344
|
}
|
|
1322
1345
|
}
|
|
1323
1346
|
for (const arg of args) {
|
|
1324
|
-
const s = arg.Pos().Offset();
|
|
1325
|
-
const e = arg.End().Offset();
|
|
1347
|
+
const s = at(arg.Pos().Offset());
|
|
1348
|
+
const e = at(arg.End().Offset());
|
|
1349
|
+
if (s < 0 || e < 0) continue;
|
|
1326
1350
|
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
1327
1351
|
const resolved = resolveWordLiteral(arg);
|
|
1328
1352
|
if (resolved === null) continue;
|
|
@@ -1445,7 +1469,347 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1445
1469
|
"nl",
|
|
1446
1470
|
"dd"
|
|
1447
1471
|
]);
|
|
1472
|
+
var GREP_SHAPE = {
|
|
1473
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
1474
|
+
"-A",
|
|
1475
|
+
"-B",
|
|
1476
|
+
"-C",
|
|
1477
|
+
"-D",
|
|
1478
|
+
"-d",
|
|
1479
|
+
"-e",
|
|
1480
|
+
"-f",
|
|
1481
|
+
"-m",
|
|
1482
|
+
"--after-context",
|
|
1483
|
+
"--before-context",
|
|
1484
|
+
"--binary-files",
|
|
1485
|
+
"--context",
|
|
1486
|
+
"--devices",
|
|
1487
|
+
"--directories",
|
|
1488
|
+
"--exclude",
|
|
1489
|
+
"--exclude-dir",
|
|
1490
|
+
"--exclude-from",
|
|
1491
|
+
"--file",
|
|
1492
|
+
"--include",
|
|
1493
|
+
"--label",
|
|
1494
|
+
"--max-count",
|
|
1495
|
+
"--regexp"
|
|
1496
|
+
]),
|
|
1497
|
+
noValue: /* @__PURE__ */ new Set([
|
|
1498
|
+
"-E",
|
|
1499
|
+
"-F",
|
|
1500
|
+
"-G",
|
|
1501
|
+
"-P",
|
|
1502
|
+
"-i",
|
|
1503
|
+
"-y",
|
|
1504
|
+
"-v",
|
|
1505
|
+
"-V",
|
|
1506
|
+
"-w",
|
|
1507
|
+
"-x",
|
|
1508
|
+
"-c",
|
|
1509
|
+
"-l",
|
|
1510
|
+
"-L",
|
|
1511
|
+
"-o",
|
|
1512
|
+
"-q",
|
|
1513
|
+
"-s",
|
|
1514
|
+
"-b",
|
|
1515
|
+
"-H",
|
|
1516
|
+
"-h",
|
|
1517
|
+
"-n",
|
|
1518
|
+
"-T",
|
|
1519
|
+
"-Z",
|
|
1520
|
+
"-z",
|
|
1521
|
+
"-R",
|
|
1522
|
+
"-r",
|
|
1523
|
+
"-U",
|
|
1524
|
+
"-u",
|
|
1525
|
+
"-I",
|
|
1526
|
+
"-a",
|
|
1527
|
+
"--basic-regexp",
|
|
1528
|
+
"--binary",
|
|
1529
|
+
"--byte-offset",
|
|
1530
|
+
"--color",
|
|
1531
|
+
"--colour",
|
|
1532
|
+
"--count",
|
|
1533
|
+
"--dereference-recursive",
|
|
1534
|
+
"--extended-regexp",
|
|
1535
|
+
"--files-with-matches",
|
|
1536
|
+
"--files-without-match",
|
|
1537
|
+
"--fixed-strings",
|
|
1538
|
+
"--help",
|
|
1539
|
+
"--ignore-case",
|
|
1540
|
+
"--initial-tab",
|
|
1541
|
+
"--invert-match",
|
|
1542
|
+
"--line-buffered",
|
|
1543
|
+
"--line-number",
|
|
1544
|
+
"--line-regexp",
|
|
1545
|
+
"--no-filename",
|
|
1546
|
+
"--no-group-separator",
|
|
1547
|
+
"--no-ignore-case",
|
|
1548
|
+
"--no-messages",
|
|
1549
|
+
"--null",
|
|
1550
|
+
"--null-data",
|
|
1551
|
+
"--only-matching",
|
|
1552
|
+
"--perl-regexp",
|
|
1553
|
+
"--quiet",
|
|
1554
|
+
"--recursive",
|
|
1555
|
+
"--silent",
|
|
1556
|
+
"--text",
|
|
1557
|
+
"--version",
|
|
1558
|
+
"--with-filename",
|
|
1559
|
+
"--word-regexp"
|
|
1560
|
+
]),
|
|
1561
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
1562
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
|
|
1563
|
+
};
|
|
1564
|
+
var PATTERN_VERBS = {
|
|
1565
|
+
grep: GREP_SHAPE,
|
|
1566
|
+
// /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
|
|
1567
|
+
// `grep -E` and `grep -F`. Read in full, not assumed.
|
|
1568
|
+
egrep: GREP_SHAPE,
|
|
1569
|
+
fgrep: GREP_SHAPE,
|
|
1570
|
+
// ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
|
|
1571
|
+
// was not installed on the measuring machine; it is, and that stale claim is why
|
|
1572
|
+
// this table shipped incomplete for two rounds, blocking ordinary searches like
|
|
1573
|
+
// `rg --sort-files .env src` (/code-review round 4).
|
|
1574
|
+
rg: {
|
|
1575
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
1576
|
+
"-A",
|
|
1577
|
+
"-B",
|
|
1578
|
+
"-C",
|
|
1579
|
+
"-d",
|
|
1580
|
+
"-E",
|
|
1581
|
+
"-e",
|
|
1582
|
+
"-f",
|
|
1583
|
+
"-g",
|
|
1584
|
+
"-j",
|
|
1585
|
+
"-M",
|
|
1586
|
+
"-m",
|
|
1587
|
+
"-r",
|
|
1588
|
+
"-t",
|
|
1589
|
+
"-T",
|
|
1590
|
+
"--after-context",
|
|
1591
|
+
"--before-context",
|
|
1592
|
+
"--color",
|
|
1593
|
+
"--colors",
|
|
1594
|
+
"--context",
|
|
1595
|
+
"--context-separator",
|
|
1596
|
+
"--dfa-size-limit",
|
|
1597
|
+
"--encoding",
|
|
1598
|
+
"--engine",
|
|
1599
|
+
"--field-context-separator",
|
|
1600
|
+
"--field-match-separator",
|
|
1601
|
+
"--file",
|
|
1602
|
+
"--generate",
|
|
1603
|
+
"--glob",
|
|
1604
|
+
"--hostname-bin",
|
|
1605
|
+
"--hyperlink-format",
|
|
1606
|
+
"--iglob",
|
|
1607
|
+
"--ignore-file",
|
|
1608
|
+
"--max-columns",
|
|
1609
|
+
"--max-count",
|
|
1610
|
+
"--max-depth",
|
|
1611
|
+
// An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
|
|
1612
|
+
// alias lines as plain switches and swept it into noValue, which hard-blocked
|
|
1613
|
+
// `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
|
|
1614
|
+
"--maxdepth",
|
|
1615
|
+
"--max-filesize",
|
|
1616
|
+
"--path-separator",
|
|
1617
|
+
"--pre",
|
|
1618
|
+
"--pre-glob",
|
|
1619
|
+
"--regexp",
|
|
1620
|
+
"--regex-size-limit",
|
|
1621
|
+
"--replace",
|
|
1622
|
+
"--sort",
|
|
1623
|
+
"--sortr",
|
|
1624
|
+
"--threads",
|
|
1625
|
+
"--type",
|
|
1626
|
+
"--type-add",
|
|
1627
|
+
"--type-clear",
|
|
1628
|
+
"--type-not"
|
|
1629
|
+
]),
|
|
1630
|
+
noValue: /* @__PURE__ */ new Set([
|
|
1631
|
+
"-.",
|
|
1632
|
+
"-0",
|
|
1633
|
+
"-a",
|
|
1634
|
+
"-b",
|
|
1635
|
+
"-c",
|
|
1636
|
+
"-F",
|
|
1637
|
+
"-h",
|
|
1638
|
+
"-H",
|
|
1639
|
+
"-i",
|
|
1640
|
+
"-I",
|
|
1641
|
+
"-l",
|
|
1642
|
+
"-L",
|
|
1643
|
+
"-n",
|
|
1644
|
+
"-N",
|
|
1645
|
+
"-o",
|
|
1646
|
+
"-p",
|
|
1647
|
+
"-P",
|
|
1648
|
+
"-q",
|
|
1649
|
+
"-s",
|
|
1650
|
+
"-S",
|
|
1651
|
+
"-u",
|
|
1652
|
+
"-U",
|
|
1653
|
+
"-v",
|
|
1654
|
+
"-V",
|
|
1655
|
+
"-w",
|
|
1656
|
+
"-x",
|
|
1657
|
+
"-z",
|
|
1658
|
+
"--auto-hybrid-regex",
|
|
1659
|
+
"--binary",
|
|
1660
|
+
"--block-buffered",
|
|
1661
|
+
"--byte-offset",
|
|
1662
|
+
"--case-sensitive",
|
|
1663
|
+
"--column",
|
|
1664
|
+
"--count",
|
|
1665
|
+
"--count-matches",
|
|
1666
|
+
"--crlf",
|
|
1667
|
+
"--debug",
|
|
1668
|
+
"--files",
|
|
1669
|
+
"--files-with-matches",
|
|
1670
|
+
"--files-without-match",
|
|
1671
|
+
"--fixed-strings",
|
|
1672
|
+
"--follow",
|
|
1673
|
+
"--glob-case-insensitive",
|
|
1674
|
+
"--heading",
|
|
1675
|
+
"--help",
|
|
1676
|
+
"--hidden",
|
|
1677
|
+
"--ignore-case",
|
|
1678
|
+
"--ignore-file-case-insensitive",
|
|
1679
|
+
"--include-zero",
|
|
1680
|
+
"--invert-match",
|
|
1681
|
+
"--json",
|
|
1682
|
+
"--line-buffered",
|
|
1683
|
+
"--line-number",
|
|
1684
|
+
"--line-regexp",
|
|
1685
|
+
"--max-columns-preview",
|
|
1686
|
+
"--mmap",
|
|
1687
|
+
"--multiline",
|
|
1688
|
+
"--multiline-dotall",
|
|
1689
|
+
"--no-column",
|
|
1690
|
+
"--no-config",
|
|
1691
|
+
"--no-context-separator",
|
|
1692
|
+
"--no-encoding",
|
|
1693
|
+
"--no-filename",
|
|
1694
|
+
"--no-ignore",
|
|
1695
|
+
"--no-ignore-dot",
|
|
1696
|
+
"--no-ignore-exclude",
|
|
1697
|
+
"--no-ignore-files",
|
|
1698
|
+
"--no-ignore-global",
|
|
1699
|
+
"--no-ignore-messages",
|
|
1700
|
+
"--no-ignore-parent",
|
|
1701
|
+
"--no-ignore-vcs",
|
|
1702
|
+
"--no-line-number",
|
|
1703
|
+
"--no-messages",
|
|
1704
|
+
"--no-pcre2-unicode",
|
|
1705
|
+
"--no-pre",
|
|
1706
|
+
"--no-require-git",
|
|
1707
|
+
"--no-unicode",
|
|
1708
|
+
"--null",
|
|
1709
|
+
"--null-data",
|
|
1710
|
+
"--one-file-system",
|
|
1711
|
+
"--only-matching",
|
|
1712
|
+
"--passthru",
|
|
1713
|
+
"--pcre2",
|
|
1714
|
+
"--pcre2-version",
|
|
1715
|
+
"--pretty",
|
|
1716
|
+
"--print0",
|
|
1717
|
+
"--quiet",
|
|
1718
|
+
"--search-zip",
|
|
1719
|
+
"--smart-case",
|
|
1720
|
+
"--sort-files",
|
|
1721
|
+
"--stats",
|
|
1722
|
+
"--stop-on-nonmatch",
|
|
1723
|
+
"--text",
|
|
1724
|
+
"--trace",
|
|
1725
|
+
"--trim",
|
|
1726
|
+
"--type-list",
|
|
1727
|
+
"--unrestricted",
|
|
1728
|
+
"--version",
|
|
1729
|
+
"--vimgrep",
|
|
1730
|
+
"--with-filename",
|
|
1731
|
+
"--word-regexp",
|
|
1732
|
+
// The COMPLETE remainder of `rg --help`, added after /code-review round 5
|
|
1733
|
+
// found 40 documented switches in neither set. The table is now the whole
|
|
1734
|
+
// option list: `rg --help` yields 149 long options, 35 of them value-taking.
|
|
1735
|
+
"--ignore",
|
|
1736
|
+
"--ignore-dot",
|
|
1737
|
+
"--ignore-exclude",
|
|
1738
|
+
"--ignore-files",
|
|
1739
|
+
"--ignore-global",
|
|
1740
|
+
"--ignore-messages",
|
|
1741
|
+
"--ignore-parent",
|
|
1742
|
+
"--ignore-vcs",
|
|
1743
|
+
"--messages",
|
|
1744
|
+
"--no-auto-hybrid-regex",
|
|
1745
|
+
"--no-binary",
|
|
1746
|
+
"--no-block-buffered",
|
|
1747
|
+
"--no-byte-offset",
|
|
1748
|
+
"--no-crlf",
|
|
1749
|
+
"--no-fixed-strings",
|
|
1750
|
+
"--no-follow",
|
|
1751
|
+
"--no-glob-case-insensitive",
|
|
1752
|
+
"--no-heading",
|
|
1753
|
+
"--no-hidden",
|
|
1754
|
+
"--no-ignore-file-case-insensitive",
|
|
1755
|
+
"--no-include-zero",
|
|
1756
|
+
"--no-invert-match",
|
|
1757
|
+
"--no-json",
|
|
1758
|
+
"--no-line-buffered",
|
|
1759
|
+
"--no-max-columns-preview",
|
|
1760
|
+
"--no-mmap",
|
|
1761
|
+
"--no-multiline",
|
|
1762
|
+
"--no-multiline-dotall",
|
|
1763
|
+
"--no-one-file-system",
|
|
1764
|
+
"--no-pcre2",
|
|
1765
|
+
"--no-search-zip",
|
|
1766
|
+
"--no-sort-files",
|
|
1767
|
+
"--no-stats",
|
|
1768
|
+
"--no-text",
|
|
1769
|
+
"--no-trim",
|
|
1770
|
+
"--passthrough",
|
|
1771
|
+
"--pcre2-unicode",
|
|
1772
|
+
"--require-git",
|
|
1773
|
+
"--unicode"
|
|
1774
|
+
]),
|
|
1775
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
1776
|
+
// `--files` and `--type-list` list or enumerate without a pattern. Founder
|
|
1777
|
+
// decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
|
|
1778
|
+
// by leaving the directory in the judged list.
|
|
1779
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
var PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
|
|
1783
|
+
var patternShapeOf = (verb) => PATTERN_VERBS[verb];
|
|
1784
|
+
var fileOperandFlagsOf = (verb) => FILE_OPERAND_FLAGS[verb];
|
|
1785
|
+
var GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
|
|
1786
|
+
var READER_VALUE_LETTERS = {
|
|
1787
|
+
awk: ["F", "v", "f"],
|
|
1788
|
+
gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
|
|
1789
|
+
sed: ["e", "f", "i", "l"],
|
|
1790
|
+
sort: ["C", "k", "o", "S", "t", "T"]
|
|
1791
|
+
};
|
|
1792
|
+
var FILE_OPERAND_FLAGS = {
|
|
1793
|
+
grep: GREP_FILE_OPERANDS,
|
|
1794
|
+
egrep: GREP_FILE_OPERANDS,
|
|
1795
|
+
fgrep: GREP_FILE_OPERANDS,
|
|
1796
|
+
// rg and gawk are NOT installed on the measuring machine: these two rows are
|
|
1797
|
+
// from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
|
|
1798
|
+
// `-f/--file`) and are marked as such in the spec.
|
|
1799
|
+
// `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
|
|
1800
|
+
// a credential makes rg open it -- the same reason grep's `--include` is here.
|
|
1801
|
+
// Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
|
|
1802
|
+
// and printed its contents.
|
|
1803
|
+
rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
|
|
1804
|
+
sed: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1805
|
+
awk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1806
|
+
gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
1807
|
+
sort: /* @__PURE__ */ new Set(["--files0-from"])
|
|
1808
|
+
};
|
|
1448
1809
|
var SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
1810
|
+
var TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
|
|
1811
|
+
var ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
|
|
1812
|
+
var RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
|
|
1449
1813
|
var RSYNC_SKIP = [
|
|
1450
1814
|
"e",
|
|
1451
1815
|
"--rsh",
|
|
@@ -1457,21 +1821,35 @@ var RSYNC_SKIP = [
|
|
|
1457
1821
|
"f",
|
|
1458
1822
|
"--filter"
|
|
1459
1823
|
];
|
|
1824
|
+
var CP_VALUE_LETTERS = ["S", "t"];
|
|
1825
|
+
var INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
|
|
1460
1826
|
var COPY_VERBS = {
|
|
1461
|
-
cp: { source: "allButLast", targetDirFlag: true },
|
|
1462
|
-
mv: { source: "allButLast", targetDirFlag: true },
|
|
1463
|
-
install: { source: "allButLast", targetDirFlag: true },
|
|
1464
|
-
ln: { source: "first", targetDirFlag: true },
|
|
1465
|
-
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
|
|
1466
|
-
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
|
|
1827
|
+
cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1828
|
+
mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1829
|
+
install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
|
|
1830
|
+
ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
1831
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
|
|
1832
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
|
|
1467
1833
|
tar: {
|
|
1468
1834
|
source: "archive",
|
|
1469
1835
|
archive: "tar",
|
|
1470
|
-
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
|
|
1836
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
|
|
1837
|
+
valueLetters: TAR_VALUE_LETTERS
|
|
1838
|
+
},
|
|
1839
|
+
zip: {
|
|
1840
|
+
source: "archive",
|
|
1841
|
+
archive: "zip",
|
|
1842
|
+
skipFlags: ["x", "i", "--exclude", "--include"],
|
|
1843
|
+
valueLetters: ZIP_VALUE_LETTERS
|
|
1471
1844
|
},
|
|
1472
|
-
zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
|
|
1473
1845
|
ar: { source: "archive", archive: "ar" },
|
|
1474
|
-
|
|
1846
|
+
// 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
|
|
1847
|
+
// ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
|
|
1848
|
+
// and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
|
|
1849
|
+
// drop the credential as an exclusion operand (/code-review round 8), and the
|
|
1850
|
+
// derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
|
|
1851
|
+
// what keeps the two tables honest about it.
|
|
1852
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
|
|
1475
1853
|
gzip: { source: "all" },
|
|
1476
1854
|
bzip2: { source: "all" },
|
|
1477
1855
|
xz: { source: "all" },
|
|
@@ -2331,7 +2709,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2331
2709
|
return result?.verdict !== "block";
|
|
2332
2710
|
}
|
|
2333
2711
|
if (nodeType !== "CallExpr") return true;
|
|
2334
|
-
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2712
|
+
const { name, flags, paths, words, args } = extractLiteralArgs(n);
|
|
2335
2713
|
if (!name) return true;
|
|
2336
2714
|
if (name === "rm") {
|
|
2337
2715
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -2371,7 +2749,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
2371
2749
|
return true;
|
|
2372
2750
|
}
|
|
2373
2751
|
}
|
|
2374
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
2752
|
+
const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
|
|
2375
2753
|
if (readPaths) {
|
|
2376
2754
|
for (const p of readPaths) {
|
|
2377
2755
|
result = stricter(result, matchSensitivePath2(p));
|
|
@@ -2407,9 +2785,22 @@ function flagIs(w, names) {
|
|
|
2407
2785
|
const f = flagInfo(w);
|
|
2408
2786
|
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
2409
2787
|
}
|
|
2410
|
-
function operandOf(a, names) {
|
|
2788
|
+
function operandOf(a, names, valueLetters) {
|
|
2411
2789
|
if (!names || a.afterFlag === null) return false;
|
|
2412
|
-
|
|
2790
|
+
const w = a.afterFlag;
|
|
2791
|
+
if (!w.startsWith("--") && valueLetters) {
|
|
2792
|
+
const letters = w.slice(1);
|
|
2793
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2794
|
+
if (!valueLetters.includes(letters[i])) continue;
|
|
2795
|
+
return i === letters.length - 1 && names.includes(letters[i]);
|
|
2796
|
+
}
|
|
2797
|
+
return false;
|
|
2798
|
+
}
|
|
2799
|
+
if (w.startsWith("--") && !w.includes("=")) {
|
|
2800
|
+
const longs = names.filter((n) => n.startsWith("--"));
|
|
2801
|
+
if (longs.some((n) => n === w || w.length >= 3 && n.startsWith(w))) return true;
|
|
2802
|
+
}
|
|
2803
|
+
return flagIs(w, names) && flagInfo(w).attached === null;
|
|
2413
2804
|
}
|
|
2414
2805
|
function resolveCopyShape(words, h) {
|
|
2415
2806
|
const verb = baseWord(words[h]);
|
|
@@ -2434,7 +2825,7 @@ function findStartPoints(words, h) {
|
|
|
2434
2825
|
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
2435
2826
|
if (k < 0) return { k, starts: [] };
|
|
2436
2827
|
const firstPredicate = words.findIndex(
|
|
2437
|
-
(w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
2828
|
+
(w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
2438
2829
|
);
|
|
2439
2830
|
const end = firstPredicate > h ? firstPredicate : k;
|
|
2440
2831
|
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
@@ -2454,14 +2845,35 @@ function copySourcePaths(words) {
|
|
|
2454
2845
|
const { shape, last } = r;
|
|
2455
2846
|
const args = positionedArgs(words, last + 1);
|
|
2456
2847
|
const tail = words.slice(last + 1);
|
|
2457
|
-
const
|
|
2458
|
-
const
|
|
2459
|
-
const
|
|
2848
|
+
const copyOptionsEnd = tail.findIndex((w) => w === "--");
|
|
2849
|
+
const copyPastOptions = (a) => copyOptionsEnd >= 0 && a.argv > last + 1 + copyOptionsEnd;
|
|
2850
|
+
const skipped = (a) => !copyPastOptions(a) && operandOf(a, shape.skipFlags, shape.valueLetters);
|
|
2851
|
+
const firstValueLetter = (w) => {
|
|
2852
|
+
const letters = w.slice(1);
|
|
2853
|
+
for (let i = 0; i < letters.length; i++) {
|
|
2854
|
+
if ((shape.valueLetters ?? []).includes(letters[i]))
|
|
2855
|
+
return { letter: letters[i], last: i === letters.length - 1 };
|
|
2856
|
+
}
|
|
2857
|
+
return null;
|
|
2858
|
+
};
|
|
2859
|
+
const isTargetDirFlag = (w) => {
|
|
2860
|
+
if (w.startsWith("--")) {
|
|
2861
|
+
const name = w.includes("=") ? w.slice(0, w.indexOf("=")) : w;
|
|
2862
|
+
return name.length >= 3 && "--target-directory".startsWith(name);
|
|
2863
|
+
}
|
|
2864
|
+
return firstValueLetter(w)?.letter === "t";
|
|
2865
|
+
};
|
|
2866
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && isTargetDirFlag(w));
|
|
2867
|
+
const targetTakesNextWord = (w) => {
|
|
2868
|
+
if (w.startsWith("--"))
|
|
2869
|
+
return !w.includes("=") && w.length >= 3 && "--target-directory".startsWith(w);
|
|
2870
|
+
const f = firstValueLetter(w);
|
|
2871
|
+
return f !== null && f.letter === "t" && f.last;
|
|
2872
|
+
};
|
|
2873
|
+
const targetOperand = (a) => targetDir && a.afterFlag !== null && !copyPastOptions(a) && targetTakesNextWord(a.afterFlag);
|
|
2460
2874
|
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
2461
2875
|
const dynamicDest = lastOperand === null;
|
|
2462
2876
|
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
2463
|
-
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
|
|
2464
|
-
return [];
|
|
2465
2877
|
let src;
|
|
2466
2878
|
switch (shape.source) {
|
|
2467
2879
|
case "all":
|
|
@@ -2471,11 +2883,17 @@ function copySourcePaths(words) {
|
|
|
2471
2883
|
src = targetDir ? args : args.slice(0, 1);
|
|
2472
2884
|
break;
|
|
2473
2885
|
case "flagOperand": {
|
|
2474
|
-
const
|
|
2886
|
+
const namesSource = (f) => {
|
|
2887
|
+
const names = shape.sourceFlags ?? [];
|
|
2888
|
+
if (f.long !== null)
|
|
2889
|
+
return names.some(
|
|
2890
|
+
(n) => n.startsWith("--") && (n === f.long || f.long.length >= 3 && n.startsWith(f.long))
|
|
2891
|
+
);
|
|
2892
|
+
return f.letter !== null && names.includes(f.letter);
|
|
2893
|
+
};
|
|
2894
|
+
const inline = tail.filter((w) => w !== null && w.startsWith("-")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && namesSource(f)).map((f) => f.attached);
|
|
2475
2895
|
return [
|
|
2476
|
-
...args.filter(
|
|
2477
|
-
(a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
|
|
2478
|
-
).map((a) => a.value),
|
|
2896
|
+
...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
|
|
2479
2897
|
...inline
|
|
2480
2898
|
];
|
|
2481
2899
|
}
|
|
@@ -2486,7 +2904,14 @@ function copySourcePaths(words) {
|
|
|
2486
2904
|
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
2487
2905
|
break;
|
|
2488
2906
|
}
|
|
2489
|
-
|
|
2907
|
+
const sources = src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
2908
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand)) {
|
|
2909
|
+
const jailedSources = sources.filter((p) => matchSensitivePath2(p));
|
|
2910
|
+
const dirOf = (p) => /[\\/]/.test(p) ? p.replace(/[\\/][^\\/]*$/, "") : "";
|
|
2911
|
+
if (jailedSources.length === 0) return [];
|
|
2912
|
+
if (jailedSources.every((p) => dirOf(p) === dirOf(lastOperand))) return [];
|
|
2913
|
+
}
|
|
2914
|
+
return sources;
|
|
2490
2915
|
}
|
|
2491
2916
|
function archiveInputs(kind, args, tail) {
|
|
2492
2917
|
const first = args[0];
|
|
@@ -2498,13 +2923,17 @@ function archiveInputs(kind, args, tail) {
|
|
|
2498
2923
|
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
2499
2924
|
if (extracting && !writing) return [];
|
|
2500
2925
|
void mode;
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
if (
|
|
2926
|
+
if (!bareKey) return args;
|
|
2927
|
+
let i = 1;
|
|
2928
|
+
const fromDirs = [];
|
|
2929
|
+
for (const ch of first.value) {
|
|
2930
|
+
if (!TAR_VALUE_LETTERS.includes(ch)) continue;
|
|
2931
|
+
const operand = args[i];
|
|
2932
|
+
if (!operand || operand.afterFlag !== null) break;
|
|
2933
|
+
i += 1;
|
|
2934
|
+
if (ch === "C") fromDirs.push(operand);
|
|
2506
2935
|
}
|
|
2507
|
-
return args.slice(i);
|
|
2936
|
+
return [...fromDirs, ...args.slice(i)];
|
|
2508
2937
|
}
|
|
2509
2938
|
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
2510
2939
|
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
@@ -2556,6 +2985,140 @@ function baseWord(w) {
|
|
|
2556
2985
|
}
|
|
2557
2986
|
var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
2558
2987
|
var positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
2988
|
+
function namesFlag(name, candidates, known) {
|
|
2989
|
+
if (candidates.has(name)) return true;
|
|
2990
|
+
if (!name.startsWith("--") || name.length < 3) return false;
|
|
2991
|
+
if (known?.has(name)) return false;
|
|
2992
|
+
for (const c of candidates) if (c.startsWith(name)) return true;
|
|
2993
|
+
return false;
|
|
2994
|
+
}
|
|
2995
|
+
function knownLongFlags(verb) {
|
|
2996
|
+
const out = /* @__PURE__ */ new Set();
|
|
2997
|
+
const shape = PATTERN_VERBS[verb];
|
|
2998
|
+
if (shape) {
|
|
2999
|
+
for (const set of [shape.takesValue, shape.noValue, shape.patternFlags, shape.noPatternFlags])
|
|
3000
|
+
for (const f of set) if (f.startsWith("--")) out.add(f);
|
|
3001
|
+
}
|
|
3002
|
+
for (const f of FILE_OPERAND_FLAGS[verb] ?? []) if (f.startsWith("--")) out.add(f);
|
|
3003
|
+
return out;
|
|
3004
|
+
}
|
|
3005
|
+
function flagNamesOf(token, shape) {
|
|
3006
|
+
if (token.startsWith("--")) {
|
|
3007
|
+
const eq = token.indexOf("=");
|
|
3008
|
+
return [eq > 0 ? token.slice(0, eq) : token];
|
|
3009
|
+
}
|
|
3010
|
+
const out = [];
|
|
3011
|
+
for (const c of token.slice(1)) {
|
|
3012
|
+
const name = `-${c}`;
|
|
3013
|
+
out.push(name);
|
|
3014
|
+
if (shape?.takesValue.has(name)) break;
|
|
3015
|
+
}
|
|
3016
|
+
return out;
|
|
3017
|
+
}
|
|
3018
|
+
var NONE = { kind: "none" };
|
|
3019
|
+
var UNKNOWN = { kind: "unknown" };
|
|
3020
|
+
function flagEffect(token, shape, known) {
|
|
3021
|
+
if (token === "--") return NONE;
|
|
3022
|
+
if (/^-+$/.test(token)) return UNKNOWN;
|
|
3023
|
+
if (/^-\d+$/.test(token)) return NONE;
|
|
3024
|
+
if (token.startsWith("--")) {
|
|
3025
|
+
if (token.includes("=")) return NONE;
|
|
3026
|
+
const takes = namesFlag(token, shape.takesValue, known);
|
|
3027
|
+
const none = namesFlag(token, shape.noValue, known);
|
|
3028
|
+
if (takes && none) return UNKNOWN;
|
|
3029
|
+
if (takes) return { kind: "takes", flag: token };
|
|
3030
|
+
if (none) return NONE;
|
|
3031
|
+
return UNKNOWN;
|
|
3032
|
+
}
|
|
3033
|
+
const letters = token.slice(1);
|
|
3034
|
+
if (!letters) return NONE;
|
|
3035
|
+
for (let i = 0; i < letters.length; i++) {
|
|
3036
|
+
const name = `-${letters[i]}`;
|
|
3037
|
+
if (shape.takesValue.has(name)) {
|
|
3038
|
+
return i === letters.length - 1 ? { kind: "takes", flag: name } : NONE;
|
|
3039
|
+
}
|
|
3040
|
+
if (!shape.noValue.has(name)) return UNKNOWN;
|
|
3041
|
+
}
|
|
3042
|
+
return NONE;
|
|
3043
|
+
}
|
|
3044
|
+
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
3045
|
+
const shape = PATTERN_VERBS[verb];
|
|
3046
|
+
if (!shape) return args.map((a) => a.value);
|
|
3047
|
+
const known = knownLongFlags(verb);
|
|
3048
|
+
const names = flags.flatMap((f) => flagNamesOf(f, shape));
|
|
3049
|
+
const patternElsewhere = names.some(
|
|
3050
|
+
(n) => namesFlag(n, shape.patternFlags, known) || namesFlag(n, shape.noPatternFlags, known)
|
|
3051
|
+
);
|
|
3052
|
+
const excused = /* @__PURE__ */ new Set();
|
|
3053
|
+
const fileFlags = FILE_OPERAND_FLAGS[verb];
|
|
3054
|
+
const optionsEnd = words.findIndex((w, i) => i >= from && w === "--");
|
|
3055
|
+
const pastOptions = (a) => optionsEnd >= 0 && a.argv > optionsEnd;
|
|
3056
|
+
for (const a of args) {
|
|
3057
|
+
if (a.afterFlag === null || pastOptions(a)) continue;
|
|
3058
|
+
const e = flagEffect(a.afterFlag, shape, known);
|
|
3059
|
+
if (e.kind !== "takes") continue;
|
|
3060
|
+
if (fileFlags && namesFlag(e.flag, fileFlags, known)) continue;
|
|
3061
|
+
excused.add(a);
|
|
3062
|
+
}
|
|
3063
|
+
const patternArgv = (() => {
|
|
3064
|
+
for (let i = from; i < words.length; i++) {
|
|
3065
|
+
const w = words[i];
|
|
3066
|
+
if (w === null) return -1;
|
|
3067
|
+
if (w === "--") return i + 1;
|
|
3068
|
+
if (w.startsWith("-")) {
|
|
3069
|
+
const e = flagEffect(w, shape, known);
|
|
3070
|
+
if (e.kind === "takes") i += 1;
|
|
3071
|
+
else if (e.kind === "unknown") return -1;
|
|
3072
|
+
continue;
|
|
3073
|
+
}
|
|
3074
|
+
return i;
|
|
3075
|
+
}
|
|
3076
|
+
return -1;
|
|
3077
|
+
})();
|
|
3078
|
+
if (!patternElsewhere && patternArgv >= 0) {
|
|
3079
|
+
const a = args.find((x) => x.argv === patternArgv);
|
|
3080
|
+
if (a) excused.add(a);
|
|
3081
|
+
}
|
|
3082
|
+
return args.filter((a) => !excused.has(a)).map((a) => a.value);
|
|
3083
|
+
}
|
|
3084
|
+
function flagOperandFiles(verb, words, from) {
|
|
3085
|
+
const flags = FILE_OPERAND_FLAGS[verb];
|
|
3086
|
+
if (!flags) return [];
|
|
3087
|
+
const known = knownLongFlags(verb);
|
|
3088
|
+
const out = [];
|
|
3089
|
+
for (let i = from; i < words.length; i++) {
|
|
3090
|
+
const w = words[i];
|
|
3091
|
+
if (w === null || !w.startsWith("-") || w === "--") continue;
|
|
3092
|
+
if (w.startsWith("--")) {
|
|
3093
|
+
const eq = w.indexOf("=");
|
|
3094
|
+
if (eq <= 0) continue;
|
|
3095
|
+
const name = w.slice(0, eq);
|
|
3096
|
+
const value = w.slice(eq + 1);
|
|
3097
|
+
if (!value) continue;
|
|
3098
|
+
if (namesFlag(name, flags, known)) {
|
|
3099
|
+
out.push(value);
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
const shape2 = PATTERN_VERBS[verb];
|
|
3103
|
+
if (!shape2) continue;
|
|
3104
|
+
const recognised = namesFlag(name, shape2.takesValue, known) || namesFlag(name, shape2.noValue, known) || namesFlag(name, shape2.patternFlags, known) || namesFlag(name, shape2.noPatternFlags, known);
|
|
3105
|
+
if (!recognised) out.push(value);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
const shape = PATTERN_VERBS[verb];
|
|
3109
|
+
const letters = w.slice(1);
|
|
3110
|
+
for (let j = 0; j < letters.length; j++) {
|
|
3111
|
+
const name = `-${letters[j]}`;
|
|
3112
|
+
const isFileFlag = flags.has(name);
|
|
3113
|
+
const argTaking = isFileFlag || (shape?.takesValue.has(name) ?? false) || (READER_VALUE_LETTERS[verb] ?? []).includes(letters[j]);
|
|
3114
|
+
if (!argTaking) continue;
|
|
3115
|
+
const attached = letters.slice(j + 1);
|
|
3116
|
+
if (isFileFlag && attached) out.push(attached);
|
|
3117
|
+
break;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return out;
|
|
3121
|
+
}
|
|
2559
3122
|
function wrappedReadPaths(words, name) {
|
|
2560
3123
|
if (name === "find") {
|
|
2561
3124
|
const { k, starts } = findStartPoints(words, 0);
|
|
@@ -2563,7 +3126,14 @@ function wrappedReadPaths(words, name) {
|
|
|
2563
3126
|
}
|
|
2564
3127
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
2565
3128
|
const h = unwrapCommandHead(words);
|
|
2566
|
-
|
|
3129
|
+
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
3130
|
+
const head = baseWord(words[h]);
|
|
3131
|
+
const rest = words.slice(h + 1);
|
|
3132
|
+
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
3133
|
+
return [
|
|
3134
|
+
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
3135
|
+
...flagOperandFiles(head, words, h + 1)
|
|
3136
|
+
];
|
|
2567
3137
|
}
|
|
2568
3138
|
function literalShellPayload(words, name) {
|
|
2569
3139
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
@@ -3713,6 +4283,21 @@ function isIgnoredTool(toolName, config) {
|
|
|
3713
4283
|
return matchesPattern(toolName, config.policy.ignoredTools);
|
|
3714
4284
|
}
|
|
3715
4285
|
|
|
4286
|
+
// src/utils/safe-text.ts
|
|
4287
|
+
var TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
4288
|
+
var CONTROL_CHAR_RE = /[\x00-\x1F\x7F]/g;
|
|
4289
|
+
function stripTerminalEscapes(s) {
|
|
4290
|
+
return s.replace(TERMINAL_ESCAPE_RE, "");
|
|
4291
|
+
}
|
|
4292
|
+
function stripControlChars(s) {
|
|
4293
|
+
return s.replace(CONTROL_CHAR_RE, "");
|
|
4294
|
+
}
|
|
4295
|
+
function safeMessage(value, max = 300) {
|
|
4296
|
+
const raw = typeof value === "string" ? value : value instanceof Error ? value.message : String(value ?? "");
|
|
4297
|
+
const s = stripTerminalEscapes(raw).replace(/\s+/g, " ").trim();
|
|
4298
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
3716
4301
|
// src/shields/index.ts
|
|
3717
4302
|
var import_safe_regex23 = __toESM(require("safe-regex2"));
|
|
3718
4303
|
|
|
@@ -4794,9 +5379,13 @@ function computeAgentDeviceScore(opts) {
|
|
|
4794
5379
|
}
|
|
4795
5380
|
|
|
4796
5381
|
// src/blast/index.ts
|
|
5382
|
+
var MAX_BLAST_PATH = 4096;
|
|
4797
5383
|
function truncateBlastPath(full) {
|
|
4798
5384
|
if (!full) return "";
|
|
4799
|
-
|
|
5385
|
+
if (full.length > MAX_BLAST_PATH) full = full.slice(-MAX_BLAST_PATH);
|
|
5386
|
+
let end = full.length;
|
|
5387
|
+
while (end > 0 && (full[end - 1] === "/" || full[end - 1] === "\\")) end--;
|
|
5388
|
+
const cleaned = full.slice(0, end);
|
|
4800
5389
|
const parts = cleaned.split(/[/\\]+/).filter((p) => p.length > 0);
|
|
4801
5390
|
if (parts.length <= 2) {
|
|
4802
5391
|
return cleaned.startsWith("~") && !cleaned.startsWith("~/") ? cleaned : cleaned.startsWith("~/") ? cleaned : parts.join("/");
|
|
@@ -5083,8 +5672,8 @@ function matchCanaryArgs(args, values) {
|
|
|
5083
5672
|
|
|
5084
5673
|
// src/scan/canonical.ts
|
|
5085
5674
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5086
|
-
var CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
5087
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
5675
|
+
var CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
|
|
5676
|
+
var CANONICAL_EXTRACTOR_HASH = "2823cd6a54a1fca7";
|
|
5088
5677
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
5089
5678
|
function extractCanonicalFindings(call, ctx) {
|
|
5090
5679
|
const out = [];
|
|
@@ -5403,13 +5992,9 @@ function toScanFinding(c) {
|
|
|
5403
5992
|
lineIndex: c.lineIndex
|
|
5404
5993
|
};
|
|
5405
5994
|
}
|
|
5406
|
-
var TERMINAL_ESCAPE_RE = (
|
|
5407
|
-
// eslint-disable-next-line no-control-regex
|
|
5408
|
-
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g
|
|
5409
|
-
);
|
|
5410
5995
|
function previewArgs(input, max) {
|
|
5411
5996
|
const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
5412
|
-
const s = String(cmd)
|
|
5997
|
+
const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
|
|
5413
5998
|
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
5414
5999
|
}
|
|
5415
6000
|
function makeFinding(args) {
|
|
@@ -5475,7 +6060,9 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5475
6060
|
LONG_OUTPUT_THRESHOLD_BYTES,
|
|
5476
6061
|
LOOP_MAX_RECORDS,
|
|
5477
6062
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
6063
|
+
MAX_BLAST_PATH,
|
|
5478
6064
|
NET_BINARIES,
|
|
6065
|
+
PATTERN_VERB_NAMES,
|
|
5479
6066
|
PRIVILEGE_ESCALATION_RE,
|
|
5480
6067
|
REALTIME_PII_PATTERNS,
|
|
5481
6068
|
SCAN_SIGNAL_WEIGHTS,
|
|
@@ -5512,6 +6099,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5512
6099
|
extractSessionLevelFindings,
|
|
5513
6100
|
extractShellDestTokens,
|
|
5514
6101
|
extractShellDestinations,
|
|
6102
|
+
fileOperandFlagsOf,
|
|
5515
6103
|
getCompiledRegex,
|
|
5516
6104
|
getNestedValue,
|
|
5517
6105
|
hostMatches,
|
|
@@ -5531,10 +6119,12 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5531
6119
|
normalizeIpLiteral,
|
|
5532
6120
|
parseAllSshHostsFromCommand,
|
|
5533
6121
|
parseDestHost,
|
|
6122
|
+
patternShapeOf,
|
|
5534
6123
|
positionedArgs,
|
|
5535
6124
|
previewArgs,
|
|
5536
6125
|
redactText,
|
|
5537
6126
|
resolvePinned,
|
|
6127
|
+
safeMessage,
|
|
5538
6128
|
sampleCopyCommand,
|
|
5539
6129
|
scanArgs,
|
|
5540
6130
|
scanInjection,
|
|
@@ -5543,6 +6133,8 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5543
6133
|
ssrfDestinationFloor,
|
|
5544
6134
|
ssrfFloor,
|
|
5545
6135
|
ssrfReason,
|
|
6136
|
+
stripControlChars,
|
|
6137
|
+
stripTerminalEscapes,
|
|
5546
6138
|
summarizeBlast,
|
|
5547
6139
|
summarizeScan,
|
|
5548
6140
|
toScanFinding,
|