@node9/proxy 2.14.0 → 2.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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;
@@ -2738,6 +2945,17 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2738
2945
  function isIgnoredTool(toolName, config) {
2739
2946
  return matchesPattern(toolName, config.policy.ignoredTools);
2740
2947
  }
2948
+ function stripTerminalEscapes(s) {
2949
+ return s.replace(TERMINAL_ESCAPE_RE, "");
2950
+ }
2951
+ function stripControlChars(s) {
2952
+ return s.replace(CONTROL_CHAR_RE, "");
2953
+ }
2954
+ function safeMessage(value, max = 300) {
2955
+ const raw = typeof value === "string" ? value : value instanceof Error ? value.message : String(value ?? "");
2956
+ const s = stripTerminalEscapes(raw).replace(/\s+/g, " ").trim();
2957
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
2958
+ }
2741
2959
  function isShieldVerdict(v) {
2742
2960
  return v === "allow" || v === "review" || v === "block";
2743
2961
  }
@@ -2955,7 +3173,10 @@ function computeSecurityScore(opts) {
2955
3173
  }
2956
3174
  function truncateBlastPath(full) {
2957
3175
  if (!full) return "";
2958
- const cleaned = full.replace(/[/\\]+$/, "");
3176
+ if (full.length > MAX_BLAST_PATH) full = full.slice(-MAX_BLAST_PATH);
3177
+ let end = full.length;
3178
+ while (end > 0 && (full[end - 1] === "/" || full[end - 1] === "\\")) end--;
3179
+ const cleaned = full.slice(0, end);
2959
3180
  const parts = cleaned.split(/[/\\]+/).filter((p) => p.length > 0);
2960
3181
  if (parts.length <= 2) {
2961
3182
  return cleaned.startsWith("~") && !cleaned.startsWith("~/") ? cleaned : cleaned.startsWith("~/") ? cleaned : parts.join("/");
@@ -3451,7 +3672,7 @@ function toScanFinding(c) {
3451
3672
  }
3452
3673
  function previewArgs(input, max) {
3453
3674
  const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
3454
- const s = String(cmd).replace(TERMINAL_ESCAPE_RE, "").replace(/\s+/g, " ").trim();
3675
+ const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
3455
3676
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
3456
3677
  }
3457
3678
  function makeFinding(args) {
@@ -3491,7 +3712,7 @@ function* stringValues(obj, depth = 0) {
3491
3712
  }
3492
3713
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3493
3714
  }
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;
3715
+ 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, TERMINAL_ESCAPE_RE, CONTROL_CHAR_RE, 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, MAX_BLAST_PATH, 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, ENGINE_VERSION;
3495
3716
  var init_dist = __esm({
3496
3717
  "packages/policy-engine/dist/index.mjs"() {
3497
3718
  "use strict";
@@ -4227,7 +4448,345 @@ var init_dist = __esm({
4227
4448
  "nl",
4228
4449
  "dd"
4229
4450
  ]);
4451
+ GREP_SHAPE = {
4452
+ takesValue: /* @__PURE__ */ new Set([
4453
+ "-A",
4454
+ "-B",
4455
+ "-C",
4456
+ "-D",
4457
+ "-d",
4458
+ "-e",
4459
+ "-f",
4460
+ "-m",
4461
+ "--after-context",
4462
+ "--before-context",
4463
+ "--binary-files",
4464
+ "--context",
4465
+ "--devices",
4466
+ "--directories",
4467
+ "--exclude",
4468
+ "--exclude-dir",
4469
+ "--exclude-from",
4470
+ "--file",
4471
+ "--include",
4472
+ "--label",
4473
+ "--max-count",
4474
+ "--regexp"
4475
+ ]),
4476
+ noValue: /* @__PURE__ */ new Set([
4477
+ "-E",
4478
+ "-F",
4479
+ "-G",
4480
+ "-P",
4481
+ "-i",
4482
+ "-y",
4483
+ "-v",
4484
+ "-V",
4485
+ "-w",
4486
+ "-x",
4487
+ "-c",
4488
+ "-l",
4489
+ "-L",
4490
+ "-o",
4491
+ "-q",
4492
+ "-s",
4493
+ "-b",
4494
+ "-H",
4495
+ "-h",
4496
+ "-n",
4497
+ "-T",
4498
+ "-Z",
4499
+ "-z",
4500
+ "-R",
4501
+ "-r",
4502
+ "-U",
4503
+ "-u",
4504
+ "-I",
4505
+ "-a",
4506
+ "--basic-regexp",
4507
+ "--binary",
4508
+ "--byte-offset",
4509
+ "--color",
4510
+ "--colour",
4511
+ "--count",
4512
+ "--dereference-recursive",
4513
+ "--extended-regexp",
4514
+ "--files-with-matches",
4515
+ "--files-without-match",
4516
+ "--fixed-strings",
4517
+ "--help",
4518
+ "--ignore-case",
4519
+ "--initial-tab",
4520
+ "--invert-match",
4521
+ "--line-buffered",
4522
+ "--line-number",
4523
+ "--line-regexp",
4524
+ "--no-filename",
4525
+ "--no-group-separator",
4526
+ "--no-ignore-case",
4527
+ "--no-messages",
4528
+ "--null",
4529
+ "--null-data",
4530
+ "--only-matching",
4531
+ "--perl-regexp",
4532
+ "--quiet",
4533
+ "--recursive",
4534
+ "--silent",
4535
+ "--text",
4536
+ "--version",
4537
+ "--with-filename",
4538
+ "--word-regexp"
4539
+ ]),
4540
+ patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
4541
+ noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
4542
+ };
4543
+ PATTERN_VERBS = {
4544
+ grep: GREP_SHAPE,
4545
+ // /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
4546
+ // `grep -E` and `grep -F`. Read in full, not assumed.
4547
+ egrep: GREP_SHAPE,
4548
+ fgrep: GREP_SHAPE,
4549
+ // ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
4550
+ // was not installed on the measuring machine; it is, and that stale claim is why
4551
+ // this table shipped incomplete for two rounds, blocking ordinary searches like
4552
+ // `rg --sort-files .env src` (/code-review round 4).
4553
+ rg: {
4554
+ takesValue: /* @__PURE__ */ new Set([
4555
+ "-A",
4556
+ "-B",
4557
+ "-C",
4558
+ "-d",
4559
+ "-E",
4560
+ "-e",
4561
+ "-f",
4562
+ "-g",
4563
+ "-j",
4564
+ "-M",
4565
+ "-m",
4566
+ "-r",
4567
+ "-t",
4568
+ "-T",
4569
+ "--after-context",
4570
+ "--before-context",
4571
+ "--color",
4572
+ "--colors",
4573
+ "--context",
4574
+ "--context-separator",
4575
+ "--dfa-size-limit",
4576
+ "--encoding",
4577
+ "--engine",
4578
+ "--field-context-separator",
4579
+ "--field-match-separator",
4580
+ "--file",
4581
+ "--generate",
4582
+ "--glob",
4583
+ "--hostname-bin",
4584
+ "--hyperlink-format",
4585
+ "--iglob",
4586
+ "--ignore-file",
4587
+ "--max-columns",
4588
+ "--max-count",
4589
+ "--max-depth",
4590
+ // An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
4591
+ // alias lines as plain switches and swept it into noValue, which hard-blocked
4592
+ // `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
4593
+ "--maxdepth",
4594
+ "--max-filesize",
4595
+ "--path-separator",
4596
+ "--pre",
4597
+ "--pre-glob",
4598
+ "--regexp",
4599
+ "--regex-size-limit",
4600
+ "--replace",
4601
+ "--sort",
4602
+ "--sortr",
4603
+ "--threads",
4604
+ "--type",
4605
+ "--type-add",
4606
+ "--type-clear",
4607
+ "--type-not"
4608
+ ]),
4609
+ noValue: /* @__PURE__ */ new Set([
4610
+ "-.",
4611
+ "-0",
4612
+ "-a",
4613
+ "-b",
4614
+ "-c",
4615
+ "-F",
4616
+ "-h",
4617
+ "-H",
4618
+ "-i",
4619
+ "-I",
4620
+ "-l",
4621
+ "-L",
4622
+ "-n",
4623
+ "-N",
4624
+ "-o",
4625
+ "-p",
4626
+ "-P",
4627
+ "-q",
4628
+ "-s",
4629
+ "-S",
4630
+ "-u",
4631
+ "-U",
4632
+ "-v",
4633
+ "-V",
4634
+ "-w",
4635
+ "-x",
4636
+ "-z",
4637
+ "--auto-hybrid-regex",
4638
+ "--binary",
4639
+ "--block-buffered",
4640
+ "--byte-offset",
4641
+ "--case-sensitive",
4642
+ "--column",
4643
+ "--count",
4644
+ "--count-matches",
4645
+ "--crlf",
4646
+ "--debug",
4647
+ "--files",
4648
+ "--files-with-matches",
4649
+ "--files-without-match",
4650
+ "--fixed-strings",
4651
+ "--follow",
4652
+ "--glob-case-insensitive",
4653
+ "--heading",
4654
+ "--help",
4655
+ "--hidden",
4656
+ "--ignore-case",
4657
+ "--ignore-file-case-insensitive",
4658
+ "--include-zero",
4659
+ "--invert-match",
4660
+ "--json",
4661
+ "--line-buffered",
4662
+ "--line-number",
4663
+ "--line-regexp",
4664
+ "--max-columns-preview",
4665
+ "--mmap",
4666
+ "--multiline",
4667
+ "--multiline-dotall",
4668
+ "--no-column",
4669
+ "--no-config",
4670
+ "--no-context-separator",
4671
+ "--no-encoding",
4672
+ "--no-filename",
4673
+ "--no-ignore",
4674
+ "--no-ignore-dot",
4675
+ "--no-ignore-exclude",
4676
+ "--no-ignore-files",
4677
+ "--no-ignore-global",
4678
+ "--no-ignore-messages",
4679
+ "--no-ignore-parent",
4680
+ "--no-ignore-vcs",
4681
+ "--no-line-number",
4682
+ "--no-messages",
4683
+ "--no-pcre2-unicode",
4684
+ "--no-pre",
4685
+ "--no-require-git",
4686
+ "--no-unicode",
4687
+ "--null",
4688
+ "--null-data",
4689
+ "--one-file-system",
4690
+ "--only-matching",
4691
+ "--passthru",
4692
+ "--pcre2",
4693
+ "--pcre2-version",
4694
+ "--pretty",
4695
+ "--print0",
4696
+ "--quiet",
4697
+ "--search-zip",
4698
+ "--smart-case",
4699
+ "--sort-files",
4700
+ "--stats",
4701
+ "--stop-on-nonmatch",
4702
+ "--text",
4703
+ "--trace",
4704
+ "--trim",
4705
+ "--type-list",
4706
+ "--unrestricted",
4707
+ "--version",
4708
+ "--vimgrep",
4709
+ "--with-filename",
4710
+ "--word-regexp",
4711
+ // The COMPLETE remainder of `rg --help`, added after /code-review round 5
4712
+ // found 40 documented switches in neither set. The table is now the whole
4713
+ // option list: `rg --help` yields 149 long options, 35 of them value-taking.
4714
+ "--ignore",
4715
+ "--ignore-dot",
4716
+ "--ignore-exclude",
4717
+ "--ignore-files",
4718
+ "--ignore-global",
4719
+ "--ignore-messages",
4720
+ "--ignore-parent",
4721
+ "--ignore-vcs",
4722
+ "--messages",
4723
+ "--no-auto-hybrid-regex",
4724
+ "--no-binary",
4725
+ "--no-block-buffered",
4726
+ "--no-byte-offset",
4727
+ "--no-crlf",
4728
+ "--no-fixed-strings",
4729
+ "--no-follow",
4730
+ "--no-glob-case-insensitive",
4731
+ "--no-heading",
4732
+ "--no-hidden",
4733
+ "--no-ignore-file-case-insensitive",
4734
+ "--no-include-zero",
4735
+ "--no-invert-match",
4736
+ "--no-json",
4737
+ "--no-line-buffered",
4738
+ "--no-max-columns-preview",
4739
+ "--no-mmap",
4740
+ "--no-multiline",
4741
+ "--no-multiline-dotall",
4742
+ "--no-one-file-system",
4743
+ "--no-pcre2",
4744
+ "--no-search-zip",
4745
+ "--no-sort-files",
4746
+ "--no-stats",
4747
+ "--no-text",
4748
+ "--no-trim",
4749
+ "--passthrough",
4750
+ "--pcre2-unicode",
4751
+ "--require-git",
4752
+ "--unicode"
4753
+ ]),
4754
+ patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
4755
+ // `--files` and `--type-list` list or enumerate without a pattern. Founder
4756
+ // decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
4757
+ // by leaving the directory in the judged list.
4758
+ noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
4759
+ }
4760
+ };
4761
+ PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
4762
+ GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
4763
+ READER_VALUE_LETTERS = {
4764
+ awk: ["F", "v", "f"],
4765
+ gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
4766
+ sed: ["e", "f", "i", "l"],
4767
+ sort: ["C", "k", "o", "S", "t", "T"]
4768
+ };
4769
+ FILE_OPERAND_FLAGS = {
4770
+ grep: GREP_FILE_OPERANDS,
4771
+ egrep: GREP_FILE_OPERANDS,
4772
+ fgrep: GREP_FILE_OPERANDS,
4773
+ // rg and gawk are NOT installed on the measuring machine: these two rows are
4774
+ // from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
4775
+ // `-f/--file`) and are marked as such in the spec.
4776
+ // `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
4777
+ // a credential makes rg open it -- the same reason grep's `--include` is here.
4778
+ // Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
4779
+ // and printed its contents.
4780
+ rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
4781
+ sed: /* @__PURE__ */ new Set(["-f", "--file"]),
4782
+ awk: /* @__PURE__ */ new Set(["-f", "--file"]),
4783
+ gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
4784
+ sort: /* @__PURE__ */ new Set(["--files0-from"])
4785
+ };
4230
4786
  SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
4787
+ TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
4788
+ ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
4789
+ RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
4231
4790
  RSYNC_SKIP = [
4232
4791
  "e",
4233
4792
  "--rsh",
@@ -4239,21 +4798,35 @@ var init_dist = __esm({
4239
4798
  "f",
4240
4799
  "--filter"
4241
4800
  ];
4801
+ CP_VALUE_LETTERS = ["S", "t"];
4802
+ INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
4242
4803
  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 },
4804
+ cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4805
+ mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4806
+ install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
4807
+ ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4808
+ scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
4809
+ rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
4249
4810
  tar: {
4250
4811
  source: "archive",
4251
4812
  archive: "tar",
4252
- skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
4813
+ skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
4814
+ valueLetters: TAR_VALUE_LETTERS
4815
+ },
4816
+ zip: {
4817
+ source: "archive",
4818
+ archive: "zip",
4819
+ skipFlags: ["x", "i", "--exclude", "--include"],
4820
+ valueLetters: ZIP_VALUE_LETTERS
4253
4821
  },
4254
- zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
4255
4822
  ar: { source: "archive", archive: "ar" },
4256
- "7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
4823
+ // 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
4824
+ // ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
4825
+ // and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
4826
+ // drop the credential as an exclusion operand (/code-review round 8), and the
4827
+ // derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
4828
+ // what keeps the two tables honest about it.
4829
+ "7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
4257
4830
  gzip: { source: "all" },
4258
4831
  bzip2: { source: "all" },
4259
4832
  xz: { source: "all" },
@@ -4575,6 +5148,8 @@ var init_dist = __esm({
4575
5148
  };
4576
5149
  isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
4577
5150
  positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
5151
+ NONE = { kind: "none" };
5152
+ UNKNOWN = { kind: "unknown" };
4578
5153
  DEFAULT_EGRESS_ALLOWLIST = [
4579
5154
  // node9's own control plane (api, app, dev-api, staging and the apex).
4580
5155
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -4764,6 +5339,8 @@ var init_dist = __esm({
4764
5339
  block: 2
4765
5340
  };
4766
5341
  SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
5342
+ TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
5343
+ CONTROL_CHAR_RE = /[\x00-\x1F\x7F]/g;
4767
5344
  aws_default = {
4768
5345
  name: "aws",
4769
5346
  description: "Protects AWS infrastructure from destructive AI operations",
@@ -5541,6 +6118,7 @@ var init_dist = __esm({
5541
6118
  longOutputRedactions: 1
5542
6119
  };
5543
6120
  LOOP_THRESHOLD_FOR_WASTE = 3;
6121
+ MAX_BLAST_PATH = 4096;
5544
6122
  DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
5545
6123
  SENSITIVE_PATH_RE = /[\\/]\.aws(?:[\\/]|$)|^\.aws[\\/]|[\\/]\.ssh(?:[\\/]|$)|^\.ssh[\\/]|(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
5546
6124
  FILE_TOOLS = /* @__PURE__ */ new Set([
@@ -5619,10 +6197,8 @@ var init_dist = __esm({
5619
6197
  { view: "separators-stripped", decoder: "separators", stripped: true }
5620
6198
  ];
5621
6199
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5622
- CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
6200
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
5623
6201
  DEDUPE_PREVIEW_LEN = 120;
5624
- TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5625
- /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
5626
6202
  ENGINE_VERSION = "1.4.0";
5627
6203
  }
5628
6204
  });
@@ -8170,6 +8746,14 @@ var init_context_sniper = __esm({
8170
8746
  }
8171
8747
  });
8172
8748
 
8749
+ // src/utils/safe-text.ts
8750
+ var init_safe_text = __esm({
8751
+ "src/utils/safe-text.ts"() {
8752
+ "use strict";
8753
+ init_dist();
8754
+ }
8755
+ });
8756
+
8173
8757
  // src/ui/native.ts
8174
8758
  import { spawn } from "child_process";
8175
8759
  import path11 from "path";
@@ -8281,7 +8865,7 @@ function escapePango(text) {
8281
8865
  function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
8282
8866
  const lines = [];
8283
8867
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
8284
- const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
8868
+ const safeAgent = safeMessage(agent ?? "AI Agent", 80);
8285
8869
  lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
8286
8870
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
8287
8871
  if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
@@ -8416,6 +9000,7 @@ var init_native = __esm({
8416
9000
  "src/ui/native.ts"() {
8417
9001
  "use strict";
8418
9002
  init_context_sniper();
9003
+ init_safe_text();
8419
9004
  isTestEnv = () => {
8420
9005
  return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
8421
9006
  };
@@ -8805,14 +9390,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
8805
9390
  if (!res.ok) {
8806
9391
  fs12.appendFileSync(
8807
9392
  HOOK_DEBUG_LOG,
8808
- `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
9393
+ `[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
8809
9394
  `
8810
9395
  );
8811
9396
  }
8812
9397
  } catch (err2) {
8813
9398
  fs12.appendFileSync(
8814
9399
  HOOK_DEBUG_LOG,
8815
- `[resolve-cloud] PATCH failed for ${requestId}: ${err2.message}
9400
+ `[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err2)}
8816
9401
  `
8817
9402
  );
8818
9403
  }
@@ -8822,6 +9407,7 @@ var init_cloud = __esm({
8822
9407
  "src/auth/cloud.ts"() {
8823
9408
  "use strict";
8824
9409
  init_audit();
9410
+ init_safe_text();
8825
9411
  DLP_SAMPLE_MAX_LEN = 200;
8826
9412
  DLP_PATTERN_MAX_LEN = 100;
8827
9413
  KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
@@ -8954,9 +9540,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
8954
9540
  if (!options?.calledFromDaemon) {
8955
9541
  const actId = randomUUID2();
8956
9542
  const actTs = Date.now();
8957
- const stripAnsi2 = (s) => s.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "");
8958
- const sanitizedAgent = meta?.agent ? stripAnsi2(meta.agent).slice(0, 80) : void 0;
8959
- const sanitizedMcpServer = meta?.mcpServer ? stripAnsi2(meta.mcpServer).slice(0, 40) : void 0;
9543
+ const sanitizedAgent = meta?.agent ? safeMessage(meta.agent, 80) : void 0;
9544
+ const sanitizedMcpServer = meta?.mcpServer ? safeMessage(meta.mcpServer, 40) : void 0;
8960
9545
  const socketOk = await notifyActivity({
8961
9546
  id: actId,
8962
9547
  ts: actTs,
@@ -9831,6 +10416,7 @@ var init_orchestrator = __esm({
9831
10416
  init_loop_detector();
9832
10417
  init_shields();
9833
10418
  init_jail();
10419
+ init_safe_text();
9834
10420
  WRITE_TOOLS = /* @__PURE__ */ new Set([
9835
10421
  "write",
9836
10422
  "write_file",
@@ -14436,7 +15022,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
14436
15022
  }
14437
15023
  }
14438
15024
  } catch (err2) {
14439
- fs26.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
15025
+ fs26.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${safeMessage(err2)}
14440
15026
  `);
14441
15027
  }
14442
15028
  }
@@ -14475,6 +15061,7 @@ var init_costSync = __esm({
14475
15061
  init_cost_gemini();
14476
15062
  init_cost_copilot();
14477
15063
  init_session_files();
15064
+ init_safe_text();
14478
15065
  SYNC_INTERVAL_MS = 10 * 60 * 1e3;
14479
15066
  claudeSource = {
14480
15067
  id: "claude",
@@ -15310,9 +15897,6 @@ function fmtTs(ts) {
15310
15897
  return ts.slice(0, 10);
15311
15898
  }
15312
15899
  }
15313
- function stripTerminalEscapes(s) {
15314
- return s.replace(TERMINAL_ESCAPE_RE2, "");
15315
- }
15316
15900
  function preview(input, max) {
15317
15901
  const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
15318
15902
  const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
@@ -18027,7 +18611,7 @@ function registerScanCommand(program2) {
18027
18611
  }
18028
18612
  );
18029
18613
  }
18030
- var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
18614
+ var toolInspectionMap, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
18031
18615
  var init_scan = __esm({
18032
18616
  "src/cli/commands/scan.ts"() {
18033
18617
  "use strict";
@@ -18053,6 +18637,7 @@ var init_scan = __esm({
18053
18637
  init_scan_json();
18054
18638
  init_session_files();
18055
18639
  init_scan_history();
18640
+ init_safe_text();
18056
18641
  toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
18057
18642
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
18058
18643
  ".ts",
@@ -18087,7 +18672,6 @@ var init_scan = __esm({
18087
18672
  /\bseverity:\s*['"](?:block|review|allow)['"]/,
18088
18673
  /NODE9 SECURITY ALERT/
18089
18674
  ];
18090
- TERMINAL_ESCAPE_RE2 = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
18091
18675
  LOOP_TOOLS = /* @__PURE__ */ new Set([
18092
18676
  "bash",
18093
18677
  "execute_bash",
@@ -18578,7 +19162,7 @@ function atomicWriteSync2(filePath, data, options) {
18578
19162
  function redactArgs(value) {
18579
19163
  if (!value || typeof value !== "object") return value;
18580
19164
  if (Array.isArray(value)) return value.map(redactArgs);
18581
- const result = {};
19165
+ const result = /* @__PURE__ */ Object.create(null);
18582
19166
  for (const [k, v] of Object.entries(value)) {
18583
19167
  result[k] = SECRET_KEY_RE.test(k) ? "[REDACTED]" : redactArgs(v);
18584
19168
  }
@@ -22930,7 +23514,7 @@ data: ${JSON.stringify(item.data)}
22930
23514
  if (req.method === "GET" && pathname === "/state/check") {
22931
23515
  const predicatesParam = reqUrl.searchParams.get("predicates") ?? "";
22932
23516
  const predicates = predicatesParam.split(",").filter(Boolean);
22933
- const results = {};
23517
+ const results = /* @__PURE__ */ Object.create(null);
22934
23518
  for (const p of predicates) {
22935
23519
  results[p] = sessionHistory.checkPredicate(p);
22936
23520
  }
@@ -50665,7 +51249,7 @@ async function startTail(options = {}) {
50665
51249
  req.on("error", (err2) => {
50666
51250
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
50667
51251
  console.error(chalk44.red(`
50668
- \u274C ${msg}`));
51252
+ \u274C ${safeMessage(msg)}`));
50669
51253
  process.exit(1);
50670
51254
  });
50671
51255
  }
@@ -50676,6 +51260,7 @@ var init_tail = __esm({
50676
51260
  init_startup_log();
50677
51261
  init_daemon2();
50678
51262
  init_daemon();
51263
+ init_safe_text();
50679
51264
  PID_FILE = path74.join(os66.homedir(), ".node9", "daemon.pid");
50680
51265
  ICONS = {
50681
51266
  bash: "\u{1F4BB}",
@@ -51322,9 +51907,7 @@ function shellInvocation(command) {
51322
51907
  }
51323
51908
 
51324
51909
  // src/proxy/index.ts
51325
- function sanitize(value) {
51326
- return value.replace(/[\x00-\x1F\x7F]/g, "");
51327
- }
51910
+ init_safe_text();
51328
51911
  async function runProxy(targetCommand) {
51329
51912
  const commandParts = parseCommandString(targetCommand);
51330
51913
  const cmd = commandParts[0];
@@ -51360,7 +51943,7 @@ async function runProxy(targetCommand) {
51360
51943
  try {
51361
51944
  const name = message.params?.name || message.params?.tool_name || "unknown";
51362
51945
  const toolArgs = message.params?.arguments || message.params?.tool_input || {};
51363
- const result = await authorizeHeadless(sanitize(name), toolArgs, {
51946
+ const result = await authorizeHeadless(stripControlChars(name), toolArgs, {
51364
51947
  agent: "Proxy/MCP"
51365
51948
  });
51366
51949
  if (!result.approved) {
@@ -51608,17 +52191,27 @@ init_machine_id();
51608
52191
 
51609
52192
  // src/utils/open-browser.ts
51610
52193
  import { spawn as spawn4 } from "child_process";
52194
+ function isOpenableUrl(url) {
52195
+ let u;
52196
+ try {
52197
+ u = new URL(url);
52198
+ } catch {
52199
+ return false;
52200
+ }
52201
+ if (u.protocol !== "https:" && u.protocol !== "http:") return false;
52202
+ return !/[\x00-\x20\x7F"'`]/.test(url);
52203
+ }
51611
52204
  function openBrowser(url) {
52205
+ if (!isOpenableUrl(url)) return false;
51612
52206
  if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return false;
51613
52207
  if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
51614
52208
  return false;
51615
52209
  }
51616
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
52210
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
51617
52211
  try {
51618
- const child = spawn4(opener, [url], {
52212
+ const child = spawn4(cmd, args, {
51619
52213
  stdio: "ignore",
51620
- detached: true,
51621
- shell: process.platform === "win32"
52214
+ detached: true
51622
52215
  });
51623
52216
  child.on("error", () => {
51624
52217
  });
@@ -51679,6 +52272,7 @@ function postJson2(url, body, bearer) {
51679
52272
  }
51680
52273
 
51681
52274
  // src/auth/device-login.ts
52275
+ init_safe_text();
51682
52276
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
51683
52277
  async function runDeviceLogin(opts = {}) {
51684
52278
  const startUrl = resolveCloudEndpoint("/device/start", opts.apiUrl);
@@ -51694,14 +52288,16 @@ async function runDeviceLogin(opts = {}) {
51694
52288
  } catch (e) {
51695
52289
  return {
51696
52290
  ok: false,
51697
- reason: `Could not reach the node9 cloud: ${e instanceof Error ? e.message : String(e)}`
52291
+ reason: `Could not reach the node9 cloud: ${safeMessage(e)}`
51698
52292
  };
51699
52293
  }
51700
52294
  console.log("");
51701
52295
  console.log(` Open this link to approve the connection:`);
51702
- console.log(` ${chalk11.cyan.underline(start.verificationUrl)}`);
52296
+ console.log(` ${chalk11.cyan.underline(safeMessage(start.verificationUrl, 200))}`);
51703
52297
  console.log("");
51704
- console.log(` Code: ${chalk11.bold(start.userCode)} ${chalk11.gray("(match it in the browser)")}`);
52298
+ console.log(
52299
+ ` Code: ${chalk11.bold(safeMessage(start.userCode, 40))} ${chalk11.gray("(match it in the browser)")}`
52300
+ );
51705
52301
  console.log("");
51706
52302
  const opened = opts.noBrowser ? false : openBrowser(start.verificationUrl);
51707
52303
  console.log(
@@ -51747,6 +52343,7 @@ import * as fs50 from "fs";
51747
52343
  import * as os45 from "os";
51748
52344
  import * as path48 from "path";
51749
52345
  import chalk12 from "chalk";
52346
+ init_safe_text();
51750
52347
  async function revokeSelf(creds) {
51751
52348
  const url = creds.apiUrl.replace(/\/$/, "") + "/machines/self/disconnect";
51752
52349
  try {
@@ -51787,7 +52384,7 @@ function registerLogoutCommand(program2) {
51787
52384
  } else if (res.outcome === "already") {
51788
52385
  console.log(chalk12.gray("\u2713 Cloud: this machine was already disconnected."));
51789
52386
  } else {
51790
- console.log(chalk12.yellow(`\u26A0 Could not reach the cloud (${res.detail}).`));
52387
+ console.log(chalk12.yellow(`\u26A0 Could not reach the cloud (${safeMessage(res.detail)}).`));
51791
52388
  console.log(
51792
52389
  chalk12.yellow(" The key was removed locally, but is still listed in the dashboard \u2014")
51793
52390
  );
@@ -52094,9 +52691,7 @@ function discardPendingReview(key, now = Date.now()) {
52094
52691
 
52095
52692
  // src/cli/commands/check.ts
52096
52693
  init_hook_payload();
52097
- function sanitize2(value) {
52098
- return value.replace(/[\x00-\x1F\x7F]/g, "");
52099
- }
52694
+ init_safe_text();
52100
52695
  function detectAiAgent(payload) {
52101
52696
  const meta = payload.meta;
52102
52697
  if (meta && typeof meta === "object") {
@@ -52342,10 +52937,13 @@ RAW: ${raw}
52342
52937
  const logPath = path51.join(os48.homedir(), ".node9", "hook-debug.log");
52343
52938
  if (!fs53.existsSync(path51.dirname(logPath)))
52344
52939
  fs53.mkdirSync(path51.dirname(logPath), { recursive: true });
52345
- fs53.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
52346
- `);
52940
+ fs53.appendFileSync(
52941
+ logPath,
52942
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${JSON.stringify(raw)}
52943
+ `
52944
+ );
52347
52945
  }
52348
- const rawToolName = sanitize2(extractToolName(payload));
52946
+ const rawToolName = stripControlChars(extractToolName(payload));
52349
52947
  const toolName = canonicalToolName(rawToolName);
52350
52948
  const toolInput = canonicalToolInput(rawToolName, extractToolInput(payload));
52351
52949
  const agent = agentOverride ?? detectAiAgent(payload);
@@ -52735,6 +53333,7 @@ function containsShellMetachar(token) {
52735
53333
 
52736
53334
  // src/cli/commands/log.ts
52737
53335
  init_hook_payload();
53336
+ init_safe_text();
52738
53337
  var TEST_COMMAND_RE2 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
52739
53338
  function detectTestResult(command, output) {
52740
53339
  if (!TEST_COMMAND_RE2.test(command)) return null;
@@ -52753,9 +53352,6 @@ var CONFIDENCE_RANK = { low: 0, medium: 1, high: 2 };
52753
53352
  function atLeastConfidence(c, min) {
52754
53353
  return CONFIDENCE_RANK[c] >= CONFIDENCE_RANK[min];
52755
53354
  }
52756
- function sanitize3(value) {
52757
- return value.replace(/[\x00-\x1F\x7F]/g, "");
52758
- }
52759
53355
  function scanCoveredEverything(value, depth = 0) {
52760
53356
  if (value === null || value === void 0) return true;
52761
53357
  if (typeof value === "string") return value.length <= DLP_SCAN_LIMITS.maxStringBytes;
@@ -52794,7 +53390,7 @@ function registerLogCommand(program2) {
52794
53390
  if (!raw || raw.trim() === "") process.exit(0);
52795
53391
  const payload = JSON.parse(raw);
52796
53392
  if (payload.toolCall === null) process.exit(0);
52797
- const rawToolName = sanitize3(extractToolName(payload, "unknown"));
53393
+ const rawToolName = stripControlChars(extractToolName(payload, "unknown"));
52798
53394
  const tool = canonicalToolName(rawToolName);
52799
53395
  const rawInput = canonicalToolInput(rawToolName, extractToolInput(payload));
52800
53396
  const metaTag = (() => {
@@ -55626,6 +56222,7 @@ import http5 from "http";
55626
56222
  import https8 from "https";
55627
56223
  import { URL as URL5 } from "url";
55628
56224
  import chalk22 from "chalk";
56225
+ init_safe_text();
55629
56226
  function resolveConnectUrl(apiUrl) {
55630
56227
  return resolveCloudEndpoint("/cli/connect", apiUrl);
55631
56228
  }
@@ -55684,7 +56281,7 @@ function registerConnectCommand(program2) {
55684
56281
  try {
55685
56282
  resp = await postConnect(resolveConnectUrl(options.apiUrl), token);
55686
56283
  } catch (e) {
55687
- console.error(chalk22.red(`\u2717 ${e instanceof Error ? e.message : "Connect failed."}`));
56284
+ console.error(chalk22.red(`\u2717 ${safeMessage(e) || "Connect failed."}`));
55688
56285
  process.exitCode = 1;
55689
56286
  return;
55690
56287
  }
@@ -55771,9 +56368,7 @@ init_mcp_pin();
55771
56368
  init_mcp_cmd();
55772
56369
  init_mcp_tools();
55773
56370
  init_daemon();
55774
- function sanitize4(value) {
55775
- return value.replace(/[\x00-\x1F\x7F]/g, "");
55776
- }
56371
+ init_safe_text();
55777
56372
  var RPC_INVALID_REQUEST = -32600;
55778
56373
  var RPC_SERVER_ERROR = -32e3;
55779
56374
  function isValidId(id) {
@@ -55797,7 +56392,7 @@ function normalizeClientName(name) {
55797
56392
  if (lower.includes("gemini")) return "Gemini";
55798
56393
  if (lower.includes("cline")) return "Cline";
55799
56394
  if (lower.includes("continue")) return "Continue";
55800
- const sanitized = sanitize4(name).slice(0, 40);
56395
+ const sanitized = stripControlChars(name).slice(0, 40);
55801
56396
  return sanitized.length > 0 ? sanitized : void 0;
55802
56397
  }
55803
56398
  function reportPinMismatchToCloud(serverKey, serverLabel, agent) {
@@ -55977,7 +56572,7 @@ async function runMcpGateway(upstreamCommand, configName) {
55977
56572
  if (!deferredStdinEnd) agentIn.pause();
55978
56573
  authPending = true;
55979
56574
  try {
55980
- const toolName = sanitize4(
56575
+ const toolName = stripControlChars(
55981
56576
  String(message.params?.name ?? message.params?.tool_name ?? "unknown")
55982
56577
  );
55983
56578
  const toolArgs = message.params?.arguments ?? message.params?.tool_input ?? {};
@@ -61417,16 +62012,13 @@ Persistent decisions (${entries.length})
61417
62012
  }
61418
62013
 
61419
62014
  // src/cli/commands/dlp.ts
62015
+ init_safe_text();
61420
62016
  import chalk42 from "chalk";
61421
62017
  import fs77 from "fs";
61422
62018
  import path72 from "path";
61423
62019
  import os64 from "os";
61424
62020
  var AUDIT_LOG = path72.join(os64.homedir(), ".node9", "audit.log");
61425
62021
  var RESOLVED_FILE = path72.join(os64.homedir(), ".node9", "dlp-resolved.json");
61426
- var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
61427
- function stripAnsi(s) {
61428
- return s.replace(ANSI_RE, "");
61429
- }
61430
62022
  function loadResolved() {
61431
62023
  try {
61432
62024
  const raw = JSON.parse(fs77.readFileSync(RESOLVED_FILE, "utf-8"));
@@ -61520,10 +62112,10 @@ function registerDlpCommand(program2) {
61520
62112
  " " + chalk42.red("\u25CF") + " " + chalk42.white(e.dlpPattern ?? "Secret") + chalk42.dim(" " + fmtDate3(e.ts))
61521
62113
  );
61522
62114
  if (e.dlpSample) {
61523
- console.log(" " + chalk42.dim("Sample: ") + chalk42.yellow(stripAnsi(e.dlpSample)));
62115
+ console.log(" " + chalk42.dim("Sample: ") + chalk42.yellow(safeMessage(e.dlpSample)));
61524
62116
  }
61525
62117
  if (e.project) {
61526
- console.log(" " + chalk42.dim("Project: ") + chalk42.dim(stripAnsi(e.project)));
62118
+ console.log(" " + chalk42.dim("Project: ") + chalk42.dim(safeMessage(e.project)));
61527
62119
  }
61528
62120
  console.log("");
61529
62121
  }
@@ -61726,6 +62318,7 @@ function registerMaskCommand(program2) {
61726
62318
 
61727
62319
  // src/cli.ts
61728
62320
  init_blast();
62321
+ init_safe_text();
61729
62322
  var { version } = JSON.parse(
61730
62323
  fs81.readFileSync(path76.join(__dirname, "../package.json"), "utf-8")
61731
62324
  );
@@ -61758,7 +62351,7 @@ program.command("login").argument("[apiKey]", "Service/legacy key. Omit to log i
61758
62351
  cliVersion: version
61759
62352
  });
61760
62353
  if (!res.ok) {
61761
- console.error(chalk45.red(`\u2717 ${res.reason}`));
62354
+ console.error(chalk45.red(`\u2717 ${safeMessage(res.reason)}`));
61762
62355
  process.exitCode = 1;
61763
62356
  return;
61764
62357
  }