@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.js CHANGED
@@ -894,6 +894,19 @@ function parseShared(command) {
894
894
  astCache.set(command, parsed);
895
895
  return parsed;
896
896
  }
897
+ function byteOffsetToCharIndex(command) {
898
+ if (!/[^\u0000-\u007F]/.test(command)) return null;
899
+ const map = /* @__PURE__ */ new Map();
900
+ let byte = 0;
901
+ for (let i = 0; i < command.length; ) {
902
+ map.set(byte, i);
903
+ const cp = command.codePointAt(i);
904
+ byte += cp < 128 ? 1 : cp < 2048 ? 2 : cp < 65536 ? 3 : 4;
905
+ i += cp > 65535 ? 2 : 1;
906
+ }
907
+ map.set(byte, command.length);
908
+ return (b) => map.get(b) ?? -1;
909
+ }
897
910
  function cachedNormalize(command, compute) {
898
911
  const hit = normalizeCache.get(command);
899
912
  if (hit !== void 0) {
@@ -923,6 +936,8 @@ function normalizeCommandForPolicyImpl(command) {
923
936
  const f = parseShared(command);
924
937
  if (f === PARSE_FAIL) return { posix: command, separator: command };
925
938
  try {
939
+ const toCharIndex = byteOffsetToCharIndex(command);
940
+ const at = (byteOffset) => toCharIndex === null ? byteOffset : toCharIndex(byteOffset);
926
941
  const strips = [];
927
942
  const rewrites = [];
928
943
  const quoteOnlyRewrites = [];
@@ -943,8 +958,9 @@ function normalizeCommandForPolicyImpl(command) {
943
958
  const quotedNode = nextParts[0];
944
959
  const nt = syntax.NodeType(quotedNode);
945
960
  const markStrip = () => {
946
- const s = next.Pos().Offset();
947
- const e = next.End().Offset();
961
+ const s = at(next.Pos().Offset());
962
+ const e = at(next.End().Offset());
963
+ if (s < 0 || e < 0) return;
948
964
  strips.push([s, e]);
949
965
  msgSpans.add(`${s}:${e}`);
950
966
  };
@@ -961,8 +977,9 @@ function normalizeCommandForPolicyImpl(command) {
961
977
  }
962
978
  }
963
979
  for (const arg of args) {
964
- const s = arg.Pos().Offset();
965
- const e = arg.End().Offset();
980
+ const s = at(arg.Pos().Offset());
981
+ const e = at(arg.End().Offset());
982
+ if (s < 0 || e < 0) continue;
966
983
  if (msgSpans.has(`${s}:${e}`)) continue;
967
984
  const resolved = resolveWordLiteral(arg);
968
985
  if (resolved === null) continue;
@@ -1588,7 +1605,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1588
1605
  return result?.verdict !== "block";
1589
1606
  }
1590
1607
  if (nodeType !== "CallExpr") return true;
1591
- const { name, flags, paths, words } = extractLiteralArgs(n);
1608
+ const { name, flags, paths, words, args } = extractLiteralArgs(n);
1592
1609
  if (!name) return true;
1593
1610
  if (name === "rm") {
1594
1611
  const flagStr = flags.join("").toLowerCase();
@@ -1628,7 +1645,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1628
1645
  return true;
1629
1646
  }
1630
1647
  }
1631
- const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
1648
+ const readPaths = FS_READ_TOOLS.has(name) ? [...readTargets(name, args, flags, words, 1), ...flagOperandFiles(name, words, 1)] : wrappedReadPaths(words, name);
1632
1649
  if (readPaths) {
1633
1650
  for (const p of readPaths) {
1634
1651
  result = stricter(result, matchSensitivePath2(p));
@@ -1664,9 +1681,22 @@ function flagIs(w, names) {
1664
1681
  const f = flagInfo(w);
1665
1682
  return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
1666
1683
  }
1667
- function operandOf(a, names) {
1684
+ function operandOf(a, names, valueLetters) {
1668
1685
  if (!names || a.afterFlag === null) return false;
1669
- return flagIs(a.afterFlag, names) && flagInfo(a.afterFlag).attached === null;
1686
+ const w = a.afterFlag;
1687
+ if (!w.startsWith("--") && valueLetters) {
1688
+ const letters = w.slice(1);
1689
+ for (let i = 0; i < letters.length; i++) {
1690
+ if (!valueLetters.includes(letters[i])) continue;
1691
+ return i === letters.length - 1 && names.includes(letters[i]);
1692
+ }
1693
+ return false;
1694
+ }
1695
+ if (w.startsWith("--") && !w.includes("=")) {
1696
+ const longs = names.filter((n) => n.startsWith("--"));
1697
+ if (longs.some((n) => n === w || w.length >= 3 && n.startsWith(w))) return true;
1698
+ }
1699
+ return flagIs(w, names) && flagInfo(w).attached === null;
1670
1700
  }
1671
1701
  function resolveCopyShape(words, h) {
1672
1702
  const verb = baseWord(words[h]);
@@ -1690,7 +1720,7 @@ function findStartPoints(words, h) {
1690
1720
  const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
1691
1721
  if (k < 0) return { k, starts: [] };
1692
1722
  const firstPredicate = words.findIndex(
1693
- (w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
1723
+ (w, i) => i > h && w !== null && w !== "--" && w.startsWith("-") && !FIND_OPTIONS.has(w)
1694
1724
  );
1695
1725
  const end = firstPredicate > h ? firstPredicate : k;
1696
1726
  return { k, starts: positionalAfter(words, h + 1, end) };
@@ -1710,14 +1740,35 @@ function copySourcePaths(words) {
1710
1740
  const { shape, last } = r;
1711
1741
  const args = positionedArgs(words, last + 1);
1712
1742
  const tail = words.slice(last + 1);
1713
- const skipped = (a) => operandOf(a, shape.skipFlags);
1714
- const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && flagIs(w, ["t", "--target-directory"]));
1715
- const targetOperand = (a) => targetDir && operandOf(a, ["t", "--target-directory"]);
1743
+ const copyOptionsEnd = tail.findIndex((w) => w === "--");
1744
+ const copyPastOptions = (a) => copyOptionsEnd >= 0 && a.argv > last + 1 + copyOptionsEnd;
1745
+ const skipped = (a) => !copyPastOptions(a) && operandOf(a, shape.skipFlags, shape.valueLetters);
1746
+ const firstValueLetter = (w) => {
1747
+ const letters = w.slice(1);
1748
+ for (let i = 0; i < letters.length; i++) {
1749
+ if ((shape.valueLetters ?? []).includes(letters[i]))
1750
+ return { letter: letters[i], last: i === letters.length - 1 };
1751
+ }
1752
+ return null;
1753
+ };
1754
+ const isTargetDirFlag = (w) => {
1755
+ if (w.startsWith("--")) {
1756
+ const name = w.includes("=") ? w.slice(0, w.indexOf("=")) : w;
1757
+ return name.length >= 3 && "--target-directory".startsWith(name);
1758
+ }
1759
+ return firstValueLetter(w)?.letter === "t";
1760
+ };
1761
+ const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && isTargetDirFlag(w));
1762
+ const targetTakesNextWord = (w) => {
1763
+ if (w.startsWith("--"))
1764
+ return !w.includes("=") && w.length >= 3 && "--target-directory".startsWith(w);
1765
+ const f = firstValueLetter(w);
1766
+ return f !== null && f.letter === "t" && f.last;
1767
+ };
1768
+ const targetOperand = (a) => targetDir && a.afterFlag !== null && !copyPastOptions(a) && targetTakesNextWord(a.afterFlag);
1716
1769
  const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
1717
1770
  const dynamicDest = lastOperand === null;
1718
1771
  const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
1719
- if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
1720
- return [];
1721
1772
  let src;
1722
1773
  switch (shape.source) {
1723
1774
  case "all":
@@ -1727,11 +1778,17 @@ function copySourcePaths(words) {
1727
1778
  src = targetDir ? args : args.slice(0, 1);
1728
1779
  break;
1729
1780
  case "flagOperand": {
1730
- 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);
1781
+ const namesSource = (f) => {
1782
+ const names = shape.sourceFlags ?? [];
1783
+ if (f.long !== null)
1784
+ return names.some(
1785
+ (n) => n.startsWith("--") && (n === f.long || f.long.length >= 3 && n.startsWith(f.long))
1786
+ );
1787
+ return f.letter !== null && names.includes(f.letter);
1788
+ };
1789
+ const inline = tail.filter((w) => w !== null && w.startsWith("-")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && namesSource(f)).map((f) => f.attached);
1731
1790
  return [
1732
- ...args.filter(
1733
- (a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
1734
- ).map((a) => a.value),
1791
+ ...args.filter((a) => !copyPastOptions(a) && operandOf(a, shape.sourceFlags, shape.valueLetters)).map((a) => a.value),
1735
1792
  ...inline
1736
1793
  ];
1737
1794
  }
@@ -1742,7 +1799,14 @@ function copySourcePaths(words) {
1742
1799
  src = targetDir || dynamicDest ? args : args.slice(0, -1);
1743
1800
  break;
1744
1801
  }
1745
- return src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
1802
+ const sources = src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
1803
+ if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand)) {
1804
+ const jailedSources = sources.filter((p) => matchSensitivePath2(p));
1805
+ const dirOf = (p) => /[\\/]/.test(p) ? p.replace(/[\\/][^\\/]*$/, "") : "";
1806
+ if (jailedSources.length === 0) return [];
1807
+ if (jailedSources.every((p) => dirOf(p) === dirOf(lastOperand))) return [];
1808
+ }
1809
+ return sources;
1746
1810
  }
1747
1811
  function archiveInputs(kind, args, tail) {
1748
1812
  const first = args[0];
@@ -1754,13 +1818,17 @@ function archiveInputs(kind, args, tail) {
1754
1818
  const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
1755
1819
  if (extracting && !writing) return [];
1756
1820
  void mode;
1757
- let i = 0;
1758
- if (bareKey) {
1759
- i = 1;
1760
- const next = args[1];
1761
- if (first.value.includes("f") && next && next.afterFlag === null) i = 2;
1762
- }
1763
- return args.slice(i);
1821
+ if (!bareKey) return args;
1822
+ let i = 1;
1823
+ const fromDirs = [];
1824
+ for (const ch of first.value) {
1825
+ if (!TAR_VALUE_LETTERS.includes(ch)) continue;
1826
+ const operand = args[i];
1827
+ if (!operand || operand.afterFlag !== null) break;
1828
+ i += 1;
1829
+ if (ch === "C") fromDirs.push(operand);
1830
+ }
1831
+ return [...fromDirs, ...args.slice(i)];
1764
1832
  }
1765
1833
  if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
1766
1834
  if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
@@ -1787,6 +1855,138 @@ function matchSensitivePath2(p) {
1787
1855
  function baseWord(w) {
1788
1856
  return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
1789
1857
  }
1858
+ function namesFlag(name, candidates2, known) {
1859
+ if (candidates2.has(name)) return true;
1860
+ if (!name.startsWith("--") || name.length < 3) return false;
1861
+ if (known?.has(name)) return false;
1862
+ for (const c of candidates2) if (c.startsWith(name)) return true;
1863
+ return false;
1864
+ }
1865
+ function knownLongFlags(verb) {
1866
+ const out = /* @__PURE__ */ new Set();
1867
+ const shape = PATTERN_VERBS[verb];
1868
+ if (shape) {
1869
+ for (const set of [shape.takesValue, shape.noValue, shape.patternFlags, shape.noPatternFlags])
1870
+ for (const f of set) if (f.startsWith("--")) out.add(f);
1871
+ }
1872
+ for (const f of FILE_OPERAND_FLAGS[verb] ?? []) if (f.startsWith("--")) out.add(f);
1873
+ return out;
1874
+ }
1875
+ function flagNamesOf(token, shape) {
1876
+ if (token.startsWith("--")) {
1877
+ const eq = token.indexOf("=");
1878
+ return [eq > 0 ? token.slice(0, eq) : token];
1879
+ }
1880
+ const out = [];
1881
+ for (const c of token.slice(1)) {
1882
+ const name = `-${c}`;
1883
+ out.push(name);
1884
+ if (shape?.takesValue.has(name)) break;
1885
+ }
1886
+ return out;
1887
+ }
1888
+ function flagEffect(token, shape, known) {
1889
+ if (token === "--") return NONE;
1890
+ if (/^-+$/.test(token)) return UNKNOWN;
1891
+ if (/^-\d+$/.test(token)) return NONE;
1892
+ if (token.startsWith("--")) {
1893
+ if (token.includes("=")) return NONE;
1894
+ const takes = namesFlag(token, shape.takesValue, known);
1895
+ const none = namesFlag(token, shape.noValue, known);
1896
+ if (takes && none) return UNKNOWN;
1897
+ if (takes) return { kind: "takes", flag: token };
1898
+ if (none) return NONE;
1899
+ return UNKNOWN;
1900
+ }
1901
+ const letters = token.slice(1);
1902
+ if (!letters) return NONE;
1903
+ for (let i = 0; i < letters.length; i++) {
1904
+ const name = `-${letters[i]}`;
1905
+ if (shape.takesValue.has(name)) {
1906
+ return i === letters.length - 1 ? { kind: "takes", flag: name } : NONE;
1907
+ }
1908
+ if (!shape.noValue.has(name)) return UNKNOWN;
1909
+ }
1910
+ return NONE;
1911
+ }
1912
+ function readTargets(verb, args, flags, words = [], from = 1) {
1913
+ const shape = PATTERN_VERBS[verb];
1914
+ if (!shape) return args.map((a) => a.value);
1915
+ const known = knownLongFlags(verb);
1916
+ const names = flags.flatMap((f) => flagNamesOf(f, shape));
1917
+ const patternElsewhere = names.some(
1918
+ (n) => namesFlag(n, shape.patternFlags, known) || namesFlag(n, shape.noPatternFlags, known)
1919
+ );
1920
+ const excused = /* @__PURE__ */ new Set();
1921
+ const fileFlags = FILE_OPERAND_FLAGS[verb];
1922
+ const optionsEnd = words.findIndex((w, i) => i >= from && w === "--");
1923
+ const pastOptions = (a) => optionsEnd >= 0 && a.argv > optionsEnd;
1924
+ for (const a of args) {
1925
+ if (a.afterFlag === null || pastOptions(a)) continue;
1926
+ const e = flagEffect(a.afterFlag, shape, known);
1927
+ if (e.kind !== "takes") continue;
1928
+ if (fileFlags && namesFlag(e.flag, fileFlags, known)) continue;
1929
+ excused.add(a);
1930
+ }
1931
+ const patternArgv = (() => {
1932
+ for (let i = from; i < words.length; i++) {
1933
+ const w = words[i];
1934
+ if (w === null) return -1;
1935
+ if (w === "--") return i + 1;
1936
+ if (w.startsWith("-")) {
1937
+ const e = flagEffect(w, shape, known);
1938
+ if (e.kind === "takes") i += 1;
1939
+ else if (e.kind === "unknown") return -1;
1940
+ continue;
1941
+ }
1942
+ return i;
1943
+ }
1944
+ return -1;
1945
+ })();
1946
+ if (!patternElsewhere && patternArgv >= 0) {
1947
+ const a = args.find((x) => x.argv === patternArgv);
1948
+ if (a) excused.add(a);
1949
+ }
1950
+ return args.filter((a) => !excused.has(a)).map((a) => a.value);
1951
+ }
1952
+ function flagOperandFiles(verb, words, from) {
1953
+ const flags = FILE_OPERAND_FLAGS[verb];
1954
+ if (!flags) return [];
1955
+ const known = knownLongFlags(verb);
1956
+ const out = [];
1957
+ for (let i = from; i < words.length; i++) {
1958
+ const w = words[i];
1959
+ if (w === null || !w.startsWith("-") || w === "--") continue;
1960
+ if (w.startsWith("--")) {
1961
+ const eq = w.indexOf("=");
1962
+ if (eq <= 0) continue;
1963
+ const name = w.slice(0, eq);
1964
+ const value = w.slice(eq + 1);
1965
+ if (!value) continue;
1966
+ if (namesFlag(name, flags, known)) {
1967
+ out.push(value);
1968
+ continue;
1969
+ }
1970
+ const shape2 = PATTERN_VERBS[verb];
1971
+ if (!shape2) continue;
1972
+ const recognised = namesFlag(name, shape2.takesValue, known) || namesFlag(name, shape2.noValue, known) || namesFlag(name, shape2.patternFlags, known) || namesFlag(name, shape2.noPatternFlags, known);
1973
+ if (!recognised) out.push(value);
1974
+ continue;
1975
+ }
1976
+ const shape = PATTERN_VERBS[verb];
1977
+ const letters = w.slice(1);
1978
+ for (let j = 0; j < letters.length; j++) {
1979
+ const name = `-${letters[j]}`;
1980
+ const isFileFlag = flags.has(name);
1981
+ const argTaking = isFileFlag || (shape?.takesValue.has(name) ?? false) || (READER_VALUE_LETTERS[verb] ?? []).includes(letters[j]);
1982
+ if (!argTaking) continue;
1983
+ const attached = letters.slice(j + 1);
1984
+ if (isFileFlag && attached) out.push(attached);
1985
+ break;
1986
+ }
1987
+ }
1988
+ return out;
1989
+ }
1790
1990
  function wrappedReadPaths(words, name) {
1791
1991
  if (name === "find") {
1792
1992
  const { k, starts } = findStartPoints(words, 0);
@@ -1794,7 +1994,14 @@ function wrappedReadPaths(words, name) {
1794
1994
  }
1795
1995
  if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
1796
1996
  const h = unwrapCommandHead(words);
1797
- return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
1997
+ if (h <= 0 || !isReaderWord(words[h] ?? null)) return null;
1998
+ const head = baseWord(words[h]);
1999
+ const rest = words.slice(h + 1);
2000
+ const restFlags = rest.filter((w) => w !== null && w.startsWith("-"));
2001
+ return [
2002
+ ...readTargets(head, positionedArgs(words, h + 1), restFlags, words, h + 1),
2003
+ ...flagOperandFiles(head, words, h + 1)
2004
+ ];
1798
2005
  }
1799
2006
  function literalShellPayload(words, name) {
1800
2007
  const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
@@ -2727,6 +2934,17 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2727
2934
  function isIgnoredTool(toolName, config) {
2728
2935
  return matchesPattern(toolName, config.policy.ignoredTools);
2729
2936
  }
2937
+ function stripTerminalEscapes(s) {
2938
+ return s.replace(TERMINAL_ESCAPE_RE, "");
2939
+ }
2940
+ function stripControlChars(s) {
2941
+ return s.replace(CONTROL_CHAR_RE, "");
2942
+ }
2943
+ function safeMessage(value, max = 300) {
2944
+ const raw = typeof value === "string" ? value : value instanceof Error ? value.message : String(value ?? "");
2945
+ const s = stripTerminalEscapes(raw).replace(/\s+/g, " ").trim();
2946
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
2947
+ }
2730
2948
  function isShieldVerdict(v) {
2731
2949
  return v === "allow" || v === "review" || v === "block";
2732
2950
  }
@@ -2944,7 +3162,10 @@ function computeSecurityScore(opts) {
2944
3162
  }
2945
3163
  function truncateBlastPath(full) {
2946
3164
  if (!full) return "";
2947
- const cleaned = full.replace(/[/\\]+$/, "");
3165
+ if (full.length > MAX_BLAST_PATH) full = full.slice(-MAX_BLAST_PATH);
3166
+ let end = full.length;
3167
+ while (end > 0 && (full[end - 1] === "/" || full[end - 1] === "\\")) end--;
3168
+ const cleaned = full.slice(0, end);
2948
3169
  const parts = cleaned.split(/[/\\]+/).filter((p) => p.length > 0);
2949
3170
  if (parts.length <= 2) {
2950
3171
  return cleaned.startsWith("~") && !cleaned.startsWith("~/") ? cleaned : cleaned.startsWith("~/") ? cleaned : parts.join("/");
@@ -3440,7 +3661,7 @@ function toScanFinding(c) {
3440
3661
  }
3441
3662
  function previewArgs(input, max) {
3442
3663
  const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
3443
- const s = String(cmd).replace(TERMINAL_ESCAPE_RE, "").replace(/\s+/g, " ").trim();
3664
+ const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
3444
3665
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
3445
3666
  }
3446
3667
  function makeFinding(args) {
@@ -3480,7 +3701,7 @@ function* stringValues(obj, depth = 0) {
3480
3701
  }
3481
3702
  for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
3482
3703
  }
3483
- var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, 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;
3704
+ var import_safe_regex2, import_crypto3, import_mvdan_sh, import_picomatch, import_safe_regex22, import_safe_regex23, import_crypto4, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, 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;
3484
3705
  var init_dist = __esm({
3485
3706
  "packages/policy-engine/dist/index.mjs"() {
3486
3707
  "use strict";
@@ -4223,7 +4444,345 @@ var init_dist = __esm({
4223
4444
  "nl",
4224
4445
  "dd"
4225
4446
  ]);
4447
+ GREP_SHAPE = {
4448
+ takesValue: /* @__PURE__ */ new Set([
4449
+ "-A",
4450
+ "-B",
4451
+ "-C",
4452
+ "-D",
4453
+ "-d",
4454
+ "-e",
4455
+ "-f",
4456
+ "-m",
4457
+ "--after-context",
4458
+ "--before-context",
4459
+ "--binary-files",
4460
+ "--context",
4461
+ "--devices",
4462
+ "--directories",
4463
+ "--exclude",
4464
+ "--exclude-dir",
4465
+ "--exclude-from",
4466
+ "--file",
4467
+ "--include",
4468
+ "--label",
4469
+ "--max-count",
4470
+ "--regexp"
4471
+ ]),
4472
+ noValue: /* @__PURE__ */ new Set([
4473
+ "-E",
4474
+ "-F",
4475
+ "-G",
4476
+ "-P",
4477
+ "-i",
4478
+ "-y",
4479
+ "-v",
4480
+ "-V",
4481
+ "-w",
4482
+ "-x",
4483
+ "-c",
4484
+ "-l",
4485
+ "-L",
4486
+ "-o",
4487
+ "-q",
4488
+ "-s",
4489
+ "-b",
4490
+ "-H",
4491
+ "-h",
4492
+ "-n",
4493
+ "-T",
4494
+ "-Z",
4495
+ "-z",
4496
+ "-R",
4497
+ "-r",
4498
+ "-U",
4499
+ "-u",
4500
+ "-I",
4501
+ "-a",
4502
+ "--basic-regexp",
4503
+ "--binary",
4504
+ "--byte-offset",
4505
+ "--color",
4506
+ "--colour",
4507
+ "--count",
4508
+ "--dereference-recursive",
4509
+ "--extended-regexp",
4510
+ "--files-with-matches",
4511
+ "--files-without-match",
4512
+ "--fixed-strings",
4513
+ "--help",
4514
+ "--ignore-case",
4515
+ "--initial-tab",
4516
+ "--invert-match",
4517
+ "--line-buffered",
4518
+ "--line-number",
4519
+ "--line-regexp",
4520
+ "--no-filename",
4521
+ "--no-group-separator",
4522
+ "--no-ignore-case",
4523
+ "--no-messages",
4524
+ "--null",
4525
+ "--null-data",
4526
+ "--only-matching",
4527
+ "--perl-regexp",
4528
+ "--quiet",
4529
+ "--recursive",
4530
+ "--silent",
4531
+ "--text",
4532
+ "--version",
4533
+ "--with-filename",
4534
+ "--word-regexp"
4535
+ ]),
4536
+ patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
4537
+ noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file"])
4538
+ };
4539
+ PATTERN_VERBS = {
4540
+ grep: GREP_SHAPE,
4541
+ // /usr/bin/egrep and /usr/bin/fgrep are 41-byte shell wrappers that exec
4542
+ // `grep -E` and `grep -F`. Read in full, not assumed.
4543
+ egrep: GREP_SHAPE,
4544
+ fgrep: GREP_SHAPE,
4545
+ // ripgrep 14.1.1, complete from its own --help. An earlier comment here said rg
4546
+ // was not installed on the measuring machine; it is, and that stale claim is why
4547
+ // this table shipped incomplete for two rounds, blocking ordinary searches like
4548
+ // `rg --sort-files .env src` (/code-review round 4).
4549
+ rg: {
4550
+ takesValue: /* @__PURE__ */ new Set([
4551
+ "-A",
4552
+ "-B",
4553
+ "-C",
4554
+ "-d",
4555
+ "-E",
4556
+ "-e",
4557
+ "-f",
4558
+ "-g",
4559
+ "-j",
4560
+ "-M",
4561
+ "-m",
4562
+ "-r",
4563
+ "-t",
4564
+ "-T",
4565
+ "--after-context",
4566
+ "--before-context",
4567
+ "--color",
4568
+ "--colors",
4569
+ "--context",
4570
+ "--context-separator",
4571
+ "--dfa-size-limit",
4572
+ "--encoding",
4573
+ "--engine",
4574
+ "--field-context-separator",
4575
+ "--field-match-separator",
4576
+ "--file",
4577
+ "--generate",
4578
+ "--glob",
4579
+ "--hostname-bin",
4580
+ "--hyperlink-format",
4581
+ "--iglob",
4582
+ "--ignore-file",
4583
+ "--max-columns",
4584
+ "--max-count",
4585
+ "--max-depth",
4586
+ // An ALIAS of --max-depth, and it takes a value. The round-5 extraction read
4587
+ // alias lines as plain switches and swept it into noValue, which hard-blocked
4588
+ // `rg --maxdepth 2 .env src` while `--max-depth 2` ran (/code-review round 7).
4589
+ "--maxdepth",
4590
+ "--max-filesize",
4591
+ "--path-separator",
4592
+ "--pre",
4593
+ "--pre-glob",
4594
+ "--regexp",
4595
+ "--regex-size-limit",
4596
+ "--replace",
4597
+ "--sort",
4598
+ "--sortr",
4599
+ "--threads",
4600
+ "--type",
4601
+ "--type-add",
4602
+ "--type-clear",
4603
+ "--type-not"
4604
+ ]),
4605
+ noValue: /* @__PURE__ */ new Set([
4606
+ "-.",
4607
+ "-0",
4608
+ "-a",
4609
+ "-b",
4610
+ "-c",
4611
+ "-F",
4612
+ "-h",
4613
+ "-H",
4614
+ "-i",
4615
+ "-I",
4616
+ "-l",
4617
+ "-L",
4618
+ "-n",
4619
+ "-N",
4620
+ "-o",
4621
+ "-p",
4622
+ "-P",
4623
+ "-q",
4624
+ "-s",
4625
+ "-S",
4626
+ "-u",
4627
+ "-U",
4628
+ "-v",
4629
+ "-V",
4630
+ "-w",
4631
+ "-x",
4632
+ "-z",
4633
+ "--auto-hybrid-regex",
4634
+ "--binary",
4635
+ "--block-buffered",
4636
+ "--byte-offset",
4637
+ "--case-sensitive",
4638
+ "--column",
4639
+ "--count",
4640
+ "--count-matches",
4641
+ "--crlf",
4642
+ "--debug",
4643
+ "--files",
4644
+ "--files-with-matches",
4645
+ "--files-without-match",
4646
+ "--fixed-strings",
4647
+ "--follow",
4648
+ "--glob-case-insensitive",
4649
+ "--heading",
4650
+ "--help",
4651
+ "--hidden",
4652
+ "--ignore-case",
4653
+ "--ignore-file-case-insensitive",
4654
+ "--include-zero",
4655
+ "--invert-match",
4656
+ "--json",
4657
+ "--line-buffered",
4658
+ "--line-number",
4659
+ "--line-regexp",
4660
+ "--max-columns-preview",
4661
+ "--mmap",
4662
+ "--multiline",
4663
+ "--multiline-dotall",
4664
+ "--no-column",
4665
+ "--no-config",
4666
+ "--no-context-separator",
4667
+ "--no-encoding",
4668
+ "--no-filename",
4669
+ "--no-ignore",
4670
+ "--no-ignore-dot",
4671
+ "--no-ignore-exclude",
4672
+ "--no-ignore-files",
4673
+ "--no-ignore-global",
4674
+ "--no-ignore-messages",
4675
+ "--no-ignore-parent",
4676
+ "--no-ignore-vcs",
4677
+ "--no-line-number",
4678
+ "--no-messages",
4679
+ "--no-pcre2-unicode",
4680
+ "--no-pre",
4681
+ "--no-require-git",
4682
+ "--no-unicode",
4683
+ "--null",
4684
+ "--null-data",
4685
+ "--one-file-system",
4686
+ "--only-matching",
4687
+ "--passthru",
4688
+ "--pcre2",
4689
+ "--pcre2-version",
4690
+ "--pretty",
4691
+ "--print0",
4692
+ "--quiet",
4693
+ "--search-zip",
4694
+ "--smart-case",
4695
+ "--sort-files",
4696
+ "--stats",
4697
+ "--stop-on-nonmatch",
4698
+ "--text",
4699
+ "--trace",
4700
+ "--trim",
4701
+ "--type-list",
4702
+ "--unrestricted",
4703
+ "--version",
4704
+ "--vimgrep",
4705
+ "--with-filename",
4706
+ "--word-regexp",
4707
+ // The COMPLETE remainder of `rg --help`, added after /code-review round 5
4708
+ // found 40 documented switches in neither set. The table is now the whole
4709
+ // option list: `rg --help` yields 149 long options, 35 of them value-taking.
4710
+ "--ignore",
4711
+ "--ignore-dot",
4712
+ "--ignore-exclude",
4713
+ "--ignore-files",
4714
+ "--ignore-global",
4715
+ "--ignore-messages",
4716
+ "--ignore-parent",
4717
+ "--ignore-vcs",
4718
+ "--messages",
4719
+ "--no-auto-hybrid-regex",
4720
+ "--no-binary",
4721
+ "--no-block-buffered",
4722
+ "--no-byte-offset",
4723
+ "--no-crlf",
4724
+ "--no-fixed-strings",
4725
+ "--no-follow",
4726
+ "--no-glob-case-insensitive",
4727
+ "--no-heading",
4728
+ "--no-hidden",
4729
+ "--no-ignore-file-case-insensitive",
4730
+ "--no-include-zero",
4731
+ "--no-invert-match",
4732
+ "--no-json",
4733
+ "--no-line-buffered",
4734
+ "--no-max-columns-preview",
4735
+ "--no-mmap",
4736
+ "--no-multiline",
4737
+ "--no-multiline-dotall",
4738
+ "--no-one-file-system",
4739
+ "--no-pcre2",
4740
+ "--no-search-zip",
4741
+ "--no-sort-files",
4742
+ "--no-stats",
4743
+ "--no-text",
4744
+ "--no-trim",
4745
+ "--passthrough",
4746
+ "--pcre2-unicode",
4747
+ "--require-git",
4748
+ "--unicode"
4749
+ ]),
4750
+ patternFlags: /* @__PURE__ */ new Set(["-e", "--regexp"]),
4751
+ // `--files` and `--type-list` list or enumerate without a pattern. Founder
4752
+ // decision 2026-09-13: `rg --files ~/.ssh` stays BLOCKED, which this achieves
4753
+ // by leaving the directory in the judged list.
4754
+ noPatternFlags: /* @__PURE__ */ new Set(["-f", "--file", "--files", "--type-list", "--pcre2-version"])
4755
+ }
4756
+ };
4757
+ PATTERN_VERB_NAMES = Object.keys(PATTERN_VERBS);
4758
+ GREP_FILE_OPERANDS = /* @__PURE__ */ new Set(["-f", "--file", "--exclude-from", "--include"]);
4759
+ READER_VALUE_LETTERS = {
4760
+ awk: ["F", "v", "f"],
4761
+ gawk: ["F", "v", "f", "e", "E", "i", "l", "o", "p", "D"],
4762
+ sed: ["e", "f", "i", "l"],
4763
+ sort: ["C", "k", "o", "S", "t", "T"]
4764
+ };
4765
+ FILE_OPERAND_FLAGS = {
4766
+ grep: GREP_FILE_OPERANDS,
4767
+ egrep: GREP_FILE_OPERANDS,
4768
+ fgrep: GREP_FILE_OPERANDS,
4769
+ // rg and gawk are NOT installed on the measuring machine: these two rows are
4770
+ // from the shipped documentation (ripgrep `-f/--file`, `--ignore-file`; gawk
4771
+ // `-f/--file`) and are marked as such in the spec.
4772
+ // `-g/--glob/--iglob` name which files ripgrep SEARCHES, so an operand naming
4773
+ // a credential makes rg open it -- the same reason grep's `--include` is here.
4774
+ // Measured (/code-review round 3): `rg --hidden -g .env AWS tree` opened .env
4775
+ // and printed its contents.
4776
+ rg: /* @__PURE__ */ new Set(["-f", "--file", "--ignore-file", "-g", "--glob", "--iglob"]),
4777
+ sed: /* @__PURE__ */ new Set(["-f", "--file"]),
4778
+ awk: /* @__PURE__ */ new Set(["-f", "--file"]),
4779
+ gawk: /* @__PURE__ */ new Set(["-f", "--file"]),
4780
+ sort: /* @__PURE__ */ new Set(["--files0-from"])
4781
+ };
4226
4782
  SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
4783
+ TAR_VALUE_LETTERS = ["b", "C", "f", "F", "g", "H", "I", "K", "L", "N", "T", "V", "X"];
4784
+ ZIP_VALUE_LETTERS = ["b", "n", "P", "t", "s", "O", "x", "i"];
4785
+ RSYNC_VALUE_LETTERS = ["e", "f", "T", "B", "M"];
4227
4786
  RSYNC_SKIP = [
4228
4787
  "e",
4229
4788
  "--rsh",
@@ -4235,21 +4794,35 @@ var init_dist = __esm({
4235
4794
  "f",
4236
4795
  "--filter"
4237
4796
  ];
4797
+ CP_VALUE_LETTERS = ["S", "t"];
4798
+ INSTALL_VALUE_LETTERS = ["S", "t", "g", "m", "o"];
4238
4799
  COPY_VERBS = {
4239
- cp: { source: "allButLast", targetDirFlag: true },
4240
- mv: { source: "allButLast", targetDirFlag: true },
4241
- install: { source: "allButLast", targetDirFlag: true },
4242
- ln: { source: "first", targetDirFlag: true },
4243
- scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
4244
- rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
4800
+ cp: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4801
+ mv: { source: "allButLast", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4802
+ install: { source: "allButLast", targetDirFlag: true, valueLetters: INSTALL_VALUE_LETTERS },
4803
+ ln: { source: "first", targetDirFlag: true, valueLetters: CP_VALUE_LETTERS },
4804
+ scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS, valueLetters: SCP_VALUE_FLAGS },
4805
+ rsync: { source: "allButLast", skipFlags: RSYNC_SKIP, valueLetters: RSYNC_VALUE_LETTERS },
4245
4806
  tar: {
4246
4807
  source: "archive",
4247
4808
  archive: "tar",
4248
- skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
4809
+ skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"],
4810
+ valueLetters: TAR_VALUE_LETTERS
4811
+ },
4812
+ zip: {
4813
+ source: "archive",
4814
+ archive: "zip",
4815
+ skipFlags: ["x", "i", "--exclude", "--include"],
4816
+ valueLetters: ZIP_VALUE_LETTERS
4249
4817
  },
4250
- zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
4251
4818
  ar: { source: "archive", archive: "ar" },
4252
- "7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
4819
+ // 7z switches are INLINE only (`-mx9`, `-px`, `-x!pat`), so no following word is
4820
+ // ever a switch's operand -- which is why the short `x` is NOT a skipFlag here
4821
+ // and valueLetters is empty. Keeping the short `x` made `7z a out.7z -mx KEY`
4822
+ // drop the credential as an exclusion operand (/code-review round 8), and the
4823
+ // derived "every skipped letter is a value letter" row in jail-copy.spec.ts is
4824
+ // what keeps the two tables honest about it.
4825
+ "7z": { source: "archive", archive: "7z", skipFlags: ["--exclude"], valueLetters: [] },
4253
4826
  gzip: { source: "all" },
4254
4827
  bzip2: { source: "all" },
4255
4828
  xz: { source: "all" },
@@ -4571,6 +5144,8 @@ var init_dist = __esm({
4571
5144
  };
4572
5145
  isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
4573
5146
  positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
5147
+ NONE = { kind: "none" };
5148
+ UNKNOWN = { kind: "unknown" };
4574
5149
  DEFAULT_EGRESS_ALLOWLIST = [
4575
5150
  // node9's own control plane (api, app, dev-api, staging and the apex).
4576
5151
  // Without it, turning egress on asks the user to approve node9 itself.
@@ -4760,6 +5335,8 @@ var init_dist = __esm({
4760
5335
  block: 2
4761
5336
  };
4762
5337
  SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
5338
+ TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
5339
+ CONTROL_CHAR_RE = /[\x00-\x1F\x7F]/g;
4763
5340
  aws_default = {
4764
5341
  name: "aws",
4765
5342
  description: "Protects AWS infrastructure from destructive AI operations",
@@ -5537,6 +6114,7 @@ var init_dist = __esm({
5537
6114
  longOutputRedactions: 1
5538
6115
  };
5539
6116
  LOOP_THRESHOLD_FOR_WASTE = 3;
6117
+ MAX_BLAST_PATH = 4096;
5540
6118
  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;
5541
6119
  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;
5542
6120
  FILE_TOOLS = /* @__PURE__ */ new Set([
@@ -5615,10 +6193,8 @@ var init_dist = __esm({
5615
6193
  { view: "separators-stripped", decoder: "separators", stripped: true }
5616
6194
  ];
5617
6195
  LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
5618
- CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
6196
+ CANONICAL_EXTRACTOR_VERSION = "canonical-v15";
5619
6197
  DEDUPE_PREVIEW_LEN = 120;
5620
- TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
5621
- /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
5622
6198
  ENGINE_VERSION = "1.4.0";
5623
6199
  }
5624
6200
  });
@@ -8167,6 +8743,14 @@ var init_context_sniper = __esm({
8167
8743
  }
8168
8744
  });
8169
8745
 
8746
+ // src/utils/safe-text.ts
8747
+ var init_safe_text = __esm({
8748
+ "src/utils/safe-text.ts"() {
8749
+ "use strict";
8750
+ init_dist();
8751
+ }
8752
+ });
8753
+
8170
8754
  // src/ui/native.ts
8171
8755
  function resolveNativeDecision(opts) {
8172
8756
  const { code, output, elapsedMs, locked } = opts;
@@ -8276,7 +8860,7 @@ function escapePango(text) {
8276
8860
  function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
8277
8861
  const lines = [];
8278
8862
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
8279
- const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
8863
+ const safeAgent = safeMessage(agent ?? "AI Agent", 80);
8280
8864
  lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
8281
8865
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
8282
8866
  if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
@@ -8413,6 +8997,7 @@ var init_native = __esm({
8413
8997
  import_child_process = require("child_process");
8414
8998
  import_path11 = __toESM(require("path"));
8415
8999
  init_context_sniper();
9000
+ init_safe_text();
8416
9001
  isTestEnv = () => {
8417
9002
  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";
8418
9003
  };
@@ -8799,14 +9384,14 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
8799
9384
  if (!res.ok) {
8800
9385
  import_fs12.default.appendFileSync(
8801
9386
  HOOK_DEBUG_LOG,
8802
- `[resolve-cloud] PATCH ${resolveUrl} \u2192 HTTP ${res.status}
9387
+ `[resolve-cloud] PATCH ${safeMessage(resolveUrl, 200)} \u2192 HTTP ${res.status}
8803
9388
  `
8804
9389
  );
8805
9390
  }
8806
9391
  } catch (err2) {
8807
9392
  import_fs12.default.appendFileSync(
8808
9393
  HOOK_DEBUG_LOG,
8809
- `[resolve-cloud] PATCH failed for ${requestId}: ${err2.message}
9394
+ `[resolve-cloud] PATCH failed for ${safeMessage(requestId, 64)}: ${safeMessage(err2)}
8810
9395
  `
8811
9396
  );
8812
9397
  }
@@ -8819,6 +9404,7 @@ var init_cloud = __esm({
8819
9404
  import_os11 = __toESM(require("os"));
8820
9405
  import_path14 = __toESM(require("path"));
8821
9406
  init_audit();
9407
+ init_safe_text();
8822
9408
  DLP_SAMPLE_MAX_LEN = 200;
8823
9409
  DLP_PATTERN_MAX_LEN = 100;
8824
9410
  KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
@@ -8951,9 +9537,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
8951
9537
  if (!options?.calledFromDaemon) {
8952
9538
  const actId = (0, import_crypto7.randomUUID)();
8953
9539
  const actTs = Date.now();
8954
- const stripAnsi2 = (s) => s.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "");
8955
- const sanitizedAgent = meta?.agent ? stripAnsi2(meta.agent).slice(0, 80) : void 0;
8956
- const sanitizedMcpServer = meta?.mcpServer ? stripAnsi2(meta.mcpServer).slice(0, 40) : void 0;
9540
+ const sanitizedAgent = meta?.agent ? safeMessage(meta.agent, 80) : void 0;
9541
+ const sanitizedMcpServer = meta?.mcpServer ? safeMessage(meta.mcpServer, 40) : void 0;
8957
9542
  const socketOk = await notifyActivity({
8958
9543
  id: actId,
8959
9544
  ts: actTs,
@@ -9829,6 +10414,7 @@ var init_orchestrator = __esm({
9829
10414
  init_loop_detector();
9830
10415
  init_shields();
9831
10416
  init_jail();
10417
+ init_safe_text();
9832
10418
  WRITE_TOOLS = /* @__PURE__ */ new Set([
9833
10419
  "write",
9834
10420
  "write_file",
@@ -14434,7 +15020,7 @@ async function postCostBatches(apiUrl, apiKey, machineId, entries) {
14434
15020
  }
14435
15021
  }
14436
15022
  } catch (err2) {
14437
- import_fs24.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${err2.message}
15023
+ import_fs24.default.appendFileSync(HOOK_DEBUG_LOG, `[cost-sync] ${safeMessage(err2)}
14438
15024
  `);
14439
15025
  }
14440
15026
  }
@@ -14476,6 +15062,7 @@ var init_costSync = __esm({
14476
15062
  init_cost_gemini();
14477
15063
  init_cost_copilot();
14478
15064
  init_session_files();
15065
+ init_safe_text();
14479
15066
  SYNC_INTERVAL_MS = 10 * 60 * 1e3;
14480
15067
  claudeSource = {
14481
15068
  id: "claude",
@@ -15306,9 +15893,6 @@ function fmtTs(ts) {
15306
15893
  return ts.slice(0, 10);
15307
15894
  }
15308
15895
  }
15309
- function stripTerminalEscapes(s) {
15310
- return s.replace(TERMINAL_ESCAPE_RE2, "");
15311
- }
15312
15896
  function preview(input, max) {
15313
15897
  const cmd = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
15314
15898
  const s = stripTerminalEscapes(String(cmd)).replace(/\s+/g, " ").trim();
@@ -18023,7 +18607,7 @@ function registerScanCommand(program2) {
18023
18607
  }
18024
18608
  );
18025
18609
  }
18026
- var import_chalk6, import_fs27, import_path29, import_os26, import_string_width2, 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;
18610
+ var import_chalk6, import_fs27, import_path29, import_os26, import_string_width2, 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;
18027
18611
  var init_scan = __esm({
18028
18612
  "src/cli/commands/scan.ts"() {
18029
18613
  "use strict";
@@ -18054,6 +18638,7 @@ var init_scan = __esm({
18054
18638
  init_scan_json();
18055
18639
  init_session_files();
18056
18640
  init_scan_history();
18641
+ init_safe_text();
18057
18642
  toolInspectionMap = DEFAULT_CONFIG.policy.toolInspection;
18058
18643
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
18059
18644
  ".ts",
@@ -18088,7 +18673,6 @@ var init_scan = __esm({
18088
18673
  /\bseverity:\s*['"](?:block|review|allow)['"]/,
18089
18674
  /NODE9 SECURITY ALERT/
18090
18675
  ];
18091
- TERMINAL_ESCAPE_RE2 = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
18092
18676
  LOOP_TOOLS = /* @__PURE__ */ new Set([
18093
18677
  "bash",
18094
18678
  "execute_bash",
@@ -18574,7 +19158,7 @@ function atomicWriteSync2(filePath, data, options) {
18574
19158
  function redactArgs(value) {
18575
19159
  if (!value || typeof value !== "object") return value;
18576
19160
  if (Array.isArray(value)) return value.map(redactArgs);
18577
- const result = {};
19161
+ const result = /* @__PURE__ */ Object.create(null);
18578
19162
  for (const [k, v] of Object.entries(value)) {
18579
19163
  result[k] = SECRET_KEY_RE.test(k) ? "[REDACTED]" : redactArgs(v);
18580
19164
  }
@@ -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
  }
@@ -50666,7 +51250,7 @@ async function startTail(options = {}) {
50666
51250
  req.on("error", (err2) => {
50667
51251
  const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
50668
51252
  console.error(import_chalk44.default.red(`
50669
- \u274C ${msg}`));
51253
+ \u274C ${safeMessage(msg)}`));
50670
51254
  process.exit(1);
50671
51255
  });
50672
51256
  }
@@ -50684,6 +51268,7 @@ var init_tail = __esm({
50684
51268
  init_startup_log();
50685
51269
  init_daemon2();
50686
51270
  init_daemon();
51271
+ init_safe_text();
50687
51272
  PID_FILE = import_path70.default.join(import_os62.default.homedir(), ".node9", "daemon.pid");
50688
51273
  ICONS = {
50689
51274
  bash: "\u{1F4BB}",
@@ -51330,9 +51915,7 @@ function shellInvocation(command) {
51330
51915
  }
51331
51916
 
51332
51917
  // src/proxy/index.ts
51333
- function sanitize(value) {
51334
- return value.replace(/[\x00-\x1F\x7F]/g, "");
51335
- }
51918
+ init_safe_text();
51336
51919
  async function runProxy(targetCommand) {
51337
51920
  const commandParts = (0, import_execa2.parseCommandString)(targetCommand);
51338
51921
  const cmd = commandParts[0];
@@ -51368,7 +51951,7 @@ async function runProxy(targetCommand) {
51368
51951
  try {
51369
51952
  const name = message.params?.name || message.params?.tool_name || "unknown";
51370
51953
  const toolArgs = message.params?.arguments || message.params?.tool_input || {};
51371
- const result = await authorizeHeadless(sanitize(name), toolArgs, {
51954
+ const result = await authorizeHeadless(stripControlChars(name), toolArgs, {
51372
51955
  agent: "Proxy/MCP"
51373
51956
  });
51374
51957
  if (!result.approved) {
@@ -51616,17 +52199,27 @@ init_machine_id();
51616
52199
 
51617
52200
  // src/utils/open-browser.ts
51618
52201
  var import_child_process6 = require("child_process");
52202
+ function isOpenableUrl(url) {
52203
+ let u;
52204
+ try {
52205
+ u = new URL(url);
52206
+ } catch {
52207
+ return false;
52208
+ }
52209
+ if (u.protocol !== "https:" && u.protocol !== "http:") return false;
52210
+ return !/[\x00-\x20\x7F"'`]/.test(url);
52211
+ }
51619
52212
  function openBrowser(url) {
52213
+ if (!isOpenableUrl(url)) return false;
51620
52214
  if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return false;
51621
52215
  if (process.platform === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
51622
52216
  return false;
51623
52217
  }
51624
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
52218
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
51625
52219
  try {
51626
- const child = (0, import_child_process6.spawn)(opener, [url], {
52220
+ const child = (0, import_child_process6.spawn)(cmd, args, {
51627
52221
  stdio: "ignore",
51628
- detached: true,
51629
- shell: process.platform === "win32"
52222
+ detached: true
51630
52223
  });
51631
52224
  child.on("error", () => {
51632
52225
  });
@@ -51687,6 +52280,7 @@ function postJson2(url, body, bearer) {
51687
52280
  }
51688
52281
 
51689
52282
  // src/auth/device-login.ts
52283
+ init_safe_text();
51690
52284
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
51691
52285
  async function runDeviceLogin(opts = {}) {
51692
52286
  const startUrl = resolveCloudEndpoint("/device/start", opts.apiUrl);
@@ -51702,14 +52296,16 @@ async function runDeviceLogin(opts = {}) {
51702
52296
  } catch (e) {
51703
52297
  return {
51704
52298
  ok: false,
51705
- reason: `Could not reach the node9 cloud: ${e instanceof Error ? e.message : String(e)}`
52299
+ reason: `Could not reach the node9 cloud: ${safeMessage(e)}`
51706
52300
  };
51707
52301
  }
51708
52302
  console.log("");
51709
52303
  console.log(` Open this link to approve the connection:`);
51710
- console.log(` ${import_chalk11.default.cyan.underline(start.verificationUrl)}`);
52304
+ console.log(` ${import_chalk11.default.cyan.underline(safeMessage(start.verificationUrl, 200))}`);
51711
52305
  console.log("");
51712
- console.log(` Code: ${import_chalk11.default.bold(start.userCode)} ${import_chalk11.default.gray("(match it in the browser)")}`);
52306
+ console.log(
52307
+ ` Code: ${import_chalk11.default.bold(safeMessage(start.userCode, 40))} ${import_chalk11.default.gray("(match it in the browser)")}`
52308
+ );
51713
52309
  console.log("");
51714
52310
  const opened = opts.noBrowser ? false : openBrowser(start.verificationUrl);
51715
52311
  console.log(
@@ -51755,6 +52351,7 @@ var fs50 = __toESM(require("fs"));
51755
52351
  var os45 = __toESM(require("os"));
51756
52352
  var path48 = __toESM(require("path"));
51757
52353
  var import_chalk12 = __toESM(require("chalk"));
52354
+ init_safe_text();
51758
52355
  async function revokeSelf(creds) {
51759
52356
  const url = creds.apiUrl.replace(/\/$/, "") + "/machines/self/disconnect";
51760
52357
  try {
@@ -51795,7 +52392,7 @@ function registerLogoutCommand(program2) {
51795
52392
  } else if (res.outcome === "already") {
51796
52393
  console.log(import_chalk12.default.gray("\u2713 Cloud: this machine was already disconnected."));
51797
52394
  } else {
51798
- console.log(import_chalk12.default.yellow(`\u26A0 Could not reach the cloud (${res.detail}).`));
52395
+ console.log(import_chalk12.default.yellow(`\u26A0 Could not reach the cloud (${safeMessage(res.detail)}).`));
51799
52396
  console.log(
51800
52397
  import_chalk12.default.yellow(" The key was removed locally, but is still listed in the dashboard \u2014")
51801
52398
  );
@@ -52102,9 +52699,7 @@ function discardPendingReview(key, now = Date.now()) {
52102
52699
 
52103
52700
  // src/cli/commands/check.ts
52104
52701
  init_hook_payload();
52105
- function sanitize2(value) {
52106
- return value.replace(/[\x00-\x1F\x7F]/g, "");
52107
- }
52702
+ init_safe_text();
52108
52703
  function detectAiAgent(payload) {
52109
52704
  const meta = payload.meta;
52110
52705
  if (meta && typeof meta === "object") {
@@ -52350,10 +52945,13 @@ RAW: ${raw}
52350
52945
  const logPath = import_path47.default.join(import_os44.default.homedir(), ".node9", "hook-debug.log");
52351
52946
  if (!import_fs49.default.existsSync(import_path47.default.dirname(logPath)))
52352
52947
  import_fs49.default.mkdirSync(import_path47.default.dirname(logPath), { recursive: true });
52353
- import_fs49.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
52354
- `);
52948
+ import_fs49.default.appendFileSync(
52949
+ logPath,
52950
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${JSON.stringify(raw)}
52951
+ `
52952
+ );
52355
52953
  }
52356
- const rawToolName = sanitize2(extractToolName(payload));
52954
+ const rawToolName = stripControlChars(extractToolName(payload));
52357
52955
  const toolName = canonicalToolName(rawToolName);
52358
52956
  const toolInput = canonicalToolInput(rawToolName, extractToolInput(payload));
52359
52957
  const agent = agentOverride ?? detectAiAgent(payload);
@@ -52743,6 +53341,7 @@ function containsShellMetachar(token) {
52743
53341
 
52744
53342
  // src/cli/commands/log.ts
52745
53343
  init_hook_payload();
53344
+ init_safe_text();
52746
53345
  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;
52747
53346
  function detectTestResult(command, output) {
52748
53347
  if (!TEST_COMMAND_RE2.test(command)) return null;
@@ -52761,9 +53360,6 @@ var CONFIDENCE_RANK = { low: 0, medium: 1, high: 2 };
52761
53360
  function atLeastConfidence(c, min) {
52762
53361
  return CONFIDENCE_RANK[c] >= CONFIDENCE_RANK[min];
52763
53362
  }
52764
- function sanitize3(value) {
52765
- return value.replace(/[\x00-\x1F\x7F]/g, "");
52766
- }
52767
53363
  function scanCoveredEverything(value, depth = 0) {
52768
53364
  if (value === null || value === void 0) return true;
52769
53365
  if (typeof value === "string") return value.length <= DLP_SCAN_LIMITS.maxStringBytes;
@@ -52802,7 +53398,7 @@ function registerLogCommand(program2) {
52802
53398
  if (!raw || raw.trim() === "") process.exit(0);
52803
53399
  const payload = JSON.parse(raw);
52804
53400
  if (payload.toolCall === null) process.exit(0);
52805
- const rawToolName = sanitize3(extractToolName(payload, "unknown"));
53401
+ const rawToolName = stripControlChars(extractToolName(payload, "unknown"));
52806
53402
  const tool = canonicalToolName(rawToolName);
52807
53403
  const rawInput = canonicalToolInput(rawToolName, extractToolInput(payload));
52808
53404
  const metaTag = (() => {
@@ -55634,6 +56230,7 @@ var import_http4 = __toESM(require("http"));
55634
56230
  var import_https7 = __toESM(require("https"));
55635
56231
  var import_url4 = require("url");
55636
56232
  var import_chalk22 = __toESM(require("chalk"));
56233
+ init_safe_text();
55637
56234
  function resolveConnectUrl(apiUrl) {
55638
56235
  return resolveCloudEndpoint("/cli/connect", apiUrl);
55639
56236
  }
@@ -55692,7 +56289,7 @@ function registerConnectCommand(program2) {
55692
56289
  try {
55693
56290
  resp = await postConnect(resolveConnectUrl(options.apiUrl), token);
55694
56291
  } catch (e) {
55695
- console.error(import_chalk22.default.red(`\u2717 ${e instanceof Error ? e.message : "Connect failed."}`));
56292
+ console.error(import_chalk22.default.red(`\u2717 ${safeMessage(e) || "Connect failed."}`));
55696
56293
  process.exitCode = 1;
55697
56294
  return;
55698
56295
  }
@@ -55779,9 +56376,7 @@ init_mcp_pin();
55779
56376
  init_mcp_cmd();
55780
56377
  init_mcp_tools();
55781
56378
  init_daemon();
55782
- function sanitize4(value) {
55783
- return value.replace(/[\x00-\x1F\x7F]/g, "");
55784
- }
56379
+ init_safe_text();
55785
56380
  var RPC_INVALID_REQUEST = -32600;
55786
56381
  var RPC_SERVER_ERROR = -32e3;
55787
56382
  function isValidId(id) {
@@ -55805,7 +56400,7 @@ function normalizeClientName(name) {
55805
56400
  if (lower.includes("gemini")) return "Gemini";
55806
56401
  if (lower.includes("cline")) return "Cline";
55807
56402
  if (lower.includes("continue")) return "Continue";
55808
- const sanitized = sanitize4(name).slice(0, 40);
56403
+ const sanitized = stripControlChars(name).slice(0, 40);
55809
56404
  return sanitized.length > 0 ? sanitized : void 0;
55810
56405
  }
55811
56406
  function reportPinMismatchToCloud(serverKey, serverLabel, agent) {
@@ -55985,7 +56580,7 @@ async function runMcpGateway(upstreamCommand, configName) {
55985
56580
  if (!deferredStdinEnd) agentIn.pause();
55986
56581
  authPending = true;
55987
56582
  try {
55988
- const toolName = sanitize4(
56583
+ const toolName = stripControlChars(
55989
56584
  String(message.params?.name ?? message.params?.tool_name ?? "unknown")
55990
56585
  );
55991
56586
  const toolArgs = message.params?.arguments ?? message.params?.tool_input ?? {};
@@ -61429,12 +62024,9 @@ var import_chalk42 = __toESM(require("chalk"));
61429
62024
  var import_fs73 = __toESM(require("fs"));
61430
62025
  var import_path68 = __toESM(require("path"));
61431
62026
  var import_os60 = __toESM(require("os"));
62027
+ init_safe_text();
61432
62028
  var AUDIT_LOG = import_path68.default.join(import_os60.default.homedir(), ".node9", "audit.log");
61433
62029
  var RESOLVED_FILE = import_path68.default.join(import_os60.default.homedir(), ".node9", "dlp-resolved.json");
61434
- var ANSI_RE = /\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g;
61435
- function stripAnsi(s) {
61436
- return s.replace(ANSI_RE, "");
61437
- }
61438
62030
  function loadResolved() {
61439
62031
  try {
61440
62032
  const raw = JSON.parse(import_fs73.default.readFileSync(RESOLVED_FILE, "utf-8"));
@@ -61528,10 +62120,10 @@ function registerDlpCommand(program2) {
61528
62120
  " " + import_chalk42.default.red("\u25CF") + " " + import_chalk42.default.white(e.dlpPattern ?? "Secret") + import_chalk42.default.dim(" " + fmtDate3(e.ts))
61529
62121
  );
61530
62122
  if (e.dlpSample) {
61531
- console.log(" " + import_chalk42.default.dim("Sample: ") + import_chalk42.default.yellow(stripAnsi(e.dlpSample)));
62123
+ console.log(" " + import_chalk42.default.dim("Sample: ") + import_chalk42.default.yellow(safeMessage(e.dlpSample)));
61532
62124
  }
61533
62125
  if (e.project) {
61534
- console.log(" " + import_chalk42.default.dim("Project: ") + import_chalk42.default.dim(stripAnsi(e.project)));
62126
+ console.log(" " + import_chalk42.default.dim("Project: ") + import_chalk42.default.dim(safeMessage(e.project)));
61535
62127
  }
61536
62128
  console.log("");
61537
62129
  }
@@ -61734,6 +62326,7 @@ function registerMaskCommand(program2) {
61734
62326
 
61735
62327
  // src/cli.ts
61736
62328
  init_blast();
62329
+ init_safe_text();
61737
62330
  var { version } = JSON.parse(
61738
62331
  import_fs77.default.readFileSync(import_path72.default.join(__dirname, "../package.json"), "utf-8")
61739
62332
  );
@@ -61766,7 +62359,7 @@ program.command("login").argument("[apiKey]", "Service/legacy key. Omit to log i
61766
62359
  cliVersion: version
61767
62360
  });
61768
62361
  if (!res.ok) {
61769
- console.error(import_chalk45.default.red(`\u2717 ${res.reason}`));
62362
+ console.error(import_chalk45.default.red(`\u2717 ${safeMessage(res.reason)}`));
61770
62363
  process.exitCode = 1;
61771
62364
  return;
61772
62365
  }