@node9/proxy 2.13.1 → 2.14.1
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 +830 -33
- package/dist/cli.mjs +830 -33
- package/dist/dashboard.mjs +814 -31
- package/dist/index.js +787 -28
- package/dist/index.mjs +787 -28
- package/dist/scan-ink.mjs +372 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -905,6 +905,19 @@ function parseShared(command) {
|
|
|
905
905
|
astCache.set(command, parsed);
|
|
906
906
|
return parsed;
|
|
907
907
|
}
|
|
908
|
+
function byteOffsetToCharIndex(command) {
|
|
909
|
+
if (!/[^\u0000-\u007F]/.test(command)) return null;
|
|
910
|
+
const map = /* @__PURE__ */ new Map();
|
|
911
|
+
let byte = 0;
|
|
912
|
+
for (let i = 0; i < command.length; ) {
|
|
913
|
+
map.set(byte, i);
|
|
914
|
+
const cp = command.codePointAt(i);
|
|
915
|
+
byte += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
916
|
+
i += cp > 65535 ? 2 : 1;
|
|
917
|
+
}
|
|
918
|
+
map.set(byte, command.length);
|
|
919
|
+
return (b) => map.get(b) ?? -1;
|
|
920
|
+
}
|
|
908
921
|
function cachedNormalize(command, compute) {
|
|
909
922
|
const hit = normalizeCache.get(command);
|
|
910
923
|
if (hit !== void 0) {
|
|
@@ -934,6 +947,8 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
934
947
|
const f = parseShared(command);
|
|
935
948
|
if (f === PARSE_FAIL) return { posix: command, separator: command };
|
|
936
949
|
try {
|
|
950
|
+
const toCharIndex = byteOffsetToCharIndex(command);
|
|
951
|
+
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
937
952
|
const strips = [];
|
|
938
953
|
const rewrites = [];
|
|
939
954
|
const quoteOnlyRewrites = [];
|
|
@@ -954,8 +969,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
954
969
|
const quotedNode = nextParts[0];
|
|
955
970
|
const nt = syntax.NodeType(quotedNode);
|
|
956
971
|
const markStrip = () => {
|
|
957
|
-
const s = next.Pos().Offset();
|
|
958
|
-
const e = next.End().Offset();
|
|
972
|
+
const s = at(next.Pos().Offset());
|
|
973
|
+
const e = at(next.End().Offset());
|
|
974
|
+
if (s < 0 || e < 0) return;
|
|
959
975
|
strips.push([s, e]);
|
|
960
976
|
msgSpans.add(`${s}:${e}`);
|
|
961
977
|
};
|
|
@@ -972,8 +988,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
972
988
|
}
|
|
973
989
|
}
|
|
974
990
|
for (const arg of args) {
|
|
975
|
-
const s = arg.Pos().Offset();
|
|
976
|
-
const e = arg.End().Offset();
|
|
991
|
+
const s = at(arg.Pos().Offset());
|
|
992
|
+
const e = at(arg.End().Offset());
|
|
993
|
+
if (s < 0 || e < 0) continue;
|
|
977
994
|
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
978
995
|
const resolved = resolveWordLiteral(arg);
|
|
979
996
|
if (resolved === null) continue;
|
|
@@ -1302,20 +1319,32 @@ function isProtectedHomePath(rawPath) {
|
|
|
1302
1319
|
}
|
|
1303
1320
|
return true;
|
|
1304
1321
|
}
|
|
1305
|
-
function
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const name = (words[0] ?? "").toLowerCase();
|
|
1310
|
-
const flags = [];
|
|
1311
|
-
const paths = [];
|
|
1312
|
-
for (let i = 1; i < words.length; i++) {
|
|
1322
|
+
function positionedArgs(words, from = 1, to = words.length) {
|
|
1323
|
+
const out = [];
|
|
1324
|
+
let afterFlag = null;
|
|
1325
|
+
for (let i = from; i < to; i++) {
|
|
1313
1326
|
const v = words[i];
|
|
1314
|
-
if (v === null)
|
|
1315
|
-
|
|
1316
|
-
|
|
1327
|
+
if (v === null) {
|
|
1328
|
+
afterFlag = null;
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (v.startsWith("-")) {
|
|
1332
|
+
afterFlag = v;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
out.push({ value: v, index: out.length, argv: i, afterFlag });
|
|
1336
|
+
afterFlag = null;
|
|
1317
1337
|
}
|
|
1318
|
-
return
|
|
1338
|
+
return out;
|
|
1339
|
+
}
|
|
1340
|
+
function extractLiteralArgs(callExpr) {
|
|
1341
|
+
const rawArgs = callExpr.Args || [];
|
|
1342
|
+
if (rawArgs.length === 0) return { name: "", flags: [], paths: [], words: [], args: [] };
|
|
1343
|
+
const words = rawArgs.map((a) => resolveWordLiteral(a));
|
|
1344
|
+
const name = baseWord(words[0]);
|
|
1345
|
+
const flags = words.slice(1).filter((w) => w !== null && w.startsWith("-"));
|
|
1346
|
+
const args = positionedArgs(words);
|
|
1347
|
+
return { name, flags, paths: args.map((a) => a.value), words, args };
|
|
1319
1348
|
}
|
|
1320
1349
|
function resolveWordLiteral(w) {
|
|
1321
1350
|
const parts = w?.Parts || [];
|
|
@@ -1587,7 +1616,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1587
1616
|
return result?.verdict !== "block";
|
|
1588
1617
|
}
|
|
1589
1618
|
if (nodeType !== "CallExpr") return true;
|
|
1590
|
-
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
1619
|
+
const { name, flags, paths, words, args } = extractLiteralArgs(n);
|
|
1591
1620
|
if (!name) return true;
|
|
1592
1621
|
if (name === "rm") {
|
|
1593
1622
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -1627,13 +1656,16 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1627
1656
|
return true;
|
|
1628
1657
|
}
|
|
1629
1658
|
}
|
|
1630
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
1659
|
+
const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
|
|
1631
1660
|
if (readPaths) {
|
|
1632
1661
|
for (const p of readPaths) {
|
|
1633
1662
|
result = stricter(result, matchSensitivePath2(p));
|
|
1634
1663
|
if (result?.verdict === "block") return false;
|
|
1635
1664
|
}
|
|
1636
1665
|
}
|
|
1666
|
+
for (const p of copySourcePaths(words)) {
|
|
1667
|
+
result = stricter(result, copyVerdictOf(matchSensitivePath2(p)));
|
|
1668
|
+
}
|
|
1637
1669
|
return true;
|
|
1638
1670
|
});
|
|
1639
1671
|
return result;
|
|
@@ -1646,6 +1678,184 @@ function stricter(a, b) {
|
|
|
1646
1678
|
if (!b) return a;
|
|
1647
1679
|
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
1648
1680
|
}
|
|
1681
|
+
function flagInfo(w) {
|
|
1682
|
+
if (w.startsWith("--")) {
|
|
1683
|
+
const eq = w.indexOf("=");
|
|
1684
|
+
return eq < 0 ? { letter: null, long: w, attached: null } : { letter: null, long: w.slice(0, eq), attached: w.slice(eq + 1) };
|
|
1685
|
+
}
|
|
1686
|
+
const m = /^-([a-zA-Z]+)(.*)$/.exec(w);
|
|
1687
|
+
if (!m) return { letter: null, long: null, attached: null };
|
|
1688
|
+
return { letter: m[1][m[1].length - 1], long: null, attached: m[2] === "" ? null : m[2] };
|
|
1689
|
+
}
|
|
1690
|
+
function flagIs(w, names) {
|
|
1691
|
+
if (w === null) return false;
|
|
1692
|
+
const f = flagInfo(w);
|
|
1693
|
+
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
1694
|
+
}
|
|
1695
|
+
function operandOf(a, names, valueLetters) {
|
|
1696
|
+
if (!names || a.afterFlag === null) return false;
|
|
1697
|
+
const w = a.afterFlag;
|
|
1698
|
+
if (!w.startsWith("--") && valueLetters) {
|
|
1699
|
+
const letters = w.slice(1);
|
|
1700
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1701
|
+
if (!valueLetters.includes(letters[i])) continue;
|
|
1702
|
+
return i === letters.length - 1 && names.includes(letters[i]);
|
|
1703
|
+
}
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
if (w.startsWith("--") && !w.includes("=")) {
|
|
1707
|
+
const longs = names.filter((n) => n.startsWith("--"));
|
|
1708
|
+
if (longs.some((n) => n === w || w.length >= 3 && n.startsWith(w))) return true;
|
|
1709
|
+
}
|
|
1710
|
+
return flagIs(w, names) && flagInfo(w).attached === null;
|
|
1711
|
+
}
|
|
1712
|
+
function resolveCopyShape(words, h) {
|
|
1713
|
+
const verb = baseWord(words[h]);
|
|
1714
|
+
if (!verb) return null;
|
|
1715
|
+
const direct = COPY_VERBS[verb];
|
|
1716
|
+
if (direct) return { shape: direct, last: h };
|
|
1717
|
+
const slots = positionedArgs(words, h + 1);
|
|
1718
|
+
for (let i = 0; i < slots.length; i++) {
|
|
1719
|
+
for (let n = 3; n >= 1; n--) {
|
|
1720
|
+
const part = slots.slice(i, i + n);
|
|
1721
|
+
if (part.length < n) continue;
|
|
1722
|
+
const key = [verb, ...part.map((a) => a.value.toLowerCase())].join(" ");
|
|
1723
|
+
const shape = COPY_VERBS[key];
|
|
1724
|
+
if (shape) return { shape, last: part[n - 1].argv };
|
|
1725
|
+
}
|
|
1726
|
+
if (slots[i].afterFlag === null) return null;
|
|
1727
|
+
}
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
function findStartPoints(words, h) {
|
|
1731
|
+
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1732
|
+
if (k < 0) return { k, starts: [] };
|
|
1733
|
+
const firstPredicate = words.findIndex(
|
|
1734
|
+
(w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
1735
|
+
);
|
|
1736
|
+
const end = firstPredicate > h ? firstPredicate : k;
|
|
1737
|
+
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
1738
|
+
}
|
|
1739
|
+
function copySourcePaths(words) {
|
|
1740
|
+
const h = unwrapCommandHead(words);
|
|
1741
|
+
const fi = words.findIndex((w, i) => i <= h && baseWord(w) === "find");
|
|
1742
|
+
if (fi >= 0) {
|
|
1743
|
+
const { k, starts } = findStartPoints(words, fi);
|
|
1744
|
+
if (k < 0) return [];
|
|
1745
|
+
const action = unwrapCommandHead(words.slice(k + 1));
|
|
1746
|
+
return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
|
|
1747
|
+
}
|
|
1748
|
+
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
1749
|
+
const r = resolveCopyShape(words, h);
|
|
1750
|
+
if (!r) return [];
|
|
1751
|
+
const { shape, last } = r;
|
|
1752
|
+
const args = positionedArgs(words, last + 1);
|
|
1753
|
+
const tail = words.slice(last + 1);
|
|
1754
|
+
const copyOptionsEnd = tail.findIndex((w) => w === "--");
|
|
1755
|
+
const copyPastOptions = (a) => copyOptionsEnd >= 0 && a.argv > last + 1 + copyOptionsEnd;
|
|
1756
|
+
const skipped = (a) => !copyPastOptions(a) && operandOf(a, shape.skipFlags, shape.valueLetters);
|
|
1757
|
+
const firstValueLetter = (w) => {
|
|
1758
|
+
const letters = w.slice(1);
|
|
1759
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1760
|
+
if ((shape.valueLetters ?? []).includes(letters[i]))
|
|
1761
|
+
return { letter: letters[i], last: i === letters.length - 1 };
|
|
1762
|
+
}
|
|
1763
|
+
return null;
|
|
1764
|
+
};
|
|
1765
|
+
const isTargetDirFlag = (w) => {
|
|
1766
|
+
if (w.startsWith("--")) {
|
|
1767
|
+
const name = w.includes("=") ? w.slice(0, w.indexOf("=")) : w;
|
|
1768
|
+
return name.length >= 3 && "--target-directory".startsWith(name);
|
|
1769
|
+
}
|
|
1770
|
+
return firstValueLetter(w)?.letter === "t";
|
|
1771
|
+
};
|
|
1772
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && isTargetDirFlag(w));
|
|
1773
|
+
const targetTakesNextWord = (w) => {
|
|
1774
|
+
if (w.startsWith("--"))
|
|
1775
|
+
return !w.includes("=") && w.length >= 3 && "--target-directory".startsWith(w);
|
|
1776
|
+
const f = firstValueLetter(w);
|
|
1777
|
+
return f !== null && f.letter === "t" && f.last;
|
|
1778
|
+
};
|
|
1779
|
+
const targetOperand = (a) => targetDir && a.afterFlag !== null && !copyPastOptions(a) && targetTakesNextWord(a.afterFlag);
|
|
1780
|
+
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
1781
|
+
const dynamicDest = lastOperand === null;
|
|
1782
|
+
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
1783
|
+
let src;
|
|
1784
|
+
switch (shape.source) {
|
|
1785
|
+
case "all":
|
|
1786
|
+
src = args;
|
|
1787
|
+
break;
|
|
1788
|
+
case "first":
|
|
1789
|
+
src = targetDir ? args : args.slice(0, 1);
|
|
1790
|
+
break;
|
|
1791
|
+
case "flagOperand": {
|
|
1792
|
+
const namesSource = (f) => {
|
|
1793
|
+
const names = shape.sourceFlags ?? [];
|
|
1794
|
+
if (f.long !== null)
|
|
1795
|
+
return names.some(
|
|
1796
|
+
(n) => n.startsWith("--") && (n === f.long || f.long.length >= 3 && n.startsWith(f.long))
|
|
1797
|
+
);
|
|
1798
|
+
return f.letter !== null && names.includes(f.letter);
|
|
1799
|
+
};
|
|
1800
|
+
const inline = tail.filter((w) => w !== null && w.startsWith("-")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && namesSource(f)).map((f) => f.attached);
|
|
1801
|
+
return [
|
|
1802
|
+
...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
|
|
1803
|
+
...inline
|
|
1804
|
+
];
|
|
1805
|
+
}
|
|
1806
|
+
case "archive":
|
|
1807
|
+
src = archiveInputs(shape.archive, args, tail);
|
|
1808
|
+
break;
|
|
1809
|
+
case "allButLast":
|
|
1810
|
+
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
1811
|
+
break;
|
|
1812
|
+
}
|
|
1813
|
+
const sources = src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
1814
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand)) {
|
|
1815
|
+
const jailedSources = sources.filter((p) => matchSensitivePath2(p));
|
|
1816
|
+
const dirOf = (p) => /[\\/]/.test(p) ? p.replace(/[\\/][^\\/]*$/, "") : "";
|
|
1817
|
+
if (jailedSources.length === 0) return [];
|
|
1818
|
+
if (jailedSources.every((p) => dirOf(p) === dirOf(lastOperand))) return [];
|
|
1819
|
+
}
|
|
1820
|
+
return sources;
|
|
1821
|
+
}
|
|
1822
|
+
function archiveInputs(kind, args, tail) {
|
|
1823
|
+
const first = args[0];
|
|
1824
|
+
const bareKey = first && first.afterFlag === null && TAR_MODE_WORD.test(first.value);
|
|
1825
|
+
if (kind === "tar") {
|
|
1826
|
+
const flagsText = tail.filter((w) => w !== null && w.startsWith("-")).join(" ");
|
|
1827
|
+
const mode = (bareKey ? first.value : "") + flagsText;
|
|
1828
|
+
const extracting = /x|t/.test(bareKey ? first.value.replace(/f/g, "") : "") || /(^|\s)-[a-zA-Z]*[xt]|--extract|--list|--get/.test(flagsText);
|
|
1829
|
+
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
1830
|
+
if (extracting && !writing) return [];
|
|
1831
|
+
void mode;
|
|
1832
|
+
if (!bareKey) return args;
|
|
1833
|
+
let i = 1;
|
|
1834
|
+
const fromDirs = [];
|
|
1835
|
+
for (const ch of first.value) {
|
|
1836
|
+
if (!TAR_VALUE_LETTERS.includes(ch)) continue;
|
|
1837
|
+
const operand = args[i];
|
|
1838
|
+
if (!operand || operand.afterFlag !== null) break;
|
|
1839
|
+
i += 1;
|
|
1840
|
+
if (ch === "C") fromDirs.push(operand);
|
|
1841
|
+
}
|
|
1842
|
+
return [...fromDirs, ...args.slice(i)];
|
|
1843
|
+
}
|
|
1844
|
+
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
1845
|
+
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
1846
|
+
return args.slice(2);
|
|
1847
|
+
}
|
|
1848
|
+
function copyVerdictOf(hit) {
|
|
1849
|
+
if (!hit) return null;
|
|
1850
|
+
const ruleName = COPY_RULE_OF[hit.ruleName];
|
|
1851
|
+
if (!ruleName) return null;
|
|
1852
|
+
return {
|
|
1853
|
+
ruleName,
|
|
1854
|
+
verdict: "review",
|
|
1855
|
+
reason: `Copying ${hit.path} moves a credential out of its jail (project-jail shield)`,
|
|
1856
|
+
path: hit.path
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1649
1859
|
function matchSensitivePath2(p) {
|
|
1650
1860
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1651
1861
|
if (sp.match(p))
|
|
@@ -1653,16 +1863,156 @@ function matchSensitivePath2(p) {
|
|
|
1653
1863
|
}
|
|
1654
1864
|
return null;
|
|
1655
1865
|
}
|
|
1866
|
+
function baseWord(w) {
|
|
1867
|
+
return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1868
|
+
}
|
|
1869
|
+
function namesFlag(name, candidates2, known) {
|
|
1870
|
+
if (candidates2.has(name)) return true;
|
|
1871
|
+
if (!name.startsWith("--") || name.length < 3) return false;
|
|
1872
|
+
if (known?.has(name)) return false;
|
|
1873
|
+
for (const c of candidates2) if (c.startsWith(name)) return true;
|
|
1874
|
+
return false;
|
|
1875
|
+
}
|
|
1876
|
+
function knownLongFlags(verb) {
|
|
1877
|
+
const out = /* @__PURE__ */ new Set();
|
|
1878
|
+
const shape = PATTERN_VERBS[verb];
|
|
1879
|
+
if (shape) {
|
|
1880
|
+
for (const set of [shape.takesValue, shape.noValue, shape.patternFlags, shape.noPatternFlags])
|
|
1881
|
+
for (const f of set) if (f.startsWith("--")) out.add(f);
|
|
1882
|
+
}
|
|
1883
|
+
for (const f of FILE_OPERAND_FLAGS[verb] ?? []) if (f.startsWith("--")) out.add(f);
|
|
1884
|
+
return out;
|
|
1885
|
+
}
|
|
1886
|
+
function flagNamesOf(token, shape) {
|
|
1887
|
+
if (token.startsWith("--")) {
|
|
1888
|
+
const eq = token.indexOf("=");
|
|
1889
|
+
return [eq > 0 ? token.slice(0, eq) : token];
|
|
1890
|
+
}
|
|
1891
|
+
const out = [];
|
|
1892
|
+
for (const c of token.slice(1)) {
|
|
1893
|
+
const name = `-${c}`;
|
|
1894
|
+
out.push(name);
|
|
1895
|
+
if (shape?.takesValue.has(name)) break;
|
|
1896
|
+
}
|
|
1897
|
+
return out;
|
|
1898
|
+
}
|
|
1899
|
+
function flagEffect(token, shape, known) {
|
|
1900
|
+
if (token === "--") return NONE;
|
|
1901
|
+
if (/^-+$/.test(token)) return UNKNOWN;
|
|
1902
|
+
if (/^-\d+$/.test(token)) return NONE;
|
|
1903
|
+
if (token.startsWith("--")) {
|
|
1904
|
+
if (token.includes("=")) return NONE;
|
|
1905
|
+
const takes = namesFlag(token, shape.takesValue, known);
|
|
1906
|
+
const none = namesFlag(token, shape.noValue, known);
|
|
1907
|
+
if (takes && none) return UNKNOWN;
|
|
1908
|
+
if (takes) return { kind: "takes", flag: token };
|
|
1909
|
+
if (none) return NONE;
|
|
1910
|
+
return UNKNOWN;
|
|
1911
|
+
}
|
|
1912
|
+
const letters = token.slice(1);
|
|
1913
|
+
if (!letters) return NONE;
|
|
1914
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1915
|
+
const name = `-${letters[i]}`;
|
|
1916
|
+
if (shape.takesValue.has(name)) {
|
|
1917
|
+
return i === letters.length - 1 ? { kind: "takes", flag: name } : NONE;
|
|
1918
|
+
}
|
|
1919
|
+
if (!shape.noValue.has(name)) return UNKNOWN;
|
|
1920
|
+
}
|
|
1921
|
+
return NONE;
|
|
1922
|
+
}
|
|
1923
|
+
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
1924
|
+
const shape = PATTERN_VERBS[verb];
|
|
1925
|
+
if (!shape) return args.map((a) => a.value);
|
|
1926
|
+
const known = knownLongFlags(verb);
|
|
1927
|
+
const names = flags.flatMap((f) => flagNamesOf(f, shape));
|
|
1928
|
+
const patternElsewhere = names.some(
|
|
1929
|
+
(n) => namesFlag(n, shape.patternFlags, known) || namesFlag(n, shape.noPatternFlags, known)
|
|
1930
|
+
);
|
|
1931
|
+
const excused = /* @__PURE__ */ new Set();
|
|
1932
|
+
const fileFlags = FILE_OPERAND_FLAGS[verb];
|
|
1933
|
+
const optionsEnd = words.findIndex((w, i) => i >= from && w === "--");
|
|
1934
|
+
const pastOptions = (a) => optionsEnd >= 0 && a.argv > optionsEnd;
|
|
1935
|
+
for (const a of args) {
|
|
1936
|
+
if (a.afterFlag === null || pastOptions(a)) continue;
|
|
1937
|
+
const e = flagEffect(a.afterFlag, shape, known);
|
|
1938
|
+
if (e.kind !== "takes") continue;
|
|
1939
|
+
if (fileFlags && namesFlag(e.flag, fileFlags, known)) continue;
|
|
1940
|
+
excused.add(a);
|
|
1941
|
+
}
|
|
1942
|
+
const patternArgv = (() => {
|
|
1943
|
+
for (let i = from; i < words.length; i++) {
|
|
1944
|
+
const w = words[i];
|
|
1945
|
+
if (w === null) return -1;
|
|
1946
|
+
if (w === "--") return i + 1;
|
|
1947
|
+
if (w.startsWith("-")) {
|
|
1948
|
+
const e = flagEffect(w, shape, known);
|
|
1949
|
+
if (e.kind === "takes") i += 1;
|
|
1950
|
+
else if (e.kind === "unknown") return -1;
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
return i;
|
|
1954
|
+
}
|
|
1955
|
+
return -1;
|
|
1956
|
+
})();
|
|
1957
|
+
if (!patternElsewhere && patternArgv >= 0) {
|
|
1958
|
+
const a = args.find((x) => x.argv === patternArgv);
|
|
1959
|
+
if (a) excused.add(a);
|
|
1960
|
+
}
|
|
1961
|
+
return args.filter((a) => !excused.has(a)).map((a) => a.value);
|
|
1962
|
+
}
|
|
1963
|
+
function flagOperandFiles(verb, words, from) {
|
|
1964
|
+
const flags = FILE_OPERAND_FLAGS[verb];
|
|
1965
|
+
if (!flags) return [];
|
|
1966
|
+
const known = knownLongFlags(verb);
|
|
1967
|
+
const out = [];
|
|
1968
|
+
for (let i = from; i < words.length; i++) {
|
|
1969
|
+
const w = words[i];
|
|
1970
|
+
if (w === null || !w.startsWith("-") || w === "--") continue;
|
|
1971
|
+
if (w.startsWith("--")) {
|
|
1972
|
+
const eq = w.indexOf("=");
|
|
1973
|
+
if (eq <= 0) continue;
|
|
1974
|
+
const name = w.slice(0, eq);
|
|
1975
|
+
const value = w.slice(eq + 1);
|
|
1976
|
+
if (!value) continue;
|
|
1977
|
+
if (namesFlag(name, flags, known)) {
|
|
1978
|
+
out.push(value);
|
|
1979
|
+
continue;
|
|
1980
|
+
}
|
|
1981
|
+
const shape2 = PATTERN_VERBS[verb];
|
|
1982
|
+
if (!shape2) continue;
|
|
1983
|
+
const recognised = namesFlag(name, shape2.takesValue, known) || namesFlag(name, shape2.noValue, known) || namesFlag(name, shape2.patternFlags, known) || namesFlag(name, shape2.noPatternFlags, known);
|
|
1984
|
+
if (!recognised) out.push(value);
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
const shape = PATTERN_VERBS[verb];
|
|
1988
|
+
const letters = w.slice(1);
|
|
1989
|
+
for (let j = 0; j < letters.length; j++) {
|
|
1990
|
+
const name = `-${letters[j]}`;
|
|
1991
|
+
const isFileFlag = flags.has(name);
|
|
1992
|
+
const argTaking = isFileFlag || (shape?.takesValue.has(name) ?? false) || (READER_VALUE_LETTERS[verb] ?? []).includes(letters[j]);
|
|
1993
|
+
if (!argTaking) continue;
|
|
1994
|
+
const attached = letters.slice(j + 1);
|
|
1995
|
+
if (isFileFlag && attached) out.push(attached);
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
return out;
|
|
2000
|
+
}
|
|
1656
2001
|
function wrappedReadPaths(words, name) {
|
|
1657
2002
|
if (name === "find") {
|
|
1658
|
-
const k = words
|
|
1659
|
-
|
|
1660
|
-
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
1661
|
-
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
2003
|
+
const { k, starts } = findStartPoints(words, 0);
|
|
2004
|
+
return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
|
|
1662
2005
|
}
|
|
1663
2006
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
1664
2007
|
const h = unwrapCommandHead(words);
|
|
1665
|
-
|
|
2008
|
+
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
2009
|
+
const head = baseWord(words[h]);
|
|
2010
|
+
const rest = words.slice(h + 1);
|
|
2011
|
+
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
2012
|
+
return [
|
|
2013
|
+
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
2014
|
+
...flagOperandFiles(head, words, h + 1)
|
|
2015
|
+
];
|
|
1666
2016
|
}
|
|
1667
2017
|
function literalShellPayload(words, name) {
|
|
1668
2018
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
@@ -2330,6 +2680,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2330
2680
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
2331
2681
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
2332
2682
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
2683
|
+
let pendingAstReview;
|
|
2333
2684
|
if (bashCommand !== null) {
|
|
2334
2685
|
const pipeVerdict = pipeChainVerdict(
|
|
2335
2686
|
bashCommand,
|
|
@@ -2341,7 +2692,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2341
2692
|
if (fsVerdict) {
|
|
2342
2693
|
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
2343
2694
|
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
2344
|
-
|
|
2695
|
+
const astVerdict = {
|
|
2345
2696
|
decision: fsVerdict.verdict,
|
|
2346
2697
|
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
2347
2698
|
reason: fsVerdict.reason,
|
|
@@ -2349,6 +2700,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2349
2700
|
ruleName: fsVerdict.ruleName,
|
|
2350
2701
|
ruleDescription: fsVerdict.reason
|
|
2351
2702
|
};
|
|
2703
|
+
if (fsVerdict.verdict === "block") return astVerdict;
|
|
2704
|
+
pendingAstReview = astVerdict;
|
|
2352
2705
|
}
|
|
2353
2706
|
const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
|
|
2354
2707
|
const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
|
|
@@ -2391,7 +2744,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2391
2744
|
const matchedRule = resolvePinned(matches);
|
|
2392
2745
|
if (matchedRule) {
|
|
2393
2746
|
if (matchedRule.verdict === "allow")
|
|
2394
|
-
return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2747
|
+
return pendingAstReview ?? { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2395
2748
|
return {
|
|
2396
2749
|
decision: matchedRule.verdict,
|
|
2397
2750
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
@@ -2418,6 +2771,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2418
2771
|
allTokens = analyzed.allTokens;
|
|
2419
2772
|
pathTokens = analyzed.paths;
|
|
2420
2773
|
const candidates2 = [];
|
|
2774
|
+
if (pendingAstReview) candidates2.push(pendingAstReview);
|
|
2421
2775
|
const evalVerdict = detectDangerousShellExec(shellCommand);
|
|
2422
2776
|
if (evalVerdict === "block") {
|
|
2423
2777
|
return {
|
|
@@ -2714,6 +3068,12 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2714
3068
|
"read-ssh",
|
|
2715
3069
|
"read-gcp",
|
|
2716
3070
|
"read-cred",
|
|
3071
|
+
// Stage 4 (2026-09-11): a copy of a credential file scores like a READ of it --
|
|
3072
|
+
// read-ssh/aws/cred are critical, so copy-ssh/aws/cred are; read-env is high
|
|
3073
|
+
// (below), so copy-env joins the high list, not this one (/code-review).
|
|
3074
|
+
"copy-ssh",
|
|
3075
|
+
"copy-aws",
|
|
3076
|
+
"copy-cred",
|
|
2717
3077
|
"delete-repo",
|
|
2718
3078
|
"helm-uninstall",
|
|
2719
3079
|
"drop-table",
|
|
@@ -2726,6 +3086,7 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2726
3086
|
];
|
|
2727
3087
|
if (criticalPatterns.some((p) => n.includes(p))) return "critical";
|
|
2728
3088
|
const highPatterns = [
|
|
3089
|
+
"copy-env",
|
|
2729
3090
|
"force-push",
|
|
2730
3091
|
"force_push",
|
|
2731
3092
|
"git-destructive",
|
|
@@ -2743,6 +3104,11 @@ function narrativeRuleLabel(name) {
|
|
|
2743
3104
|
const map = {
|
|
2744
3105
|
"read-aws": "AWS credentials read",
|
|
2745
3106
|
"read-ssh": "SSH private key read",
|
|
3107
|
+
// Stage 4 copy twins, so `scan --narrative` prints a label, not a raw slug.
|
|
3108
|
+
"copy-ssh": "SSH private key copied out",
|
|
3109
|
+
"copy-aws": "AWS credentials copied out",
|
|
3110
|
+
"copy-env": ".env file copied out",
|
|
3111
|
+
"copy-cred": "credential file copied out",
|
|
2746
3112
|
"read-gcp": "GCP credentials read",
|
|
2747
3113
|
"read-cred": "credential file read",
|
|
2748
3114
|
"delete-repo": "GitHub repository deletion",
|
|
@@ -3332,7 +3698,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3332
3698
|
}
|
|
3333
3699
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3334
3700
|
}
|
|
3335
|
-
var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, 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, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, isReaderWord, positionalAfter, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, 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, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, 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, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
|
|
3701
|
+
var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, 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, BASH_TOOL_NAMES, 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, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, 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, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, 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, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, 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, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
|
|
3336
3702
|
var init_dist = __esm({
|
|
3337
3703
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3338
3704
|
"use strict";
|
|
@@ -4068,12 +4434,408 @@ var init_dist = __esm({
|
|
|
4068
4434
|
"nl",
|
|
4069
4435
|
"dd"
|
|
4070
4436
|
]);
|
|
4437
|
+
GREP_SHAPE = {
|
|
4438
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
4439
|
+
"-A",
|
|
4440
|
+
"-B",
|
|
4441
|
+
"-C",
|
|
4442
|
+
"-D",
|
|
4443
|
+
"-d",
|
|
4444
|
+
"-e",
|
|
4445
|
+
"-f",
|
|
4446
|
+
"-m",
|
|
4447
|
+
"--after-context",
|
|
4448
|
+
"--before-context",
|
|
4449
|
+
"--binary-files",
|
|
4450
|
+
"--context",
|
|
4451
|
+
"--devices",
|
|
4452
|
+
"--directories",
|
|
4453
|
+
"--exclude",
|
|
4454
|
+
"--exclude-dir",
|
|
4455
|
+
"--exclude-from",
|
|
4456
|
+
"--file",
|
|
4457
|
+
"--include",
|
|
4458
|
+
"--label",
|
|
4459
|
+
"--max-count",
|
|
4460
|
+
"--regexp"
|
|
4461
|
+
]),
|
|
4462
|
+
noValue: /* @__PURE__ */ new Set([
|
|
4463
|
+
"-E",
|
|
4464
|
+
"-F",
|
|
4465
|
+
"-G",
|
|
4466
|
+
"-P",
|
|
4467
|
+
"-i",
|
|
4468
|
+
"-y",
|
|
4469
|
+
"-v",
|
|
4470
|
+
"-V",
|
|
4471
|
+
"-w",
|
|
4472
|
+
"-x",
|
|
4473
|
+
"-c",
|
|
4474
|
+
"-l",
|
|
4475
|
+
"-L",
|
|
4476
|
+
"-o",
|
|
4477
|
+
"-q",
|
|
4478
|
+
"-s",
|
|
4479
|
+
"-b",
|
|
4480
|
+
"-H",
|
|
4481
|
+
"-h",
|
|
4482
|
+
"-n",
|
|
4483
|
+
"-T",
|
|
4484
|
+
"-Z",
|
|
4485
|
+
"-z",
|
|
4486
|
+
"-R",
|
|
4487
|
+
"-r",
|
|
4488
|
+
"-U",
|
|
4489
|
+
"-u",
|
|
4490
|
+
"-I",
|
|
4491
|
+
"-a",
|
|
4492
|
+
"--basic-regexp",
|
|
4493
|
+
"--binary",
|
|
4494
|
+
"--byte-offset",
|
|
4495
|
+
"--color",
|
|
4496
|
+
"--colour",
|
|
4497
|
+
"--count",
|
|
4498
|
+
"--dereference-recursive",
|
|
4499
|
+
"--extended-regexp",
|
|
4500
|
+
"--files-with-matches",
|
|
4501
|
+
"--files-without-match",
|
|
4502
|
+
"--fixed-strings",
|
|
4503
|
+
"--help",
|
|
4504
|
+
"--ignore-case",
|
|
4505
|
+
"--initial-tab",
|
|
4506
|
+
"--invert-match",
|
|
4507
|
+
"--line-buffered",
|
|
4508
|
+
"--line-number",
|
|
4509
|
+
"--line-regexp",
|
|
4510
|
+
"--no-filename",
|
|
4511
|
+
"--no-group-separator",
|
|
4512
|
+
"--no-ignore-case",
|
|
4513
|
+
"--no-messages",
|
|
4514
|
+
"--null",
|
|
4515
|
+
"--null-data",
|
|
4516
|
+
"--only-matching",
|
|
4517
|
+
"--perl-regexp",
|
|
4518
|
+
"--quiet",
|
|
4519
|
+
"--recursive",
|
|
4520
|
+
"--silent",
|
|
4521
|
+
"--text",
|
|
4522
|
+
"--version",
|
|
4523
|
+
"--with-filename",
|
|
4524
|
+
"--word-regexp"
|
|
4525
|
+
]),
|
|
4526
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
4527
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
|
|
4528
|
+
};
|
|
4529
|
+
PATTERN_VERBS = {
|
|
4530
|
+
grep: GREP_SHAPE,
|
|
4531
|
+
// /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
|
|
4532
|
+
// `grep -E` and `grep -F`. Read in full, not assumed.
|
|
4533
|
+
egrep: GREP_SHAPE,
|
|
4534
|
+
fgrep: GREP_SHAPE,
|
|
4535
|
+
// ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
|
|
4536
|
+
// was not installed on the measuring machine; it is, and that stale claim is why
|
|
4537
|
+
// this table shipped incomplete for two rounds, blocking ordinary searches like
|
|
4538
|
+
// `rg --sort-files .env src` (/code-review round 4).
|
|
4539
|
+
rg: {
|
|
4540
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
4541
|
+
"-A",
|
|
4542
|
+
"-B",
|
|
4543
|
+
"-C",
|
|
4544
|
+
"-d",
|
|
4545
|
+
"-E",
|
|
4546
|
+
"-e",
|
|
4547
|
+
"-f",
|
|
4548
|
+
"-g",
|
|
4549
|
+
"-j",
|
|
4550
|
+
"-M",
|
|
4551
|
+
"-m",
|
|
4552
|
+
"-r",
|
|
4553
|
+
"-t",
|
|
4554
|
+
"-T",
|
|
4555
|
+
"--after-context",
|
|
4556
|
+
"--before-context",
|
|
4557
|
+
"--color",
|
|
4558
|
+
"--colors",
|
|
4559
|
+
"--context",
|
|
4560
|
+
"--context-separator",
|
|
4561
|
+
"--dfa-size-limit",
|
|
4562
|
+
"--encoding",
|
|
4563
|
+
"--engine",
|
|
4564
|
+
"--field-context-separator",
|
|
4565
|
+
"--field-match-separator",
|
|
4566
|
+
"--file",
|
|
4567
|
+
"--generate",
|
|
4568
|
+
"--glob",
|
|
4569
|
+
"--hostname-bin",
|
|
4570
|
+
"--hyperlink-format",
|
|
4571
|
+
"--iglob",
|
|
4572
|
+
"--ignore-file",
|
|
4573
|
+
"--max-columns",
|
|
4574
|
+
"--max-count",
|
|
4575
|
+
"--max-depth",
|
|
4576
|
+
// An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
|
|
4577
|
+
// alias lines as plain switches and swept it into noValue, which hard-blocked
|
|
4578
|
+
// `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
|
|
4579
|
+
"--maxdepth",
|
|
4580
|
+
"--max-filesize",
|
|
4581
|
+
"--path-separator",
|
|
4582
|
+
"--pre",
|
|
4583
|
+
"--pre-glob",
|
|
4584
|
+
"--regexp",
|
|
4585
|
+
"--regex-size-limit",
|
|
4586
|
+
"--replace",
|
|
4587
|
+
"--sort",
|
|
4588
|
+
"--sortr",
|
|
4589
|
+
"--threads",
|
|
4590
|
+
"--type",
|
|
4591
|
+
"--type-add",
|
|
4592
|
+
"--type-clear",
|
|
4593
|
+
"--type-not"
|
|
4594
|
+
]),
|
|
4595
|
+
noValue: /* @__PURE__ */ new Set([
|
|
4596
|
+
"-.",
|
|
4597
|
+
"-0",
|
|
4598
|
+
"-a",
|
|
4599
|
+
"-b",
|
|
4600
|
+
"-c",
|
|
4601
|
+
"-F",
|
|
4602
|
+
"-h",
|
|
4603
|
+
"-H",
|
|
4604
|
+
"-i",
|
|
4605
|
+
"-I",
|
|
4606
|
+
"-l",
|
|
4607
|
+
"-L",
|
|
4608
|
+
"-n",
|
|
4609
|
+
"-N",
|
|
4610
|
+
"-o",
|
|
4611
|
+
"-p",
|
|
4612
|
+
"-P",
|
|
4613
|
+
"-q",
|
|
4614
|
+
"-s",
|
|
4615
|
+
"-S",
|
|
4616
|
+
"-u",
|
|
4617
|
+
"-U",
|
|
4618
|
+
"-v",
|
|
4619
|
+
"-V",
|
|
4620
|
+
"-w",
|
|
4621
|
+
"-x",
|
|
4622
|
+
"-z",
|
|
4623
|
+
"--auto-hybrid-regex",
|
|
4624
|
+
"--binary",
|
|
4625
|
+
"--block-buffered",
|
|
4626
|
+
"--byte-offset",
|
|
4627
|
+
"--case-sensitive",
|
|
4628
|
+
"--column",
|
|
4629
|
+
"--count",
|
|
4630
|
+
"--count-matches",
|
|
4631
|
+
"--crlf",
|
|
4632
|
+
"--debug",
|
|
4633
|
+
"--files",
|
|
4634
|
+
"--files-with-matches",
|
|
4635
|
+
"--files-without-match",
|
|
4636
|
+
"--fixed-strings",
|
|
4637
|
+
"--follow",
|
|
4638
|
+
"--glob-case-insensitive",
|
|
4639
|
+
"--heading",
|
|
4640
|
+
"--help",
|
|
4641
|
+
"--hidden",
|
|
4642
|
+
"--ignore-case",
|
|
4643
|
+
"--ignore-file-case-insensitive",
|
|
4644
|
+
"--include-zero",
|
|
4645
|
+
"--invert-match",
|
|
4646
|
+
"--json",
|
|
4647
|
+
"--line-buffered",
|
|
4648
|
+
"--line-number",
|
|
4649
|
+
"--line-regexp",
|
|
4650
|
+
"--max-columns-preview",
|
|
4651
|
+
"--mmap",
|
|
4652
|
+
"--multiline",
|
|
4653
|
+
"--multiline-dotall",
|
|
4654
|
+
"--no-column",
|
|
4655
|
+
"--no-config",
|
|
4656
|
+
"--no-context-separator",
|
|
4657
|
+
"--no-encoding",
|
|
4658
|
+
"--no-filename",
|
|
4659
|
+
"--no-ignore",
|
|
4660
|
+
"--no-ignore-dot",
|
|
4661
|
+
"--no-ignore-exclude",
|
|
4662
|
+
"--no-ignore-files",
|
|
4663
|
+
"--no-ignore-global",
|
|
4664
|
+
"--no-ignore-messages",
|
|
4665
|
+
"--no-ignore-parent",
|
|
4666
|
+
"--no-ignore-vcs",
|
|
4667
|
+
"--no-line-number",
|
|
4668
|
+
"--no-messages",
|
|
4669
|
+
"--no-pcre2-unicode",
|
|
4670
|
+
"--no-pre",
|
|
4671
|
+
"--no-require-git",
|
|
4672
|
+
"--no-unicode",
|
|
4673
|
+
"--null",
|
|
4674
|
+
"--null-data",
|
|
4675
|
+
"--one-file-system",
|
|
4676
|
+
"--only-matching",
|
|
4677
|
+
"--passthru",
|
|
4678
|
+
"--pcre2",
|
|
4679
|
+
"--pcre2-version",
|
|
4680
|
+
"--pretty",
|
|
4681
|
+
"--print0",
|
|
4682
|
+
"--quiet",
|
|
4683
|
+
"--search-zip",
|
|
4684
|
+
"--smart-case",
|
|
4685
|
+
"--sort-files",
|
|
4686
|
+
"--stats",
|
|
4687
|
+
"--stop-on-nonmatch",
|
|
4688
|
+
"--text",
|
|
4689
|
+
"--trace",
|
|
4690
|
+
"--trim",
|
|
4691
|
+
"--type-list",
|
|
4692
|
+
"--unrestricted",
|
|
4693
|
+
"--version",
|
|
4694
|
+
"--vimgrep",
|
|
4695
|
+
"--with-filename",
|
|
4696
|
+
"--word-regexp",
|
|
4697
|
+
// The COMPLETE remainder of `rg --help`, added after /code-review round 5
|
|
4698
|
+
// found 40 documented switches in neither set. The table is now the whole
|
|
4699
|
+
// option list: `rg --help` yields 149 long options, 35 of them value-taking.
|
|
4700
|
+
"--ignore",
|
|
4701
|
+
"--ignore-dot",
|
|
4702
|
+
"--ignore-exclude",
|
|
4703
|
+
"--ignore-files",
|
|
4704
|
+
"--ignore-global",
|
|
4705
|
+
"--ignore-messages",
|
|
4706
|
+
"--ignore-parent",
|
|
4707
|
+
"--ignore-vcs",
|
|
4708
|
+
"--messages",
|
|
4709
|
+
"--no-auto-hybrid-regex",
|
|
4710
|
+
"--no-binary",
|
|
4711
|
+
"--no-block-buffered",
|
|
4712
|
+
"--no-byte-offset",
|
|
4713
|
+
"--no-crlf",
|
|
4714
|
+
"--no-fixed-strings",
|
|
4715
|
+
"--no-follow",
|
|
4716
|
+
"--no-glob-case-insensitive",
|
|
4717
|
+
"--no-heading",
|
|
4718
|
+
"--no-hidden",
|
|
4719
|
+
"--no-ignore-file-case-insensitive",
|
|
4720
|
+
"--no-include-zero",
|
|
4721
|
+
"--no-invert-match",
|
|
4722
|
+
"--no-json",
|
|
4723
|
+
"--no-line-buffered",
|
|
4724
|
+
"--no-max-columns-preview",
|
|
4725
|
+
"--no-mmap",
|
|
4726
|
+
"--no-multiline",
|
|
4727
|
+
"--no-multiline-dotall",
|
|
4728
|
+
"--no-one-file-system",
|
|
4729
|
+
"--no-pcre2",
|
|
4730
|
+
"--no-search-zip",
|
|
4731
|
+
"--no-sort-files",
|
|
4732
|
+
"--no-stats",
|
|
4733
|
+
"--no-text",
|
|
4734
|
+
"--no-trim",
|
|
4735
|
+
"--passthrough",
|
|
4736
|
+
"--pcre2-unicode",
|
|
4737
|
+
"--require-git",
|
|
4738
|
+
"--unicode"
|
|
4739
|
+
]),
|
|
4740
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
4741
|
+
// `--files` and `--type-list` list or enumerate without a pattern. Founder
|
|
4742
|
+
// decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
|
|
4743
|
+
// by leaving the directory in the judged list.
|
|
4744
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
|
|
4745
|
+
}
|
|
4746
|
+
};
|
|
4747
|
+
PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
|
|
4748
|
+
GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
|
|
4749
|
+
READER_VALUE_LETTERS = {
|
|
4750
|
+
awk: ["F", "v", "f"],
|
|
4751
|
+
gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
|
|
4752
|
+
sed: ["e", "f", "i", "l"],
|
|
4753
|
+
sort: ["C", "k", "o", "S", "t", "T"]
|
|
4754
|
+
};
|
|
4755
|
+
FILE_OPERAND_FLAGS = {
|
|
4756
|
+
grep: GREP_FILE_OPERANDS,
|
|
4757
|
+
egrep: GREP_FILE_OPERANDS,
|
|
4758
|
+
fgrep: GREP_FILE_OPERANDS,
|
|
4759
|
+
// rg and gawk are NOT installed on the measuring machine: these two rows are
|
|
4760
|
+
// from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
|
|
4761
|
+
// `-f/--file`) and are marked as such in the spec.
|
|
4762
|
+
// `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
|
|
4763
|
+
// a credential makes rg open it -- the same reason grep's `--include` is here.
|
|
4764
|
+
// Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
|
|
4765
|
+
// and printed its contents.
|
|
4766
|
+
rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
|
|
4767
|
+
sed: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4768
|
+
awk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4769
|
+
gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4770
|
+
sort: /* @__PURE__ */ new Set(["--files0-from"])
|
|
4771
|
+
};
|
|
4772
|
+
SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
4773
|
+
TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
|
|
4774
|
+
ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
|
|
4775
|
+
RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
|
|
4776
|
+
RSYNC_SKIP = [
|
|
4777
|
+
"e",
|
|
4778
|
+
"--rsh",
|
|
4779
|
+
"--exclude",
|
|
4780
|
+
"--exclude-from",
|
|
4781
|
+
"--include",
|
|
4782
|
+
"--include-from",
|
|
4783
|
+
"--files-from",
|
|
4784
|
+
"f",
|
|
4785
|
+
"--filter"
|
|
4786
|
+
];
|
|
4787
|
+
CP_VALUE_LETTERS = ["S", "t"];
|
|
4788
|
+
INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
|
|
4789
|
+
COPY_VERBS = {
|
|
4790
|
+
cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4791
|
+
mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4792
|
+
install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
|
|
4793
|
+
ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4794
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
|
|
4795
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
|
|
4796
|
+
tar: {
|
|
4797
|
+
source: "archive",
|
|
4798
|
+
archive: "tar",
|
|
4799
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
|
|
4800
|
+
valueLetters: TAR_VALUE_LETTERS
|
|
4801
|
+
},
|
|
4802
|
+
zip: {
|
|
4803
|
+
source: "archive",
|
|
4804
|
+
archive: "zip",
|
|
4805
|
+
skipFlags: ["x", "i", "--exclude", "--include"],
|
|
4806
|
+
valueLetters: ZIP_VALUE_LETTERS
|
|
4807
|
+
},
|
|
4808
|
+
ar: { source: "archive", archive: "ar" },
|
|
4809
|
+
// 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
|
|
4810
|
+
// ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
|
|
4811
|
+
// and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
|
|
4812
|
+
// drop the credential as an exclusion operand (/code-review round 8), and the
|
|
4813
|
+
// derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
|
|
4814
|
+
// what keeps the two tables honest about it.
|
|
4815
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
|
|
4816
|
+
gzip: { source: "all" },
|
|
4817
|
+
bzip2: { source: "all" },
|
|
4818
|
+
xz: { source: "all" },
|
|
4819
|
+
"docker cp": { source: "allButLast" },
|
|
4820
|
+
"kubectl cp": { source: "allButLast" },
|
|
4821
|
+
"gsutil cp": { source: "allButLast" },
|
|
4822
|
+
"gsutil rsync": { source: "allButLast" },
|
|
4823
|
+
"rclone copy": { source: "allButLast" },
|
|
4824
|
+
"rclone sync": { source: "allButLast" },
|
|
4825
|
+
"aws s3 cp": { source: "allButLast" },
|
|
4826
|
+
"aws s3 mv": { source: "allButLast" },
|
|
4827
|
+
"aws s3 sync": { source: "allButLast" },
|
|
4828
|
+
"gcloud storage cp": { source: "allButLast" },
|
|
4829
|
+
"az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
|
|
4830
|
+
};
|
|
4831
|
+
TAR_MODE_WORD = /^[a-zA-Z]+$/;
|
|
4832
|
+
COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
|
|
4071
4833
|
FS_OP_PRESCREEN_RE = new RegExp(
|
|
4072
4834
|
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
4073
4835
|
// reader right after `"` / `'`, and without these two characters the
|
|
4074
4836
|
// prescreen rejected every string-wrapped read before the parser ran.
|
|
4075
4837
|
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
4076
|
-
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4838
|
+
`(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4077
4839
|
);
|
|
4078
4840
|
HOME_CACHE_ALLOWLIST = [
|
|
4079
4841
|
".cache",
|
|
@@ -4363,8 +5125,17 @@ var init_dist = __esm({
|
|
|
4363
5125
|
deriveRedirOp("cat <<X\nX"),
|
|
4364
5126
|
deriveRedirOp("cat <<-X\nX")
|
|
4365
5127
|
]);
|
|
4366
|
-
|
|
4367
|
-
|
|
5128
|
+
FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
5129
|
+
COPY_RULE_OF = {
|
|
5130
|
+
"shield:project-jail:block-read-ssh": "shield:project-jail:review-copy-ssh",
|
|
5131
|
+
"shield:project-jail:block-read-aws": "shield:project-jail:review-copy-aws",
|
|
5132
|
+
"shield:project-jail:block-read-env": "shield:project-jail:review-copy-env",
|
|
5133
|
+
"shield:project-jail:review-read-credentials": "shield:project-jail:review-copy-credentials"
|
|
5134
|
+
};
|
|
5135
|
+
isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
5136
|
+
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
5137
|
+
NONE = { kind: "none" };
|
|
5138
|
+
UNKNOWN = { kind: "unknown" };
|
|
4368
5139
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
4369
5140
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
4370
5141
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -5409,7 +6180,7 @@ var init_dist = __esm({
|
|
|
5409
6180
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
5410
6181
|
];
|
|
5411
6182
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5412
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
6183
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
|
|
5413
6184
|
DEDUPE_PREVIEW_LEN = 120;
|
|
5414
6185
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
5415
6186
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -12979,6 +13750,28 @@ function codexSessionCost(model, tokens, request2) {
|
|
|
12979
13750
|
const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
|
|
12980
13751
|
return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
|
|
12981
13752
|
}
|
|
13753
|
+
function statAndFirstLine(file) {
|
|
13754
|
+
const CAP = 4 * 1024 * 1024;
|
|
13755
|
+
const CHUNK = 64 * 1024;
|
|
13756
|
+
const fd = fs21.openSync(file, "r");
|
|
13757
|
+
try {
|
|
13758
|
+
const stat = fs21.fstatSync(fd);
|
|
13759
|
+
const limit = Math.min(stat.size, CAP);
|
|
13760
|
+
const parts = [];
|
|
13761
|
+
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
13762
|
+
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
13763
|
+
const read2 = fs21.readSync(fd, buf, 0, buf.length, pos);
|
|
13764
|
+
if (read2 <= 0) break;
|
|
13765
|
+
const slice = buf.subarray(0, read2);
|
|
13766
|
+
const nl = slice.indexOf(10);
|
|
13767
|
+
parts.push(nl >= 0 ? slice.subarray(0, nl) : slice);
|
|
13768
|
+
if (nl >= 0) break;
|
|
13769
|
+
}
|
|
13770
|
+
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
13771
|
+
} finally {
|
|
13772
|
+
fs21.closeSync(fd);
|
|
13773
|
+
}
|
|
13774
|
+
}
|
|
12982
13775
|
function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
12983
13776
|
const files = [];
|
|
12984
13777
|
const walk = (dir) => {
|
|
@@ -12996,10 +13789,10 @@ function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
|
12996
13789
|
const sessions = /* @__PURE__ */ new Map();
|
|
12997
13790
|
for (const file of files.sort()) {
|
|
12998
13791
|
try {
|
|
12999
|
-
const stat =
|
|
13792
|
+
const { stat, first: head } = statAndFirstLine(file);
|
|
13000
13793
|
let id = "";
|
|
13001
13794
|
try {
|
|
13002
|
-
const first = JSON.parse(
|
|
13795
|
+
const first = JSON.parse(head);
|
|
13003
13796
|
if (first?.type === "session_meta" && typeof first.payload?.id === "string")
|
|
13004
13797
|
id = first.payload.id;
|
|
13005
13798
|
} catch {
|
|
@@ -59452,7 +60245,11 @@ var BUILTIN_JAIL = [
|
|
|
59452
60245
|
"~/.ssh \u2014 SSH private keys",
|
|
59453
60246
|
"~/.aws \u2014 AWS credentials",
|
|
59454
60247
|
".env files",
|
|
59455
|
-
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
|
|
60248
|
+
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud",
|
|
60249
|
+
// Stage 4 (2026-09-11): reads are blocked; a COPY out of the jail (cp, tar,
|
|
60250
|
+
// scp, rsync, aws s3 cp, ...) is reviewed, because a backup and a theft are
|
|
60251
|
+
// the same command shape.
|
|
60252
|
+
"copies out of the jail (cp, tar, scp, rsync, cloud upload) \u2014 review"
|
|
59456
60253
|
];
|
|
59457
60254
|
function registerJailCommand(program2) {
|
|
59458
60255
|
const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
|