@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.js
CHANGED
|
@@ -894,6 +894,19 @@ function parseShared(command) {
|
|
|
894
894
|
astCache.set(command, parsed);
|
|
895
895
|
return parsed;
|
|
896
896
|
}
|
|
897
|
+
function byteOffsetToCharIndex(command) {
|
|
898
|
+
if (!/[^\u0000-\u007F]/.test(command)) return null;
|
|
899
|
+
const map = /* @__PURE__ */ new Map();
|
|
900
|
+
let byte = 0;
|
|
901
|
+
for (let i = 0; i < command.length; ) {
|
|
902
|
+
map.set(byte, i);
|
|
903
|
+
const cp = command.codePointAt(i);
|
|
904
|
+
byte += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
|
|
905
|
+
i += cp > 65535 ? 2 : 1;
|
|
906
|
+
}
|
|
907
|
+
map.set(byte, command.length);
|
|
908
|
+
return (b) => map.get(b) ?? -1;
|
|
909
|
+
}
|
|
897
910
|
function cachedNormalize(command, compute) {
|
|
898
911
|
const hit = normalizeCache.get(command);
|
|
899
912
|
if (hit !== void 0) {
|
|
@@ -923,6 +936,8 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
923
936
|
const f = parseShared(command);
|
|
924
937
|
if (f === PARSE_FAIL) return { posix: command, separator: command };
|
|
925
938
|
try {
|
|
939
|
+
const toCharIndex = byteOffsetToCharIndex(command);
|
|
940
|
+
const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
|
|
926
941
|
const strips = [];
|
|
927
942
|
const rewrites = [];
|
|
928
943
|
const quoteOnlyRewrites = [];
|
|
@@ -943,8 +958,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
943
958
|
const quotedNode = nextParts[0];
|
|
944
959
|
const nt = syntax.NodeType(quotedNode);
|
|
945
960
|
const markStrip = () => {
|
|
946
|
-
const s = next.Pos().Offset();
|
|
947
|
-
const e = next.End().Offset();
|
|
961
|
+
const s = at(next.Pos().Offset());
|
|
962
|
+
const e = at(next.End().Offset());
|
|
963
|
+
if (s < 0 || e < 0) return;
|
|
948
964
|
strips.push([s, e]);
|
|
949
965
|
msgSpans.add(`${s}:${e}`);
|
|
950
966
|
};
|
|
@@ -961,8 +977,9 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
961
977
|
}
|
|
962
978
|
}
|
|
963
979
|
for (const arg of args) {
|
|
964
|
-
const s = arg.Pos().Offset();
|
|
965
|
-
const e = arg.End().Offset();
|
|
980
|
+
const s = at(arg.Pos().Offset());
|
|
981
|
+
const e = at(arg.End().Offset());
|
|
982
|
+
if (s < 0 || e < 0) continue;
|
|
966
983
|
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
967
984
|
const resolved = resolveWordLiteral(arg);
|
|
968
985
|
if (resolved === null) continue;
|
|
@@ -1291,20 +1308,32 @@ function isProtectedHomePath(rawPath) {
|
|
|
1291
1308
|
}
|
|
1292
1309
|
return true;
|
|
1293
1310
|
}
|
|
1294
|
-
function
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
const name = (words[0] ?? "").toLowerCase();
|
|
1299
|
-
const flags = [];
|
|
1300
|
-
const paths = [];
|
|
1301
|
-
for (let i = 1; i < words.length; i++) {
|
|
1311
|
+
function positionedArgs(words, from = 1, to = words.length) {
|
|
1312
|
+
const out = [];
|
|
1313
|
+
let afterFlag = null;
|
|
1314
|
+
for (let i = from; i < to; i++) {
|
|
1302
1315
|
const v = words[i];
|
|
1303
|
-
if (v === null)
|
|
1304
|
-
|
|
1305
|
-
|
|
1316
|
+
if (v === null) {
|
|
1317
|
+
afterFlag = null;
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (v.startsWith("-")) {
|
|
1321
|
+
afterFlag = v;
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
out.push({ value: v, index: out.length, argv: i, afterFlag });
|
|
1325
|
+
afterFlag = null;
|
|
1306
1326
|
}
|
|
1307
|
-
return
|
|
1327
|
+
return out;
|
|
1328
|
+
}
|
|
1329
|
+
function extractLiteralArgs(callExpr) {
|
|
1330
|
+
const rawArgs = callExpr.Args || [];
|
|
1331
|
+
if (rawArgs.length === 0) return { name: "", flags: [], paths: [], words: [], args: [] };
|
|
1332
|
+
const words = rawArgs.map((a) => resolveWordLiteral(a));
|
|
1333
|
+
const name = baseWord(words[0]);
|
|
1334
|
+
const flags = words.slice(1).filter((w) => w !== null && w.startsWith("-"));
|
|
1335
|
+
const args = positionedArgs(words);
|
|
1336
|
+
return { name, flags, paths: args.map((a) => a.value), words, args };
|
|
1308
1337
|
}
|
|
1309
1338
|
function resolveWordLiteral(w) {
|
|
1310
1339
|
const parts = w?.Parts || [];
|
|
@@ -1576,7 +1605,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1576
1605
|
return result?.verdict !== "block";
|
|
1577
1606
|
}
|
|
1578
1607
|
if (nodeType !== "CallExpr") return true;
|
|
1579
|
-
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
1608
|
+
const { name, flags, paths, words, args } = extractLiteralArgs(n);
|
|
1580
1609
|
if (!name) return true;
|
|
1581
1610
|
if (name === "rm") {
|
|
1582
1611
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -1616,13 +1645,16 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1616
1645
|
return true;
|
|
1617
1646
|
}
|
|
1618
1647
|
}
|
|
1619
|
-
const readPaths = FS_READ_TOOLS.has(name) ?
|
|
1648
|
+
const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
|
|
1620
1649
|
if (readPaths) {
|
|
1621
1650
|
for (const p of readPaths) {
|
|
1622
1651
|
result = stricter(result, matchSensitivePath2(p));
|
|
1623
1652
|
if (result?.verdict === "block") return false;
|
|
1624
1653
|
}
|
|
1625
1654
|
}
|
|
1655
|
+
for (const p of copySourcePaths(words)) {
|
|
1656
|
+
result = stricter(result, copyVerdictOf(matchSensitivePath2(p)));
|
|
1657
|
+
}
|
|
1626
1658
|
return true;
|
|
1627
1659
|
});
|
|
1628
1660
|
return result;
|
|
@@ -1635,6 +1667,184 @@ function stricter(a, b) {
|
|
|
1635
1667
|
if (!b) return a;
|
|
1636
1668
|
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
1637
1669
|
}
|
|
1670
|
+
function flagInfo(w) {
|
|
1671
|
+
if (w.startsWith("--")) {
|
|
1672
|
+
const eq = w.indexOf("=");
|
|
1673
|
+
return eq < 0 ? { letter: null, long: w, attached: null } : { letter: null, long: w.slice(0, eq), attached: w.slice(eq + 1) };
|
|
1674
|
+
}
|
|
1675
|
+
const m = /^-([a-zA-Z]+)(.*)$/.exec(w);
|
|
1676
|
+
if (!m) return { letter: null, long: null, attached: null };
|
|
1677
|
+
return { letter: m[1][m[1].length - 1], long: null, attached: m[2] === "" ? null : m[2] };
|
|
1678
|
+
}
|
|
1679
|
+
function flagIs(w, names) {
|
|
1680
|
+
if (w === null) return false;
|
|
1681
|
+
const f = flagInfo(w);
|
|
1682
|
+
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
1683
|
+
}
|
|
1684
|
+
function operandOf(a, names, valueLetters) {
|
|
1685
|
+
if (!names || a.afterFlag === null) return false;
|
|
1686
|
+
const w = a.afterFlag;
|
|
1687
|
+
if (!w.startsWith("--") && valueLetters) {
|
|
1688
|
+
const letters = w.slice(1);
|
|
1689
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1690
|
+
if (!valueLetters.includes(letters[i])) continue;
|
|
1691
|
+
return i === letters.length - 1 && names.includes(letters[i]);
|
|
1692
|
+
}
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
if (w.startsWith("--") && !w.includes("=")) {
|
|
1696
|
+
const longs = names.filter((n) => n.startsWith("--"));
|
|
1697
|
+
if (longs.some((n) => n === w || w.length >= 3 && n.startsWith(w))) return true;
|
|
1698
|
+
}
|
|
1699
|
+
return flagIs(w, names) && flagInfo(w).attached === null;
|
|
1700
|
+
}
|
|
1701
|
+
function resolveCopyShape(words, h) {
|
|
1702
|
+
const verb = baseWord(words[h]);
|
|
1703
|
+
if (!verb) return null;
|
|
1704
|
+
const direct = COPY_VERBS[verb];
|
|
1705
|
+
if (direct) return { shape: direct, last: h };
|
|
1706
|
+
const slots = positionedArgs(words, h + 1);
|
|
1707
|
+
for (let i = 0; i < slots.length; i++) {
|
|
1708
|
+
for (let n = 3; n >= 1; n--) {
|
|
1709
|
+
const part = slots.slice(i, i + n);
|
|
1710
|
+
if (part.length < n) continue;
|
|
1711
|
+
const key = [verb, ...part.map((a) => a.value.toLowerCase())].join(" ");
|
|
1712
|
+
const shape = COPY_VERBS[key];
|
|
1713
|
+
if (shape) return { shape, last: part[n - 1].argv };
|
|
1714
|
+
}
|
|
1715
|
+
if (slots[i].afterFlag === null) return null;
|
|
1716
|
+
}
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
function findStartPoints(words, h) {
|
|
1720
|
+
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1721
|
+
if (k < 0) return { k, starts: [] };
|
|
1722
|
+
const firstPredicate = words.findIndex(
|
|
1723
|
+
(w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
1724
|
+
);
|
|
1725
|
+
const end = firstPredicate > h ? firstPredicate : k;
|
|
1726
|
+
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
1727
|
+
}
|
|
1728
|
+
function copySourcePaths(words) {
|
|
1729
|
+
const h = unwrapCommandHead(words);
|
|
1730
|
+
const fi = words.findIndex((w, i) => i <= h && baseWord(w) === "find");
|
|
1731
|
+
if (fi >= 0) {
|
|
1732
|
+
const { k, starts } = findStartPoints(words, fi);
|
|
1733
|
+
if (k < 0) return [];
|
|
1734
|
+
const action = unwrapCommandHead(words.slice(k + 1));
|
|
1735
|
+
return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
|
|
1736
|
+
}
|
|
1737
|
+
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
1738
|
+
const r = resolveCopyShape(words, h);
|
|
1739
|
+
if (!r) return [];
|
|
1740
|
+
const { shape, last } = r;
|
|
1741
|
+
const args = positionedArgs(words, last + 1);
|
|
1742
|
+
const tail = words.slice(last + 1);
|
|
1743
|
+
const copyOptionsEnd = tail.findIndex((w) => w === "--");
|
|
1744
|
+
const copyPastOptions = (a) => copyOptionsEnd >= 0 && a.argv > last + 1 + copyOptionsEnd;
|
|
1745
|
+
const skipped = (a) => !copyPastOptions(a) && operandOf(a, shape.skipFlags, shape.valueLetters);
|
|
1746
|
+
const firstValueLetter = (w) => {
|
|
1747
|
+
const letters = w.slice(1);
|
|
1748
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1749
|
+
if ((shape.valueLetters ?? []).includes(letters[i]))
|
|
1750
|
+
return { letter: letters[i], last: i === letters.length - 1 };
|
|
1751
|
+
}
|
|
1752
|
+
return null;
|
|
1753
|
+
};
|
|
1754
|
+
const isTargetDirFlag = (w) => {
|
|
1755
|
+
if (w.startsWith("--")) {
|
|
1756
|
+
const name = w.includes("=") ? w.slice(0, w.indexOf("=")) : w;
|
|
1757
|
+
return name.length >= 3 && "--target-directory".startsWith(name);
|
|
1758
|
+
}
|
|
1759
|
+
return firstValueLetter(w)?.letter === "t";
|
|
1760
|
+
};
|
|
1761
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && isTargetDirFlag(w));
|
|
1762
|
+
const targetTakesNextWord = (w) => {
|
|
1763
|
+
if (w.startsWith("--"))
|
|
1764
|
+
return !w.includes("=") && w.length >= 3 && "--target-directory".startsWith(w);
|
|
1765
|
+
const f = firstValueLetter(w);
|
|
1766
|
+
return f !== null && f.letter === "t" && f.last;
|
|
1767
|
+
};
|
|
1768
|
+
const targetOperand = (a) => targetDir && a.afterFlag !== null && !copyPastOptions(a) && targetTakesNextWord(a.afterFlag);
|
|
1769
|
+
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
1770
|
+
const dynamicDest = lastOperand === null;
|
|
1771
|
+
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
1772
|
+
let src;
|
|
1773
|
+
switch (shape.source) {
|
|
1774
|
+
case "all":
|
|
1775
|
+
src = args;
|
|
1776
|
+
break;
|
|
1777
|
+
case "first":
|
|
1778
|
+
src = targetDir ? args : args.slice(0, 1);
|
|
1779
|
+
break;
|
|
1780
|
+
case "flagOperand": {
|
|
1781
|
+
const namesSource = (f) => {
|
|
1782
|
+
const names = shape.sourceFlags ?? [];
|
|
1783
|
+
if (f.long !== null)
|
|
1784
|
+
return names.some(
|
|
1785
|
+
(n) => n.startsWith("--") && (n === f.long || f.long.length >= 3 && n.startsWith(f.long))
|
|
1786
|
+
);
|
|
1787
|
+
return f.letter !== null && names.includes(f.letter);
|
|
1788
|
+
};
|
|
1789
|
+
const inline = tail.filter((w) => w !== null && w.startsWith("-")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && namesSource(f)).map((f) => f.attached);
|
|
1790
|
+
return [
|
|
1791
|
+
...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
|
|
1792
|
+
...inline
|
|
1793
|
+
];
|
|
1794
|
+
}
|
|
1795
|
+
case "archive":
|
|
1796
|
+
src = archiveInputs(shape.archive, args, tail);
|
|
1797
|
+
break;
|
|
1798
|
+
case "allButLast":
|
|
1799
|
+
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
const sources = src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
1803
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand)) {
|
|
1804
|
+
const jailedSources = sources.filter((p) => matchSensitivePath2(p));
|
|
1805
|
+
const dirOf = (p) => /[\\/]/.test(p) ? p.replace(/[\\/][^\\/]*$/, "") : "";
|
|
1806
|
+
if (jailedSources.length === 0) return [];
|
|
1807
|
+
if (jailedSources.every((p) => dirOf(p) === dirOf(lastOperand))) return [];
|
|
1808
|
+
}
|
|
1809
|
+
return sources;
|
|
1810
|
+
}
|
|
1811
|
+
function archiveInputs(kind, args, tail) {
|
|
1812
|
+
const first = args[0];
|
|
1813
|
+
const bareKey = first && first.afterFlag === null && TAR_MODE_WORD.test(first.value);
|
|
1814
|
+
if (kind === "tar") {
|
|
1815
|
+
const flagsText = tail.filter((w) => w !== null && w.startsWith("-")).join(" ");
|
|
1816
|
+
const mode = (bareKey ? first.value : "") + flagsText;
|
|
1817
|
+
const extracting = /x|t/.test(bareKey ? first.value.replace(/f/g, "") : "") || /(^|\s)-[a-zA-Z]*[xt]|--extract|--list|--get/.test(flagsText);
|
|
1818
|
+
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
1819
|
+
if (extracting && !writing) return [];
|
|
1820
|
+
void mode;
|
|
1821
|
+
if (!bareKey) return args;
|
|
1822
|
+
let i = 1;
|
|
1823
|
+
const fromDirs = [];
|
|
1824
|
+
for (const ch of first.value) {
|
|
1825
|
+
if (!TAR_VALUE_LETTERS.includes(ch)) continue;
|
|
1826
|
+
const operand = args[i];
|
|
1827
|
+
if (!operand || operand.afterFlag !== null) break;
|
|
1828
|
+
i += 1;
|
|
1829
|
+
if (ch === "C") fromDirs.push(operand);
|
|
1830
|
+
}
|
|
1831
|
+
return [...fromDirs, ...args.slice(i)];
|
|
1832
|
+
}
|
|
1833
|
+
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
1834
|
+
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
1835
|
+
return args.slice(2);
|
|
1836
|
+
}
|
|
1837
|
+
function copyVerdictOf(hit) {
|
|
1838
|
+
if (!hit) return null;
|
|
1839
|
+
const ruleName = COPY_RULE_OF[hit.ruleName];
|
|
1840
|
+
if (!ruleName) return null;
|
|
1841
|
+
return {
|
|
1842
|
+
ruleName,
|
|
1843
|
+
verdict: "review",
|
|
1844
|
+
reason: `Copying ${hit.path} moves a credential out of its jail (project-jail shield)`,
|
|
1845
|
+
path: hit.path
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1638
1848
|
function matchSensitivePath2(p) {
|
|
1639
1849
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1640
1850
|
if (sp.match(p))
|
|
@@ -1642,16 +1852,156 @@ function matchSensitivePath2(p) {
|
|
|
1642
1852
|
}
|
|
1643
1853
|
return null;
|
|
1644
1854
|
}
|
|
1855
|
+
function baseWord(w) {
|
|
1856
|
+
return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1857
|
+
}
|
|
1858
|
+
function namesFlag(name, candidates2, known) {
|
|
1859
|
+
if (candidates2.has(name)) return true;
|
|
1860
|
+
if (!name.startsWith("--") || name.length < 3) return false;
|
|
1861
|
+
if (known?.has(name)) return false;
|
|
1862
|
+
for (const c of candidates2) if (c.startsWith(name)) return true;
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
function knownLongFlags(verb) {
|
|
1866
|
+
const out = /* @__PURE__ */ new Set();
|
|
1867
|
+
const shape = PATTERN_VERBS[verb];
|
|
1868
|
+
if (shape) {
|
|
1869
|
+
for (const set of [shape.takesValue, shape.noValue, shape.patternFlags, shape.noPatternFlags])
|
|
1870
|
+
for (const f of set) if (f.startsWith("--")) out.add(f);
|
|
1871
|
+
}
|
|
1872
|
+
for (const f of FILE_OPERAND_FLAGS[verb] ?? []) if (f.startsWith("--")) out.add(f);
|
|
1873
|
+
return out;
|
|
1874
|
+
}
|
|
1875
|
+
function flagNamesOf(token, shape) {
|
|
1876
|
+
if (token.startsWith("--")) {
|
|
1877
|
+
const eq = token.indexOf("=");
|
|
1878
|
+
return [eq > 0 ? token.slice(0, eq) : token];
|
|
1879
|
+
}
|
|
1880
|
+
const out = [];
|
|
1881
|
+
for (const c of token.slice(1)) {
|
|
1882
|
+
const name = `-${c}`;
|
|
1883
|
+
out.push(name);
|
|
1884
|
+
if (shape?.takesValue.has(name)) break;
|
|
1885
|
+
}
|
|
1886
|
+
return out;
|
|
1887
|
+
}
|
|
1888
|
+
function flagEffect(token, shape, known) {
|
|
1889
|
+
if (token === "--") return NONE;
|
|
1890
|
+
if (/^-+$/.test(token)) return UNKNOWN;
|
|
1891
|
+
if (/^-\d+$/.test(token)) return NONE;
|
|
1892
|
+
if (token.startsWith("--")) {
|
|
1893
|
+
if (token.includes("=")) return NONE;
|
|
1894
|
+
const takes = namesFlag(token, shape.takesValue, known);
|
|
1895
|
+
const none = namesFlag(token, shape.noValue, known);
|
|
1896
|
+
if (takes && none) return UNKNOWN;
|
|
1897
|
+
if (takes) return { kind: "takes", flag: token };
|
|
1898
|
+
if (none) return NONE;
|
|
1899
|
+
return UNKNOWN;
|
|
1900
|
+
}
|
|
1901
|
+
const letters = token.slice(1);
|
|
1902
|
+
if (!letters) return NONE;
|
|
1903
|
+
for (let i = 0; i < letters.length; i++) {
|
|
1904
|
+
const name = `-${letters[i]}`;
|
|
1905
|
+
if (shape.takesValue.has(name)) {
|
|
1906
|
+
return i === letters.length - 1 ? { kind: "takes", flag: name } : NONE;
|
|
1907
|
+
}
|
|
1908
|
+
if (!shape.noValue.has(name)) return UNKNOWN;
|
|
1909
|
+
}
|
|
1910
|
+
return NONE;
|
|
1911
|
+
}
|
|
1912
|
+
function readTargets(verb, args, flags, words = [], from = 1) {
|
|
1913
|
+
const shape = PATTERN_VERBS[verb];
|
|
1914
|
+
if (!shape) return args.map((a) => a.value);
|
|
1915
|
+
const known = knownLongFlags(verb);
|
|
1916
|
+
const names = flags.flatMap((f) => flagNamesOf(f, shape));
|
|
1917
|
+
const patternElsewhere = names.some(
|
|
1918
|
+
(n) => namesFlag(n, shape.patternFlags, known) || namesFlag(n, shape.noPatternFlags, known)
|
|
1919
|
+
);
|
|
1920
|
+
const excused = /* @__PURE__ */ new Set();
|
|
1921
|
+
const fileFlags = FILE_OPERAND_FLAGS[verb];
|
|
1922
|
+
const optionsEnd = words.findIndex((w, i) => i >= from && w === "--");
|
|
1923
|
+
const pastOptions = (a) => optionsEnd >= 0 && a.argv > optionsEnd;
|
|
1924
|
+
for (const a of args) {
|
|
1925
|
+
if (a.afterFlag === null || pastOptions(a)) continue;
|
|
1926
|
+
const e = flagEffect(a.afterFlag, shape, known);
|
|
1927
|
+
if (e.kind !== "takes") continue;
|
|
1928
|
+
if (fileFlags && namesFlag(e.flag, fileFlags, known)) continue;
|
|
1929
|
+
excused.add(a);
|
|
1930
|
+
}
|
|
1931
|
+
const patternArgv = (() => {
|
|
1932
|
+
for (let i = from; i < words.length; i++) {
|
|
1933
|
+
const w = words[i];
|
|
1934
|
+
if (w === null) return -1;
|
|
1935
|
+
if (w === "--") return i + 1;
|
|
1936
|
+
if (w.startsWith("-")) {
|
|
1937
|
+
const e = flagEffect(w, shape, known);
|
|
1938
|
+
if (e.kind === "takes") i += 1;
|
|
1939
|
+
else if (e.kind === "unknown") return -1;
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
return i;
|
|
1943
|
+
}
|
|
1944
|
+
return -1;
|
|
1945
|
+
})();
|
|
1946
|
+
if (!patternElsewhere && patternArgv >= 0) {
|
|
1947
|
+
const a = args.find((x) => x.argv === patternArgv);
|
|
1948
|
+
if (a) excused.add(a);
|
|
1949
|
+
}
|
|
1950
|
+
return args.filter((a) => !excused.has(a)).map((a) => a.value);
|
|
1951
|
+
}
|
|
1952
|
+
function flagOperandFiles(verb, words, from) {
|
|
1953
|
+
const flags = FILE_OPERAND_FLAGS[verb];
|
|
1954
|
+
if (!flags) return [];
|
|
1955
|
+
const known = knownLongFlags(verb);
|
|
1956
|
+
const out = [];
|
|
1957
|
+
for (let i = from; i < words.length; i++) {
|
|
1958
|
+
const w = words[i];
|
|
1959
|
+
if (w === null || !w.startsWith("-") || w === "--") continue;
|
|
1960
|
+
if (w.startsWith("--")) {
|
|
1961
|
+
const eq = w.indexOf("=");
|
|
1962
|
+
if (eq <= 0) continue;
|
|
1963
|
+
const name = w.slice(0, eq);
|
|
1964
|
+
const value = w.slice(eq + 1);
|
|
1965
|
+
if (!value) continue;
|
|
1966
|
+
if (namesFlag(name, flags, known)) {
|
|
1967
|
+
out.push(value);
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
const shape2 = PATTERN_VERBS[verb];
|
|
1971
|
+
if (!shape2) continue;
|
|
1972
|
+
const recognised = namesFlag(name, shape2.takesValue, known) || namesFlag(name, shape2.noValue, known) || namesFlag(name, shape2.patternFlags, known) || namesFlag(name, shape2.noPatternFlags, known);
|
|
1973
|
+
if (!recognised) out.push(value);
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
const shape = PATTERN_VERBS[verb];
|
|
1977
|
+
const letters = w.slice(1);
|
|
1978
|
+
for (let j = 0; j < letters.length; j++) {
|
|
1979
|
+
const name = `-${letters[j]}`;
|
|
1980
|
+
const isFileFlag = flags.has(name);
|
|
1981
|
+
const argTaking = isFileFlag || (shape?.takesValue.has(name) ?? false) || (READER_VALUE_LETTERS[verb] ?? []).includes(letters[j]);
|
|
1982
|
+
if (!argTaking) continue;
|
|
1983
|
+
const attached = letters.slice(j + 1);
|
|
1984
|
+
if (isFileFlag && attached) out.push(attached);
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
return out;
|
|
1989
|
+
}
|
|
1645
1990
|
function wrappedReadPaths(words, name) {
|
|
1646
1991
|
if (name === "find") {
|
|
1647
|
-
const k = words
|
|
1648
|
-
|
|
1649
|
-
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
1650
|
-
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
1992
|
+
const { k, starts } = findStartPoints(words, 0);
|
|
1993
|
+
return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
|
|
1651
1994
|
}
|
|
1652
1995
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
1653
1996
|
const h = unwrapCommandHead(words);
|
|
1654
|
-
|
|
1997
|
+
if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
|
|
1998
|
+
const head = baseWord(words[h]);
|
|
1999
|
+
const rest = words.slice(h + 1);
|
|
2000
|
+
const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
|
|
2001
|
+
return [
|
|
2002
|
+
...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
|
|
2003
|
+
...flagOperandFiles(head, words, h + 1)
|
|
2004
|
+
];
|
|
1655
2005
|
}
|
|
1656
2006
|
function literalShellPayload(words, name) {
|
|
1657
2007
|
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
@@ -2319,6 +2669,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2319
2669
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
2320
2670
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
2321
2671
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
2672
|
+
let pendingAstReview;
|
|
2322
2673
|
if (bashCommand !== null) {
|
|
2323
2674
|
const pipeVerdict = pipeChainVerdict(
|
|
2324
2675
|
bashCommand,
|
|
@@ -2330,7 +2681,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2330
2681
|
if (fsVerdict) {
|
|
2331
2682
|
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
2332
2683
|
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
2333
|
-
|
|
2684
|
+
const astVerdict = {
|
|
2334
2685
|
decision: fsVerdict.verdict,
|
|
2335
2686
|
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
2336
2687
|
reason: fsVerdict.reason,
|
|
@@ -2338,6 +2689,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2338
2689
|
ruleName: fsVerdict.ruleName,
|
|
2339
2690
|
ruleDescription: fsVerdict.reason
|
|
2340
2691
|
};
|
|
2692
|
+
if (fsVerdict.verdict === "block") return astVerdict;
|
|
2693
|
+
pendingAstReview = astVerdict;
|
|
2341
2694
|
}
|
|
2342
2695
|
const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
|
|
2343
2696
|
const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
|
|
@@ -2380,7 +2733,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2380
2733
|
const matchedRule = resolvePinned(matches);
|
|
2381
2734
|
if (matchedRule) {
|
|
2382
2735
|
if (matchedRule.verdict === "allow")
|
|
2383
|
-
return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2736
|
+
return pendingAstReview ?? { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2384
2737
|
return {
|
|
2385
2738
|
decision: matchedRule.verdict,
|
|
2386
2739
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
@@ -2407,6 +2760,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2407
2760
|
allTokens = analyzed.allTokens;
|
|
2408
2761
|
pathTokens = analyzed.paths;
|
|
2409
2762
|
const candidates2 = [];
|
|
2763
|
+
if (pendingAstReview) candidates2.push(pendingAstReview);
|
|
2410
2764
|
const evalVerdict = detectDangerousShellExec(shellCommand);
|
|
2411
2765
|
if (evalVerdict === "block") {
|
|
2412
2766
|
return {
|
|
@@ -2703,6 +3057,12 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2703
3057
|
"read-ssh",
|
|
2704
3058
|
"read-gcp",
|
|
2705
3059
|
"read-cred",
|
|
3060
|
+
// Stage 4 (2026-09-11): a copy of a credential file scores like a READ of it --
|
|
3061
|
+
// read-ssh/aws/cred are critical, so copy-ssh/aws/cred are; read-env is high
|
|
3062
|
+
// (below), so copy-env joins the high list, not this one (/code-review).
|
|
3063
|
+
"copy-ssh",
|
|
3064
|
+
"copy-aws",
|
|
3065
|
+
"copy-cred",
|
|
2706
3066
|
"delete-repo",
|
|
2707
3067
|
"helm-uninstall",
|
|
2708
3068
|
"drop-table",
|
|
@@ -2715,6 +3075,7 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2715
3075
|
];
|
|
2716
3076
|
if (criticalPatterns.some((p) => n.includes(p))) return "critical";
|
|
2717
3077
|
const highPatterns = [
|
|
3078
|
+
"copy-env",
|
|
2718
3079
|
"force-push",
|
|
2719
3080
|
"force_push",
|
|
2720
3081
|
"git-destructive",
|
|
@@ -2732,6 +3093,11 @@ function narrativeRuleLabel(name) {
|
|
|
2732
3093
|
const map = {
|
|
2733
3094
|
"read-aws": "AWS credentials read",
|
|
2734
3095
|
"read-ssh": "SSH private key read",
|
|
3096
|
+
// Stage 4 copy twins, so `scan --narrative` prints a label, not a raw slug.
|
|
3097
|
+
"copy-ssh": "SSH private key copied out",
|
|
3098
|
+
"copy-aws": "AWS credentials copied out",
|
|
3099
|
+
"copy-env": ".env file copied out",
|
|
3100
|
+
"copy-cred": "credential file copied out",
|
|
2735
3101
|
"read-gcp": "GCP credentials read",
|
|
2736
3102
|
"read-cred": "credential file read",
|
|
2737
3103
|
"delete-repo": "GitHub repository deletion",
|
|
@@ -3321,7 +3687,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3321
3687
|
}
|
|
3322
3688
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3323
3689
|
}
|
|
3324
|
-
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, 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;
|
|
3690
|
+
var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, 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;
|
|
3325
3691
|
var init_dist = __esm({
|
|
3326
3692
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3327
3693
|
"use strict";
|
|
@@ -4064,12 +4430,408 @@ var init_dist = __esm({
|
|
|
4064
4430
|
"nl",
|
|
4065
4431
|
"dd"
|
|
4066
4432
|
]);
|
|
4433
|
+
GREP_SHAPE = {
|
|
4434
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
4435
|
+
"-A",
|
|
4436
|
+
"-B",
|
|
4437
|
+
"-C",
|
|
4438
|
+
"-D",
|
|
4439
|
+
"-d",
|
|
4440
|
+
"-e",
|
|
4441
|
+
"-f",
|
|
4442
|
+
"-m",
|
|
4443
|
+
"--after-context",
|
|
4444
|
+
"--before-context",
|
|
4445
|
+
"--binary-files",
|
|
4446
|
+
"--context",
|
|
4447
|
+
"--devices",
|
|
4448
|
+
"--directories",
|
|
4449
|
+
"--exclude",
|
|
4450
|
+
"--exclude-dir",
|
|
4451
|
+
"--exclude-from",
|
|
4452
|
+
"--file",
|
|
4453
|
+
"--include",
|
|
4454
|
+
"--label",
|
|
4455
|
+
"--max-count",
|
|
4456
|
+
"--regexp"
|
|
4457
|
+
]),
|
|
4458
|
+
noValue: /* @__PURE__ */ new Set([
|
|
4459
|
+
"-E",
|
|
4460
|
+
"-F",
|
|
4461
|
+
"-G",
|
|
4462
|
+
"-P",
|
|
4463
|
+
"-i",
|
|
4464
|
+
"-y",
|
|
4465
|
+
"-v",
|
|
4466
|
+
"-V",
|
|
4467
|
+
"-w",
|
|
4468
|
+
"-x",
|
|
4469
|
+
"-c",
|
|
4470
|
+
"-l",
|
|
4471
|
+
"-L",
|
|
4472
|
+
"-o",
|
|
4473
|
+
"-q",
|
|
4474
|
+
"-s",
|
|
4475
|
+
"-b",
|
|
4476
|
+
"-H",
|
|
4477
|
+
"-h",
|
|
4478
|
+
"-n",
|
|
4479
|
+
"-T",
|
|
4480
|
+
"-Z",
|
|
4481
|
+
"-z",
|
|
4482
|
+
"-R",
|
|
4483
|
+
"-r",
|
|
4484
|
+
"-U",
|
|
4485
|
+
"-u",
|
|
4486
|
+
"-I",
|
|
4487
|
+
"-a",
|
|
4488
|
+
"--basic-regexp",
|
|
4489
|
+
"--binary",
|
|
4490
|
+
"--byte-offset",
|
|
4491
|
+
"--color",
|
|
4492
|
+
"--colour",
|
|
4493
|
+
"--count",
|
|
4494
|
+
"--dereference-recursive",
|
|
4495
|
+
"--extended-regexp",
|
|
4496
|
+
"--files-with-matches",
|
|
4497
|
+
"--files-without-match",
|
|
4498
|
+
"--fixed-strings",
|
|
4499
|
+
"--help",
|
|
4500
|
+
"--ignore-case",
|
|
4501
|
+
"--initial-tab",
|
|
4502
|
+
"--invert-match",
|
|
4503
|
+
"--line-buffered",
|
|
4504
|
+
"--line-number",
|
|
4505
|
+
"--line-regexp",
|
|
4506
|
+
"--no-filename",
|
|
4507
|
+
"--no-group-separator",
|
|
4508
|
+
"--no-ignore-case",
|
|
4509
|
+
"--no-messages",
|
|
4510
|
+
"--null",
|
|
4511
|
+
"--null-data",
|
|
4512
|
+
"--only-matching",
|
|
4513
|
+
"--perl-regexp",
|
|
4514
|
+
"--quiet",
|
|
4515
|
+
"--recursive",
|
|
4516
|
+
"--silent",
|
|
4517
|
+
"--text",
|
|
4518
|
+
"--version",
|
|
4519
|
+
"--with-filename",
|
|
4520
|
+
"--word-regexp"
|
|
4521
|
+
]),
|
|
4522
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
4523
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
|
|
4524
|
+
};
|
|
4525
|
+
PATTERN_VERBS = {
|
|
4526
|
+
grep: GREP_SHAPE,
|
|
4527
|
+
// /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
|
|
4528
|
+
// `grep -E` and `grep -F`. Read in full, not assumed.
|
|
4529
|
+
egrep: GREP_SHAPE,
|
|
4530
|
+
fgrep: GREP_SHAPE,
|
|
4531
|
+
// ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
|
|
4532
|
+
// was not installed on the measuring machine; it is, and that stale claim is why
|
|
4533
|
+
// this table shipped incomplete for two rounds, blocking ordinary searches like
|
|
4534
|
+
// `rg --sort-files .env src` (/code-review round 4).
|
|
4535
|
+
rg: {
|
|
4536
|
+
takesValue: /* @__PURE__ */ new Set([
|
|
4537
|
+
"-A",
|
|
4538
|
+
"-B",
|
|
4539
|
+
"-C",
|
|
4540
|
+
"-d",
|
|
4541
|
+
"-E",
|
|
4542
|
+
"-e",
|
|
4543
|
+
"-f",
|
|
4544
|
+
"-g",
|
|
4545
|
+
"-j",
|
|
4546
|
+
"-M",
|
|
4547
|
+
"-m",
|
|
4548
|
+
"-r",
|
|
4549
|
+
"-t",
|
|
4550
|
+
"-T",
|
|
4551
|
+
"--after-context",
|
|
4552
|
+
"--before-context",
|
|
4553
|
+
"--color",
|
|
4554
|
+
"--colors",
|
|
4555
|
+
"--context",
|
|
4556
|
+
"--context-separator",
|
|
4557
|
+
"--dfa-size-limit",
|
|
4558
|
+
"--encoding",
|
|
4559
|
+
"--engine",
|
|
4560
|
+
"--field-context-separator",
|
|
4561
|
+
"--field-match-separator",
|
|
4562
|
+
"--file",
|
|
4563
|
+
"--generate",
|
|
4564
|
+
"--glob",
|
|
4565
|
+
"--hostname-bin",
|
|
4566
|
+
"--hyperlink-format",
|
|
4567
|
+
"--iglob",
|
|
4568
|
+
"--ignore-file",
|
|
4569
|
+
"--max-columns",
|
|
4570
|
+
"--max-count",
|
|
4571
|
+
"--max-depth",
|
|
4572
|
+
// An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
|
|
4573
|
+
// alias lines as plain switches and swept it into noValue, which hard-blocked
|
|
4574
|
+
// `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
|
|
4575
|
+
"--maxdepth",
|
|
4576
|
+
"--max-filesize",
|
|
4577
|
+
"--path-separator",
|
|
4578
|
+
"--pre",
|
|
4579
|
+
"--pre-glob",
|
|
4580
|
+
"--regexp",
|
|
4581
|
+
"--regex-size-limit",
|
|
4582
|
+
"--replace",
|
|
4583
|
+
"--sort",
|
|
4584
|
+
"--sortr",
|
|
4585
|
+
"--threads",
|
|
4586
|
+
"--type",
|
|
4587
|
+
"--type-add",
|
|
4588
|
+
"--type-clear",
|
|
4589
|
+
"--type-not"
|
|
4590
|
+
]),
|
|
4591
|
+
noValue: /* @__PURE__ */ new Set([
|
|
4592
|
+
"-.",
|
|
4593
|
+
"-0",
|
|
4594
|
+
"-a",
|
|
4595
|
+
"-b",
|
|
4596
|
+
"-c",
|
|
4597
|
+
"-F",
|
|
4598
|
+
"-h",
|
|
4599
|
+
"-H",
|
|
4600
|
+
"-i",
|
|
4601
|
+
"-I",
|
|
4602
|
+
"-l",
|
|
4603
|
+
"-L",
|
|
4604
|
+
"-n",
|
|
4605
|
+
"-N",
|
|
4606
|
+
"-o",
|
|
4607
|
+
"-p",
|
|
4608
|
+
"-P",
|
|
4609
|
+
"-q",
|
|
4610
|
+
"-s",
|
|
4611
|
+
"-S",
|
|
4612
|
+
"-u",
|
|
4613
|
+
"-U",
|
|
4614
|
+
"-v",
|
|
4615
|
+
"-V",
|
|
4616
|
+
"-w",
|
|
4617
|
+
"-x",
|
|
4618
|
+
"-z",
|
|
4619
|
+
"--auto-hybrid-regex",
|
|
4620
|
+
"--binary",
|
|
4621
|
+
"--block-buffered",
|
|
4622
|
+
"--byte-offset",
|
|
4623
|
+
"--case-sensitive",
|
|
4624
|
+
"--column",
|
|
4625
|
+
"--count",
|
|
4626
|
+
"--count-matches",
|
|
4627
|
+
"--crlf",
|
|
4628
|
+
"--debug",
|
|
4629
|
+
"--files",
|
|
4630
|
+
"--files-with-matches",
|
|
4631
|
+
"--files-without-match",
|
|
4632
|
+
"--fixed-strings",
|
|
4633
|
+
"--follow",
|
|
4634
|
+
"--glob-case-insensitive",
|
|
4635
|
+
"--heading",
|
|
4636
|
+
"--help",
|
|
4637
|
+
"--hidden",
|
|
4638
|
+
"--ignore-case",
|
|
4639
|
+
"--ignore-file-case-insensitive",
|
|
4640
|
+
"--include-zero",
|
|
4641
|
+
"--invert-match",
|
|
4642
|
+
"--json",
|
|
4643
|
+
"--line-buffered",
|
|
4644
|
+
"--line-number",
|
|
4645
|
+
"--line-regexp",
|
|
4646
|
+
"--max-columns-preview",
|
|
4647
|
+
"--mmap",
|
|
4648
|
+
"--multiline",
|
|
4649
|
+
"--multiline-dotall",
|
|
4650
|
+
"--no-column",
|
|
4651
|
+
"--no-config",
|
|
4652
|
+
"--no-context-separator",
|
|
4653
|
+
"--no-encoding",
|
|
4654
|
+
"--no-filename",
|
|
4655
|
+
"--no-ignore",
|
|
4656
|
+
"--no-ignore-dot",
|
|
4657
|
+
"--no-ignore-exclude",
|
|
4658
|
+
"--no-ignore-files",
|
|
4659
|
+
"--no-ignore-global",
|
|
4660
|
+
"--no-ignore-messages",
|
|
4661
|
+
"--no-ignore-parent",
|
|
4662
|
+
"--no-ignore-vcs",
|
|
4663
|
+
"--no-line-number",
|
|
4664
|
+
"--no-messages",
|
|
4665
|
+
"--no-pcre2-unicode",
|
|
4666
|
+
"--no-pre",
|
|
4667
|
+
"--no-require-git",
|
|
4668
|
+
"--no-unicode",
|
|
4669
|
+
"--null",
|
|
4670
|
+
"--null-data",
|
|
4671
|
+
"--one-file-system",
|
|
4672
|
+
"--only-matching",
|
|
4673
|
+
"--passthru",
|
|
4674
|
+
"--pcre2",
|
|
4675
|
+
"--pcre2-version",
|
|
4676
|
+
"--pretty",
|
|
4677
|
+
"--print0",
|
|
4678
|
+
"--quiet",
|
|
4679
|
+
"--search-zip",
|
|
4680
|
+
"--smart-case",
|
|
4681
|
+
"--sort-files",
|
|
4682
|
+
"--stats",
|
|
4683
|
+
"--stop-on-nonmatch",
|
|
4684
|
+
"--text",
|
|
4685
|
+
"--trace",
|
|
4686
|
+
"--trim",
|
|
4687
|
+
"--type-list",
|
|
4688
|
+
"--unrestricted",
|
|
4689
|
+
"--version",
|
|
4690
|
+
"--vimgrep",
|
|
4691
|
+
"--with-filename",
|
|
4692
|
+
"--word-regexp",
|
|
4693
|
+
// The COMPLETE remainder of `rg --help`, added after /code-review round 5
|
|
4694
|
+
// found 40 documented switches in neither set. The table is now the whole
|
|
4695
|
+
// option list: `rg --help` yields 149 long options, 35 of them value-taking.
|
|
4696
|
+
"--ignore",
|
|
4697
|
+
"--ignore-dot",
|
|
4698
|
+
"--ignore-exclude",
|
|
4699
|
+
"--ignore-files",
|
|
4700
|
+
"--ignore-global",
|
|
4701
|
+
"--ignore-messages",
|
|
4702
|
+
"--ignore-parent",
|
|
4703
|
+
"--ignore-vcs",
|
|
4704
|
+
"--messages",
|
|
4705
|
+
"--no-auto-hybrid-regex",
|
|
4706
|
+
"--no-binary",
|
|
4707
|
+
"--no-block-buffered",
|
|
4708
|
+
"--no-byte-offset",
|
|
4709
|
+
"--no-crlf",
|
|
4710
|
+
"--no-fixed-strings",
|
|
4711
|
+
"--no-follow",
|
|
4712
|
+
"--no-glob-case-insensitive",
|
|
4713
|
+
"--no-heading",
|
|
4714
|
+
"--no-hidden",
|
|
4715
|
+
"--no-ignore-file-case-insensitive",
|
|
4716
|
+
"--no-include-zero",
|
|
4717
|
+
"--no-invert-match",
|
|
4718
|
+
"--no-json",
|
|
4719
|
+
"--no-line-buffered",
|
|
4720
|
+
"--no-max-columns-preview",
|
|
4721
|
+
"--no-mmap",
|
|
4722
|
+
"--no-multiline",
|
|
4723
|
+
"--no-multiline-dotall",
|
|
4724
|
+
"--no-one-file-system",
|
|
4725
|
+
"--no-pcre2",
|
|
4726
|
+
"--no-search-zip",
|
|
4727
|
+
"--no-sort-files",
|
|
4728
|
+
"--no-stats",
|
|
4729
|
+
"--no-text",
|
|
4730
|
+
"--no-trim",
|
|
4731
|
+
"--passthrough",
|
|
4732
|
+
"--pcre2-unicode",
|
|
4733
|
+
"--require-git",
|
|
4734
|
+
"--unicode"
|
|
4735
|
+
]),
|
|
4736
|
+
patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
|
|
4737
|
+
// `--files` and `--type-list` list or enumerate without a pattern. Founder
|
|
4738
|
+
// decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
|
|
4739
|
+
// by leaving the directory in the judged list.
|
|
4740
|
+
noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
|
|
4741
|
+
}
|
|
4742
|
+
};
|
|
4743
|
+
PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
|
|
4744
|
+
GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
|
|
4745
|
+
READER_VALUE_LETTERS = {
|
|
4746
|
+
awk: ["F", "v", "f"],
|
|
4747
|
+
gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
|
|
4748
|
+
sed: ["e", "f", "i", "l"],
|
|
4749
|
+
sort: ["C", "k", "o", "S", "t", "T"]
|
|
4750
|
+
};
|
|
4751
|
+
FILE_OPERAND_FLAGS = {
|
|
4752
|
+
grep: GREP_FILE_OPERANDS,
|
|
4753
|
+
egrep: GREP_FILE_OPERANDS,
|
|
4754
|
+
fgrep: GREP_FILE_OPERANDS,
|
|
4755
|
+
// rg and gawk are NOT installed on the measuring machine: these two rows are
|
|
4756
|
+
// from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
|
|
4757
|
+
// `-f/--file`) and are marked as such in the spec.
|
|
4758
|
+
// `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
|
|
4759
|
+
// a credential makes rg open it -- the same reason grep's `--include` is here.
|
|
4760
|
+
// Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
|
|
4761
|
+
// and printed its contents.
|
|
4762
|
+
rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
|
|
4763
|
+
sed: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4764
|
+
awk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4765
|
+
gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
|
|
4766
|
+
sort: /* @__PURE__ */ new Set(["--files0-from"])
|
|
4767
|
+
};
|
|
4768
|
+
SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
4769
|
+
TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
|
|
4770
|
+
ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
|
|
4771
|
+
RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
|
|
4772
|
+
RSYNC_SKIP = [
|
|
4773
|
+
"e",
|
|
4774
|
+
"--rsh",
|
|
4775
|
+
"--exclude",
|
|
4776
|
+
"--exclude-from",
|
|
4777
|
+
"--include",
|
|
4778
|
+
"--include-from",
|
|
4779
|
+
"--files-from",
|
|
4780
|
+
"f",
|
|
4781
|
+
"--filter"
|
|
4782
|
+
];
|
|
4783
|
+
CP_VALUE_LETTERS = ["S", "t"];
|
|
4784
|
+
INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
|
|
4785
|
+
COPY_VERBS = {
|
|
4786
|
+
cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4787
|
+
mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4788
|
+
install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
|
|
4789
|
+
ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
|
|
4790
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
|
|
4791
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
|
|
4792
|
+
tar: {
|
|
4793
|
+
source: "archive",
|
|
4794
|
+
archive: "tar",
|
|
4795
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
|
|
4796
|
+
valueLetters: TAR_VALUE_LETTERS
|
|
4797
|
+
},
|
|
4798
|
+
zip: {
|
|
4799
|
+
source: "archive",
|
|
4800
|
+
archive: "zip",
|
|
4801
|
+
skipFlags: ["x", "i", "--exclude", "--include"],
|
|
4802
|
+
valueLetters: ZIP_VALUE_LETTERS
|
|
4803
|
+
},
|
|
4804
|
+
ar: { source: "archive", archive: "ar" },
|
|
4805
|
+
// 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
|
|
4806
|
+
// ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
|
|
4807
|
+
// and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
|
|
4808
|
+
// drop the credential as an exclusion operand (/code-review round 8), and the
|
|
4809
|
+
// derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
|
|
4810
|
+
// what keeps the two tables honest about it.
|
|
4811
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
|
|
4812
|
+
gzip: { source: "all" },
|
|
4813
|
+
bzip2: { source: "all" },
|
|
4814
|
+
xz: { source: "all" },
|
|
4815
|
+
"docker cp": { source: "allButLast" },
|
|
4816
|
+
"kubectl cp": { source: "allButLast" },
|
|
4817
|
+
"gsutil cp": { source: "allButLast" },
|
|
4818
|
+
"gsutil rsync": { source: "allButLast" },
|
|
4819
|
+
"rclone copy": { source: "allButLast" },
|
|
4820
|
+
"rclone sync": { source: "allButLast" },
|
|
4821
|
+
"aws s3 cp": { source: "allButLast" },
|
|
4822
|
+
"aws s3 mv": { source: "allButLast" },
|
|
4823
|
+
"aws s3 sync": { source: "allButLast" },
|
|
4824
|
+
"gcloud storage cp": { source: "allButLast" },
|
|
4825
|
+
"az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
|
|
4826
|
+
};
|
|
4827
|
+
TAR_MODE_WORD = /^[a-zA-Z]+$/;
|
|
4828
|
+
COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
|
|
4067
4829
|
FS_OP_PRESCREEN_RE = new RegExp(
|
|
4068
4830
|
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
4069
4831
|
// reader right after `"` / `'`, and without these two characters the
|
|
4070
4832
|
// prescreen rejected every string-wrapped read before the parser ran.
|
|
4071
4833
|
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
4072
|
-
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4834
|
+
`(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4073
4835
|
);
|
|
4074
4836
|
HOME_CACHE_ALLOWLIST = [
|
|
4075
4837
|
".cache",
|
|
@@ -4359,8 +5121,17 @@ var init_dist = __esm({
|
|
|
4359
5121
|
deriveRedirOp("cat <<X\nX"),
|
|
4360
5122
|
deriveRedirOp("cat <<-X\nX")
|
|
4361
5123
|
]);
|
|
4362
|
-
|
|
4363
|
-
|
|
5124
|
+
FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
5125
|
+
COPY_RULE_OF = {
|
|
5126
|
+
"shield:project-jail:block-read-ssh": "shield:project-jail:review-copy-ssh",
|
|
5127
|
+
"shield:project-jail:block-read-aws": "shield:project-jail:review-copy-aws",
|
|
5128
|
+
"shield:project-jail:block-read-env": "shield:project-jail:review-copy-env",
|
|
5129
|
+
"shield:project-jail:review-read-credentials": "shield:project-jail:review-copy-credentials"
|
|
5130
|
+
};
|
|
5131
|
+
isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
5132
|
+
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
5133
|
+
NONE = { kind: "none" };
|
|
5134
|
+
UNKNOWN = { kind: "unknown" };
|
|
4364
5135
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
4365
5136
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
4366
5137
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -5405,7 +6176,7 @@ var init_dist = __esm({
|
|
|
5405
6176
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
5406
6177
|
];
|
|
5407
6178
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5408
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
6179
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
|
|
5409
6180
|
DEDUPE_PREVIEW_LEN = 120;
|
|
5410
6181
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
5411
6182
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -12975,6 +13746,28 @@ function codexSessionCost(model, tokens, request2) {
|
|
|
12975
13746
|
const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
|
|
12976
13747
|
return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
|
|
12977
13748
|
}
|
|
13749
|
+
function statAndFirstLine(file) {
|
|
13750
|
+
const CAP = 4 * 1024 * 1024;
|
|
13751
|
+
const CHUNK = 64 * 1024;
|
|
13752
|
+
const fd = import_fs20.default.openSync(file, "r");
|
|
13753
|
+
try {
|
|
13754
|
+
const stat = import_fs20.default.fstatSync(fd);
|
|
13755
|
+
const limit = Math.min(stat.size, CAP);
|
|
13756
|
+
const parts = [];
|
|
13757
|
+
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
13758
|
+
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
13759
|
+
const read2 = import_fs20.default.readSync(fd, buf, 0, buf.length, pos);
|
|
13760
|
+
if (read2 <= 0) break;
|
|
13761
|
+
const slice = buf.subarray(0, read2);
|
|
13762
|
+
const nl = slice.indexOf(10);
|
|
13763
|
+
parts.push(nl >= 0 ? slice.subarray(0, nl) : slice);
|
|
13764
|
+
if (nl >= 0) break;
|
|
13765
|
+
}
|
|
13766
|
+
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
13767
|
+
} finally {
|
|
13768
|
+
import_fs20.default.closeSync(fd);
|
|
13769
|
+
}
|
|
13770
|
+
}
|
|
12978
13771
|
function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
12979
13772
|
const files = [];
|
|
12980
13773
|
const walk = (dir) => {
|
|
@@ -12992,10 +13785,10 @@ function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
|
12992
13785
|
const sessions = /* @__PURE__ */ new Map();
|
|
12993
13786
|
for (const file of files.sort()) {
|
|
12994
13787
|
try {
|
|
12995
|
-
const stat =
|
|
13788
|
+
const { stat, first: head } = statAndFirstLine(file);
|
|
12996
13789
|
let id = "";
|
|
12997
13790
|
try {
|
|
12998
|
-
const first = JSON.parse(
|
|
13791
|
+
const first = JSON.parse(head);
|
|
12999
13792
|
if (first?.type === "session_meta" && typeof first.payload?.id === "string")
|
|
13000
13793
|
id = first.payload.id;
|
|
13001
13794
|
} catch {
|
|
@@ -59460,7 +60253,11 @@ var BUILTIN_JAIL = [
|
|
|
59460
60253
|
"~/.ssh \u2014 SSH private keys",
|
|
59461
60254
|
"~/.aws \u2014 AWS credentials",
|
|
59462
60255
|
".env files",
|
|
59463
|
-
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
|
|
60256
|
+
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud",
|
|
60257
|
+
// Stage 4 (2026-09-11): reads are blocked; a COPY out of the jail (cp, tar,
|
|
60258
|
+
// scp, rsync, aws s3 cp, ...) is reviewed, because a backup and a theft are
|
|
60259
|
+
// the same command shape.
|
|
60260
|
+
"copies out of the jail (cp, tar, scp, rsync, cloud upload) \u2014 review"
|
|
59464
60261
|
];
|
|
59465
60262
|
function registerJailCommand(program2) {
|
|
59466
60263
|
const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
|