@node9/proxy 2.14.0 → 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.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;
@@ -1599,7 +1616,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1599
1616
  return result?.verdict !== "block";
1600
1617
  }
1601
1618
  if (nodeType !== "CallExpr") return true;
1602
- const { name, flags, paths, words } = extractLiteralArgs(n);
1619
+ const { name, flags, paths, words, args } = extractLiteralArgs(n);
1603
1620
  if (!name) return true;
1604
1621
  if (name === "rm") {
1605
1622
  const flagStr = flags.join("").toLowerCase();
@@ -1639,7 +1656,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1639
1656
  return true;
1640
1657
  }
1641
1658
  }
1642
- const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
1659
+ const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
1643
1660
  if (readPaths) {
1644
1661
  for (const p of readPaths) {
1645
1662
  result = stricter(result, matchSensitivePath2(p));
@@ -1675,9 +1692,22 @@ function flagIs(w, names) {
1675
1692
  const f = flagInfo(w);
1676
1693
  return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
1677
1694
  }
1678
- function operandOf(a, names) {
1695
+ function operandOf(a, names, valueLetters) {
1679
1696
  if (!names || a.afterFlag === null) return false;
1680
- return flagIs(a.afterFlag, names) && flagInfo(a.afterFlag).attached === null;
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;
1681
1711
  }
1682
1712
  function resolveCopyShape(words, h) {
1683
1713
  const verb = baseWord(words[h]);
@@ -1701,7 +1731,7 @@ function findStartPoints(words, h) {
1701
1731
  const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
1702
1732
  if (k < 0) return { k, starts: [] };
1703
1733
  const firstPredicate = words.findIndex(
1704
- (w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
1734
+ (w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
1705
1735
  );
1706
1736
  const end = firstPredicate > h ? firstPredicate : k;
1707
1737
  return { k, starts: positionalAfter(words, h + 1, end) };
@@ -1721,14 +1751,35 @@ function copySourcePaths(words) {
1721
1751
  const { shape, last } = r;
1722
1752
  const args = positionedArgs(words, last + 1);
1723
1753
  const tail = words.slice(last + 1);
1724
- const skipped = (a) => operandOf(a, shape.skipFlags);
1725
- const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && flagIs(w, ["t", "--target-directory"]));
1726
- const targetOperand = (a) => targetDir && operandOf(a, ["t", "--target-directory"]);
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);
1727
1780
  const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
1728
1781
  const dynamicDest = lastOperand === null;
1729
1782
  const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
1730
- if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
1731
- return [];
1732
1783
  let src;
1733
1784
  switch (shape.source) {
1734
1785
  case "all":
@@ -1738,11 +1789,17 @@ function copySourcePaths(words) {
1738
1789
  src = targetDir ? args : args.slice(0, 1);
1739
1790
  break;
1740
1791
  case "flagOperand": {
1741
- const inline = tail.filter((w) => w !== null && w.startsWith("--")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && (shape.sourceFlags ?? []).includes(f.long ?? "")).map((f) => f.attached);
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);
1742
1801
  return [
1743
- ...args.filter(
1744
- (a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
1745
- ).map((a) => a.value),
1802
+ ...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
1746
1803
  ...inline
1747
1804
  ];
1748
1805
  }
@@ -1753,7 +1810,14 @@ function copySourcePaths(words) {
1753
1810
  src = targetDir || dynamicDest ? args : args.slice(0, -1);
1754
1811
  break;
1755
1812
  }
1756
- return src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
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;
1757
1821
  }
1758
1822
  function archiveInputs(kind, args, tail) {
1759
1823
  const first = args[0];
@@ -1765,13 +1829,17 @@ function archiveInputs(kind, args, tail) {
1765
1829
  const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
1766
1830
  if (extracting && !writing) return [];
1767
1831
  void mode;
1768
- let i = 0;
1769
- if (bareKey) {
1770
- i = 1;
1771
- const next = args[1];
1772
- if (first.value.includes("f") && next && next.afterFlag === null) i = 2;
1773
- }
1774
- return args.slice(i);
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)];
1775
1843
  }
1776
1844
  if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
1777
1845
  if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
@@ -1798,6 +1866,138 @@ function matchSensitivePath2(p) {
1798
1866
  function baseWord(w) {
1799
1867
  return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
1800
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
+ }
1801
2001
  function wrappedReadPaths(words, name) {
1802
2002
  if (name === "find") {
1803
2003
  const { k, starts } = findStartPoints(words, 0);
@@ -1805,7 +2005,14 @@ function wrappedReadPaths(words, name) {
1805
2005
  }
1806
2006
  if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1807
2007
  const h = unwrapCommandHead(words);
1808
- return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
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
+ ];
1809
2016
  }
1810
2017
  function literalShellPayload(words, name) {
1811
2018
  const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
@@ -3491,7 +3698,7 @@ function* stringValues(obj, depth = 0) {
3491
3698
  }
3492
3699
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3493
3700
  }
3494
- 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, SCP_VALUE_FLAGS, RSYNC_SKIP, 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, 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;
3495
3702
  var init_dist = __esm({
3496
3703
  "packages/policy-engine/dist/index.mjs"() {
3497
3704
  "use strict";
@@ -4227,7 +4434,345 @@ var init_dist = __esm({
4227
4434
  "nl",
4228
4435
  "dd"
4229
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
+ };
4230
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"];
4231
4776
  RSYNC_SKIP = [
4232
4777
  "e",
4233
4778
  "--rsh",
@@ -4239,21 +4784,35 @@ var init_dist = __esm({
4239
4784
  "f",
4240
4785
  "--filter"
4241
4786
  ];
4787
+ CP_VALUE_LETTERS = ["S", "t"];
4788
+ INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
4242
4789
  COPY_VERBS = {
4243
- cp: { source: "allButLast", targetDirFlag: true },
4244
- mv: { source: "allButLast", targetDirFlag: true },
4245
- install: { source: "allButLast", targetDirFlag: true },
4246
- ln: { source: "first", targetDirFlag: true },
4247
- scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
4248
- rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
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 },
4249
4796
  tar: {
4250
4797
  source: "archive",
4251
4798
  archive: "tar",
4252
- skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
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
4253
4807
  },
4254
- zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
4255
4808
  ar: { source: "archive", archive: "ar" },
4256
- "7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
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: [] },
4257
4816
  gzip: { source: "all" },
4258
4817
  bzip2: { source: "all" },
4259
4818
  xz: { source: "all" },
@@ -4575,6 +5134,8 @@ var init_dist = __esm({
4575
5134
  };
4576
5135
  isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
4577
5136
  positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
5137
+ NONE = { kind: "none" };
5138
+ UNKNOWN = { kind: "unknown" };
4578
5139
  DEFAULT_EGRESS_ALLOWLIST = [
4579
5140
  // node9's own control plane (api, app, dev-api, staging and the apex).
4580
5141
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -5619,7 +6180,7 @@ var init_dist = __esm({
5619
6180
  { view: "separators-stripped", decoder: "separators", stripped: true }
5620
6181
  ];
5621
6182
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5622
- CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
6183
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
5623
6184
  DEDUPE_PREVIEW_LEN = 120;
5624
6185
  TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5625
6186
  /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;