@node9/proxy 2.32.0 → 2.33.0

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.
@@ -1662,32 +1662,102 @@ function evaluateSmartConditions(args, rule) {
1662
1662
  function isBashTool(tool) {
1663
1663
  return BASH_TOOL_NAMES.has(tool.toLowerCase());
1664
1664
  }
1665
- function inspectionEntry(tool, inspection = {}) {
1665
+ function isShellEntry(value) {
1666
+ return typeof value === "object" && value !== null && value.kind === "shell" && typeof value.field === "string" && Object.keys(value).length === 2;
1667
+ }
1668
+ function isInspectionLayers(input) {
1669
+ const trusted = input.trusted;
1670
+ return typeof trusted === "object" && trusted !== null && !isShellEntry(trusted);
1671
+ }
1672
+ function layersOf(input) {
1673
+ return isInspectionLayers(input) ? { trusted: input.trusted, project: input.project ?? {} } : { trusted: input, project: {} };
1674
+ }
1675
+ function entryOf(tool, inspection) {
1666
1676
  const pattern = Object.keys(inspection).find((p) => matchesPattern(tool, p));
1667
1677
  return pattern === void 0 ? void 0 : inspection[pattern];
1668
1678
  }
1679
+ function executorName(tool) {
1680
+ const name = tool.toLowerCase();
1681
+ if (!name.startsWith("mcp__")) return name;
1682
+ const at = name.lastIndexOf("__");
1683
+ return at >= "mcp__".length ? name.slice(at + 2) : name;
1684
+ }
1685
+ function entryFields(entry) {
1686
+ if (typeof entry === "object") return [entry.field];
1687
+ return entry === "command" ? ["command"] : null;
1688
+ }
1669
1689
  function shellFields(tool, inspection) {
1670
1690
  if (isBashTool(tool)) return ["command"];
1671
- const entry = inspectionEntry(tool, inspection);
1672
- if (typeof entry === "object") return [entry.field];
1673
- if (entry !== void 0) return entry === "command" ? [entry] : null;
1674
- const name = tool.toLowerCase().replace(/^mcp__[a-z0-9_-]+?__/, "");
1675
- return MCP_EXECUTORS.has(name) ? ["command", "cmd", "script"] : null;
1691
+ const { trusted, project } = layersOf(inspection);
1692
+ const trustedEntry = entryOf(tool, trusted);
1693
+ if (trustedEntry !== void 0) return entryFields(trustedEntry);
1694
+ const builtin = MCP_EXECUTORS.has(executorName(tool)) ? BUILTIN_FIELDS : null;
1695
+ const projectEntry = entryOf(tool, project);
1696
+ if (projectEntry === void 0) return builtin;
1697
+ const added = entryFields(projectEntry);
1698
+ if (!builtin) return added;
1699
+ if (!added) return builtin;
1700
+ return [.../* @__PURE__ */ new Set([...builtin, ...added])];
1676
1701
  }
1677
1702
  function isShellShapedTool(tool, inspection = {}) {
1678
1703
  return shellFields(tool, inspection) !== null;
1679
1704
  }
1705
+ function shellWord(word) {
1706
+ if (BARE_WORD.test(word)) return word;
1707
+ return word === "" ? "''" : `'${word.replace(/'/g, `'\\''`)}'`;
1708
+ }
1709
+ function isStringArray(value) {
1710
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
1711
+ }
1712
+ function commandLine(args, fieldPath, value, native) {
1713
+ const missing = { reason: "Shell command field is missing or is not a string" };
1714
+ const unreadable = { reason: "Shell command arguments could not be read" };
1715
+ if (typeof value !== "string" && (native || !Array.isArray(value))) return missing;
1716
+ const dot = fieldPath.lastIndexOf(".");
1717
+ const parent = dot === -1 ? args : getNestedValue(args, fieldPath.slice(0, dot));
1718
+ const siblings = ARGV_FIELDS.map((key) => ({
1719
+ key,
1720
+ value: parent && typeof parent === "object" ? parent[key] : void 0
1721
+ })).filter(({ value: value2 }) => value2 !== void 0 && value2 !== null);
1722
+ let argv = siblings[0];
1723
+ if (native) argv = siblings.find(({ value: v }) => isStringArray(v));
1724
+ else if (siblings.length === 2 && JSON.stringify(siblings[0].value) !== JSON.stringify(siblings[1].value))
1725
+ return { reason: "Shell command arguments disagree" };
1726
+ let command;
1727
+ let source = fieldPath;
1728
+ if (Array.isArray(value)) {
1729
+ if (argv || !value.length || !isStringArray(value)) return unreadable;
1730
+ command = value.map(shellWord).join(" ");
1731
+ } else if (!argv) {
1732
+ return { command: value, fieldPath };
1733
+ } else {
1734
+ if (!isStringArray(argv.value)) return unreadable;
1735
+ if (!argv.value.length) return { command: value, fieldPath };
1736
+ command = `${value} ${argv.value.map(shellWord).join(" ")}`;
1737
+ source = `${fieldPath}+${argv.key}`;
1738
+ }
1739
+ if (command.length > MAX_ARGV_CHARACTERS)
1740
+ return { reason: "Shell command arguments exceed the inspectable size" };
1741
+ return { command, fieldPath: source };
1742
+ }
1680
1743
  function resolveShellInput(tool, args, inspection = {}) {
1681
1744
  const fields = shellFields(tool, inspection);
1682
1745
  if (!fields) return { kind: "not-shell" };
1683
1746
  const values = fields.map((fieldPath) => ({ fieldPath, value: getNestedValue(args, fieldPath) })).filter(({ value }) => value !== void 0 && value !== null && value !== "");
1684
- if (!values.length || values.some(({ value }) => typeof value !== "string")) {
1747
+ if (!values.length) {
1685
1748
  return { kind: "uninspectable", reason: "Shell command field is missing or is not a string" };
1686
1749
  }
1687
- if (new Set(values.map(({ value }) => value)).size !== 1) {
1750
+ const native = isBashTool(tool);
1751
+ const lines = values.map(({ fieldPath, value }) => commandLine(args, fieldPath, value, native));
1752
+ const failed = lines.find((line) => "reason" in line);
1753
+ if (failed) return { kind: "uninspectable", reason: failed.reason };
1754
+ const commands = lines.filter(
1755
+ (line) => "command" in line
1756
+ );
1757
+ if (new Set(commands.map(({ command }) => command)).size !== 1) {
1688
1758
  return { kind: "uninspectable", reason: "Multiple shell command fields disagree" };
1689
1759
  }
1690
- return { kind: "shell", command: values[0].value, fieldPath: values[0].fieldPath };
1760
+ return { kind: "shell", ...commands[0] };
1691
1761
  }
1692
1762
  function shellRuleArgs(tool, args, ruleTool, inspection = {}) {
1693
1763
  if (!ruleTool || !matchesPattern("bash", ruleTool) || matchesPattern(tool, ruleTool)) return args;
@@ -1697,6 +1767,22 @@ function shellRuleArgs(tool, args, ruleTool, inspection = {}) {
1697
1767
  function opOf(sample) {
1698
1768
  return parseShared(sample).Stmts[0].Redirs[0].Op;
1699
1769
  }
1770
+ function validateIban(raw) {
1771
+ const s = raw.replace(/[ -]/g, "").toUpperCase();
1772
+ if (!/^[A-Z]{2}\d{2}/.test(s)) return false;
1773
+ const want = IBAN_LENGTH[s.slice(0, 2)];
1774
+ if (want === void 0 || s.length < want) return false;
1775
+ const iban = s.slice(0, want);
1776
+ if (!/^[A-Z0-9]+$/.test(iban)) return false;
1777
+ const rearranged = iban.slice(4) + iban.slice(0, 4);
1778
+ let mod = 0;
1779
+ for (const ch of rearranged) {
1780
+ const code = ch.charCodeAt(0);
1781
+ const digits = code >= 65 ? String(code - 55) : ch;
1782
+ for (const d of digits) mod = (mod * 10 + (d.charCodeAt(0) - 48)) % 97;
1783
+ }
1784
+ return mod === 1;
1785
+ }
1700
1786
  function validateBase58Check(s) {
1701
1787
  if (!s) return null;
1702
1788
  let n = 0n;
@@ -2093,6 +2179,79 @@ function configurableValues(id) {
2093
2179
  if (def.pack || MAP_GOVERNED.has(id)) return def.values;
2094
2180
  return KNOB_GOVERNED[id] ?? [];
2095
2181
  }
2182
+ function normalizeHost(raw) {
2183
+ let h = raw.trim().toLowerCase();
2184
+ if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
2185
+ if (!h || h === "localhost") return null;
2186
+ if (/^[0-9a-f:]+$/.test(h) && h.includes(":")) return h;
2187
+ if (!h.includes(".") || !/^[\p{L}\p{N}.-]+$/u.test(h)) return null;
2188
+ return h;
2189
+ }
2190
+ function hostOfUrl(value) {
2191
+ const v = value.replace(/[.,;:!?)\]}>'"`]+$/, "");
2192
+ try {
2193
+ const u = new URL(v.toLowerCase().startsWith("www.") ? `http://${v}` : v);
2194
+ return normalizeHost(u.hostname);
2195
+ } catch {
2196
+ return null;
2197
+ }
2198
+ }
2199
+ function addIbans(text, out) {
2200
+ for (const m of text.toUpperCase().matchAll(IBAN_RE)) {
2201
+ const compact = m[0].replace(/[ -]/g, "");
2202
+ if (validateIban(compact)) out.add(`iban:${compact}`);
2203
+ else if (/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(compact)) out.add(`account:${compact}`);
2204
+ if (out.size >= MAX_DESTINATIONS) return;
2205
+ }
2206
+ }
2207
+ function addEmails(text, out) {
2208
+ for (const m of text.matchAll(EMAIL_RE)) {
2209
+ out.add(`email:${m[0].toLowerCase()}`);
2210
+ if (out.size >= MAX_DESTINATIONS) return;
2211
+ }
2212
+ }
2213
+ function addUrlHosts(text, out) {
2214
+ for (const m of text.matchAll(URL_RE)) {
2215
+ const h = hostOfUrl(m[0]);
2216
+ if (h) out.add(`host:${h}`);
2217
+ if (out.size >= MAX_DESTINATIONS) return;
2218
+ }
2219
+ }
2220
+ function extractArgDestinations(args) {
2221
+ const out = /* @__PURE__ */ new Set();
2222
+ try {
2223
+ walk(args, void 0, out, 0);
2224
+ } catch {
2225
+ }
2226
+ return [...out];
2227
+ }
2228
+ function walk(value, key, out, depth) {
2229
+ if (out.size >= MAX_DESTINATIONS || depth > 12) return;
2230
+ if (typeof value === "string") {
2231
+ const s = value.length > MAX_SCAN_CHARS ? value.slice(0, MAX_SCAN_CHARS) : value;
2232
+ if (s.includes("@")) addEmails(s, out);
2233
+ addUrlHosts(s, out);
2234
+ addIbans(s, out);
2235
+ if (key && RECIPIENT_KEYS.has(key.toLowerCase()) && !/[\s@]/.test(s)) {
2236
+ const h = normalizeHost(
2237
+ s.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/^\/\//, "").split(/[/?#]/)[0].replace(/:\d+$/, "")
2238
+ );
2239
+ if (h && (h.includes(":") || /\.\p{L}{2,}$/u.test(h))) out.add(`host:${h}`);
2240
+ const acct = s.replace(/[ .-]/g, "").toUpperCase();
2241
+ if (/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(acct) && !validateIban(acct))
2242
+ out.add(`account:${acct}`);
2243
+ }
2244
+ return;
2245
+ }
2246
+ if (Array.isArray(value)) {
2247
+ for (const v of value) walk(v, key, out, depth + 1);
2248
+ return;
2249
+ }
2250
+ if (value && typeof value === "object") {
2251
+ for (const [k, v] of Object.entries(value))
2252
+ walk(v, k, out, depth + 1);
2253
+ }
2254
+ }
2096
2255
  function stripTerminalEscapes(s) {
2097
2256
  return s.replace(TERMINAL_ESCAPE_RE, "");
2098
2257
  }
@@ -2204,7 +2363,7 @@ function matchCanaryArgs(args, values) {
2204
2363
  return null;
2205
2364
  }
2206
2365
  }
2207
- var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, 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, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, syntax2, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, LEGACY_SYSTEM_PATH_RE, BIP39_ENGLISH, indices, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, HOMOGLYPHS, HOMOGLYPH_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax3, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, SOURCE_COMMANDS, syntax4, PUBLISH, OWN_HEADS, TERMINAL_ESCAPE_RE, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
2366
+ var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, 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, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, LEGACY_SYSTEM_PATH_RE, BIP39_ENGLISH, indices, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax3, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, SOURCE_COMMANDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, syntax4, PUBLISH, OWN_HEADS, TERMINAL_ESCAPE_RE, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
2208
2367
  var init_dist = __esm({
2209
2368
  "packages/policy-engine/dist/index.mjs"() {
2210
2369
  "use strict";
@@ -4387,7 +4546,23 @@ var init_dist = __esm({
4387
4546
  "shell",
4388
4547
  "exec_command"
4389
4548
  ]);
4390
- MCP_EXECUTORS = /* @__PURE__ */ new Set([...BASH_TOOL_NAMES, "exec", "run_command", "terminal"]);
4549
+ MCP_EXECUTORS = /* @__PURE__ */ new Set([
4550
+ ...BASH_TOOL_NAMES,
4551
+ "exec",
4552
+ "run_command",
4553
+ "terminal",
4554
+ "execute_command",
4555
+ "execute_shell",
4556
+ "execute_shell_command",
4557
+ "shell_execute",
4558
+ "run_shell",
4559
+ "run_terminal_command",
4560
+ "start_process"
4561
+ ]);
4562
+ BUILTIN_FIELDS = ["command", "cmd", "script"];
4563
+ ARGV_FIELDS = ["args", "argv"];
4564
+ BARE_WORD = /^[A-Za-z0-9_./+:@%,-]+$/;
4565
+ MAX_ARGV_CHARACTERS = 262144;
4391
4566
  ({ syntax: syntax2 } = mvdanSh2);
4392
4567
  WRITES = new Set([">x", ">>x", ">|x", "&>x", "&>>x", "<>x"].map(opOf));
4393
4568
  READS = new Set(["<x", "<>x"].map(opOf));
@@ -6450,6 +6625,98 @@ zero
6450
6625
  zone
6451
6626
  zoo`.split("\n");
6452
6627
  indices = new Map(BIP39_ENGLISH.map((word, i) => [word, i]));
6628
+ IBAN_LENGTH = {
6629
+ AD: 24,
6630
+ AE: 23,
6631
+ AL: 28,
6632
+ AT: 20,
6633
+ AZ: 28,
6634
+ BA: 20,
6635
+ BE: 16,
6636
+ BG: 22,
6637
+ BH: 22,
6638
+ BI: 27,
6639
+ BR: 29,
6640
+ BY: 28,
6641
+ CH: 21,
6642
+ CR: 22,
6643
+ CY: 28,
6644
+ CZ: 24,
6645
+ DE: 22,
6646
+ DJ: 27,
6647
+ DK: 18,
6648
+ DO: 28,
6649
+ EE: 20,
6650
+ EG: 29,
6651
+ ES: 24,
6652
+ FI: 18,
6653
+ FK: 18,
6654
+ FO: 18,
6655
+ FR: 27,
6656
+ GB: 22,
6657
+ GE: 22,
6658
+ GI: 23,
6659
+ GL: 18,
6660
+ GR: 27,
6661
+ GT: 28,
6662
+ HN: 28,
6663
+ HR: 21,
6664
+ HU: 28,
6665
+ IE: 22,
6666
+ IL: 23,
6667
+ IQ: 23,
6668
+ IS: 26,
6669
+ IT: 27,
6670
+ JO: 30,
6671
+ KW: 30,
6672
+ KZ: 20,
6673
+ LB: 28,
6674
+ LC: 32,
6675
+ LI: 21,
6676
+ LT: 20,
6677
+ LU: 20,
6678
+ LV: 21,
6679
+ LY: 25,
6680
+ MC: 27,
6681
+ MD: 24,
6682
+ ME: 22,
6683
+ MK: 19,
6684
+ MN: 20,
6685
+ MR: 27,
6686
+ MT: 31,
6687
+ MU: 30,
6688
+ NI: 28,
6689
+ NL: 18,
6690
+ NO: 15,
6691
+ OM: 23,
6692
+ PK: 24,
6693
+ PL: 28,
6694
+ PS: 29,
6695
+ PT: 25,
6696
+ QA: 29,
6697
+ RO: 24,
6698
+ RS: 22,
6699
+ RU: 33,
6700
+ SA: 24,
6701
+ SC: 31,
6702
+ SD: 18,
6703
+ SE: 24,
6704
+ SI: 19,
6705
+ SK: 24,
6706
+ SM: 27,
6707
+ SN: 28,
6708
+ SO: 23,
6709
+ ST: 25,
6710
+ SV: 28,
6711
+ TL: 23,
6712
+ TN: 24,
6713
+ TR: 26,
6714
+ UA: 29,
6715
+ VA: 22,
6716
+ VG: 24,
6717
+ XK: 20,
6718
+ YE: 30
6719
+ };
6453
6720
  B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
6454
6721
  B58_INDEX = Object.fromEntries(
6455
6722
  [...B58].map((c, i) => [c, i])
@@ -6464,6 +6731,8 @@ zoo`.split("\n");
6464
6731
  );
6465
6732
  CASK_TWO_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AEIMQUYcgkosw048"]);
6466
6733
  CASK_FOUR_ZERO_SUFFIX = /* @__PURE__ */ new Set([..."AQgw"]);
6734
+ INVISIBLE_RE = /[­͏؜᠎​-‏‪-‮⁠-⁤-︀-️]|[\u{E0000}-\u{E007F}]/gu;
6735
+ INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
6467
6736
  HOMOGLYPHS = {
6468
6737
  // Cyrillic lowercase
6469
6738
  \u0430: "a",
@@ -7212,6 +7481,38 @@ zoo`.split("\n");
7212
7481
  (c) => configurableValues(c.id).length > 0
7213
7482
  ).map((c) => c.id);
7214
7483
  SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
7484
+ MAX_DESTINATIONS = 200;
7485
+ MAX_SCAN_CHARS = 1e5;
7486
+ EMAIL_RE = /[\p{L}\p{N}._%+-]+@[\p{L}\p{N}](?:[\p{L}\p{N}-]*[\p{L}\p{N}])?(?:\.[\p{L}\p{N}](?:[\p{L}\p{N}-]*[\p{L}\p{N}])?)*\.\p{L}{2,}/gu;
7487
+ URL_RE = /\b(?:[a-z][a-z0-9+.-]*:\/\/|www\.)[^\s<>"'`){}]+/gi;
7488
+ IBAN_RE = /\b[A-Z]{2}\d{2}(?:[ -]?[A-Z0-9]{4}){2,7}(?:[ -]?[A-Z0-9]{1,4})?\b/g;
7489
+ RECIPIENT_KEYS = /* @__PURE__ */ new Set([
7490
+ "to",
7491
+ "cc",
7492
+ "bcc",
7493
+ "recipient",
7494
+ "recipients",
7495
+ "email",
7496
+ "participants",
7497
+ "attendees",
7498
+ "destination",
7499
+ "iban",
7500
+ "account",
7501
+ // Where a request goes, when the value is a bare host or a scheme-less URL.
7502
+ "url",
7503
+ "uri",
7504
+ "host",
7505
+ "hostname",
7506
+ "endpoint",
7507
+ "server",
7508
+ "domain",
7509
+ "address",
7510
+ "webhook",
7511
+ "webhook_url",
7512
+ "callback",
7513
+ "callback_url",
7514
+ "target"
7515
+ ]);
7215
7516
  ({ syntax: syntax4 } = mvdanSh4);
7216
7517
  PUBLISH = {
7217
7518
  npm: { verbs: /* @__PURE__ */ new Set(["publish"]), registry: "registry.npmjs.org", flag: ["--registry"] },
@@ -7560,18 +7861,18 @@ function statAndFirstLine(file) {
7560
7861
  }
7561
7862
  function listCodexSessionFiles(base = codexSessionsDir()) {
7562
7863
  const files = [];
7563
- const walk = (dir) => {
7864
+ const walk2 = (dir) => {
7564
7865
  try {
7565
7866
  for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
7566
7867
  const file = path3.join(dir, entry.name);
7567
- if (entry.isDirectory()) walk(file);
7868
+ if (entry.isDirectory()) walk2(file);
7568
7869
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(file);
7569
7870
  }
7570
7871
  } catch {
7571
7872
  }
7572
7873
  };
7573
- walk(base);
7574
- if (path3.basename(base) === "sessions") walk(path3.join(path3.dirname(base), "archived_sessions"));
7874
+ walk2(base);
7875
+ if (path3.basename(base) === "sessions") walk2(path3.join(path3.dirname(base), "archived_sessions"));
7575
7876
  const sessions = /* @__PURE__ */ new Map();
7576
7877
  for (const file of files.sort()) {
7577
7878
  try {
@@ -7894,6 +8195,26 @@ var init_daemon = __esm({
7894
8195
  }
7895
8196
  });
7896
8197
 
8198
+ // src/auth/trusted-recipients.ts
8199
+ function normalizeRecipient(raw) {
8200
+ const v = raw.trim();
8201
+ if (!v) return null;
8202
+ if (/^(email|host|iban):/i.test(v)) {
8203
+ const i = v.indexOf(":");
8204
+ const kind = v.slice(0, i).toLowerCase();
8205
+ const inner = normalizeRecipient(v.slice(i + 1));
8206
+ return inner && inner.startsWith(`${kind}:`) ? inner : null;
8207
+ }
8208
+ const found = extractArgDestinations({ to: v });
8209
+ return found.length === 1 ? found[0] : null;
8210
+ }
8211
+ var init_trusted_recipients = __esm({
8212
+ "src/auth/trusted-recipients.ts"() {
8213
+ "use strict";
8214
+ init_dist();
8215
+ }
8216
+ });
8217
+
7897
8218
  // src/config-schema.ts
7898
8219
  import { z } from "zod";
7899
8220
  function formatIssues(issues) {
@@ -8447,7 +8768,7 @@ var init_build = __esm({
8447
8768
  });
8448
8769
 
8449
8770
  // src/auth/trusted-hosts.ts
8450
- function normalizeHost(raw) {
8771
+ function normalizeHost2(raw) {
8451
8772
  return raw.toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^[^@]+@/, "").replace(/:\d+$/, "");
8452
8773
  }
8453
8774
  var init_trusted_hosts = __esm({
@@ -8768,6 +9089,7 @@ function getConfig(cwd) {
8768
9089
  dangerousWords: [...DEFAULT_CONFIG.policy.dangerousWords],
8769
9090
  ignoredTools: [...DEFAULT_CONFIG.policy.ignoredTools],
8770
9091
  toolInspection: { ...DEFAULT_CONFIG.policy.toolInspection },
9092
+ projectToolInspection: {},
8771
9093
  // Shipped rules carry `builtin`: the catalog's per-check value governs
8772
9094
  // them and only them (a user rule that reuses a name keeps its verdict).
8773
9095
  smartRules: DEFAULT_CONFIG.policy.smartRules.map((r) => ({ ...r, builtin: true })),
@@ -8794,7 +9116,8 @@ function getConfig(cwd) {
8794
9116
  untrustedContent: {
8795
9117
  ...DEFAULT_CONFIG.policy.untrustedContent,
8796
9118
  orgDomains: [...DEFAULT_CONFIG.policy.untrustedContent.orgDomains],
8797
- roles: {}
9119
+ roles: {},
9120
+ trustedRecipients: []
8798
9121
  },
8799
9122
  // Left empty on purpose: the local file is read fresh at policy-eval time
8800
9123
  // (getCachedHosts via isTrustedHost), NOT snapshotted into the frozen config
@@ -8840,8 +9163,14 @@ function getConfig(cwd) {
8840
9163
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
8841
9164
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
8842
9165
  if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
8843
- if (p.toolInspection)
8844
- mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
9166
+ if (p.toolInspection) {
9167
+ if (isProject)
9168
+ mergedPolicy.projectToolInspection = {
9169
+ ...mergedPolicy.projectToolInspection,
9170
+ ...p.toolInspection
9171
+ };
9172
+ else mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
9173
+ }
8845
9174
  if (p.smartRules) {
8846
9175
  const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
8847
9176
  const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
@@ -9191,7 +9520,7 @@ function getConfig(cwd) {
9191
9520
  }
9192
9521
  if (Array.isArray(mc.trustedHosts)) {
9193
9522
  mergedPolicy.trustedHostsManaged = true;
9194
- mergedPolicy.trustedHosts = mc.trustedHosts.filter((h) => typeof h === "string").map((h) => normalizeHost(h));
9523
+ mergedPolicy.trustedHosts = mc.trustedHosts.filter((h) => typeof h === "string").map((h) => normalizeHost2(h));
9195
9524
  }
9196
9525
  if (mc.appPermissions && typeof mc.appPermissions === "object" && !Array.isArray(mc.appPermissions)) {
9197
9526
  const coerced = {};
@@ -9460,6 +9789,13 @@ function applyUntrustedContent(cur, raw, replaceLists = false, isProject = false
9460
9789
  if (replaceLists) cur.orgDomains = domains;
9461
9790
  else for (const d of domains) if (!cur.orgDomains.includes(d)) cur.orgDomains.push(d);
9462
9791
  }
9792
+ if (replaceLists && Array.isArray(uc.trustedRecipients)) {
9793
+ cur.trustedRecipients = [
9794
+ ...new Set(
9795
+ uc.trustedRecipients.filter((v) => typeof v === "string").map((v) => normalizeRecipient(v)).filter((v) => v !== null)
9796
+ )
9797
+ ].slice(0, 200);
9798
+ }
9463
9799
  if (uc.roles && typeof uc.roles === "object" && !Array.isArray(uc.roles)) {
9464
9800
  const next = replaceLists ? {} : cur.roles;
9465
9801
  for (const [server, tools] of Object.entries(uc.roles)) {
@@ -9480,6 +9816,7 @@ var DANGEROUS_WORDS, RUNTIME_ONLY_CONFIG_KEYS, DEFAULT_CONFIG, RM_SAFE_PATH_PATT
9480
9816
  var init_config = __esm({
9481
9817
  "src/config/index.ts"() {
9482
9818
  "use strict";
9819
+ init_trusted_recipients();
9483
9820
  init_config_schema();
9484
9821
  init_shields();
9485
9822
  init_managed();
@@ -9734,7 +10071,8 @@ var init_config = __esm({
9734
10071
  spotlight: true,
9735
10072
  afterExposure: "ask-once",
9736
10073
  orgDomains: [],
9737
- roles: {}
10074
+ roles: {},
10075
+ trustedRecipients: []
9738
10076
  },
9739
10077
  trustedHosts: [],
9740
10078
  trustedHostsManaged: false,
@@ -9852,7 +10190,7 @@ import * as fs7 from "fs";
9852
10190
  import * as path8 from "path";
9853
10191
  function listSessionFiles(dir, maxDepth = 6) {
9854
10192
  const out = [];
9855
- const walk = (d, rel, depth) => {
10193
+ const walk2 = (d, rel, depth) => {
9856
10194
  if (depth > maxDepth) return;
9857
10195
  let entries;
9858
10196
  try {
@@ -9862,11 +10200,11 @@ function listSessionFiles(dir, maxDepth = 6) {
9862
10200
  }
9863
10201
  for (const e of entries) {
9864
10202
  const childRel = rel ? path8.join(rel, e.name) : e.name;
9865
- if (e.isDirectory()) walk(path8.join(d, e.name), childRel, depth + 1);
10203
+ if (e.isDirectory()) walk2(path8.join(d, e.name), childRel, depth + 1);
9866
10204
  else if (e.name.endsWith(".jsonl")) out.push(childRel);
9867
10205
  }
9868
10206
  };
9869
- walk(dir, "", 0);
10207
+ walk2(dir, "", 0);
9870
10208
  return out;
9871
10209
  }
9872
10210
  function sessionIdOf(relPath) {
@@ -10464,7 +10802,9 @@ function dimensionOfBlock(checkedBy, ruleName) {
10464
10802
  if (checkedBy.includes("egress")) return "network";
10465
10803
  if (ruleName.startsWith("ssrf:")) return "network";
10466
10804
  if (checkedBy.includes("pii") || checkedBy.includes("dlp")) return "data";
10467
- if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection"))
10805
+ if (checkedBy === "loop-detected" || checkedBy === "mcp-pin-mismatch" || checkedBy.startsWith("injection") || // fw#318: a send held after the session read outside content, and the
10806
+ // read rows themselves. Mirrors node9Firewall canon-taxonomy.ts.
10807
+ ruleName.startsWith("exposure") || checkedBy.startsWith("exposure"))
10468
10808
  return "detection";
10469
10809
  if (checkedBy === "app-permission-block" || ruleName.startsWith("app-permission:")) return "apps";
10470
10810
  if (ruleName.startsWith("shield:project-jail")) return "files";
@@ -11143,19 +11483,19 @@ function scrubDecoys(subject, values) {
11143
11483
  for (const v of values) if (v) out = out.split(v).join("[node9-decoy]");
11144
11484
  return out;
11145
11485
  };
11146
- const walk = (v, depth) => {
11486
+ const walk2 = (v, depth) => {
11147
11487
  if (depth > 6) return v;
11148
11488
  if (typeof v === "string") return scrubText(v);
11149
- if (Array.isArray(v)) return v.map((x) => walk(x, depth + 1));
11489
+ if (Array.isArray(v)) return v.map((x) => walk2(x, depth + 1));
11150
11490
  if (v && typeof v === "object") {
11151
11491
  const out = {};
11152
11492
  for (const [k, child] of Object.entries(v))
11153
- out[k] = walk(child, depth + 1);
11493
+ out[k] = walk2(child, depth + 1);
11154
11494
  return out;
11155
11495
  }
11156
11496
  return v;
11157
11497
  };
11158
- return walk(subject, 0);
11498
+ return walk2(subject, 0);
11159
11499
  }
11160
11500
  function pushFsOpAstFinding(command, toolName, input, timestamp2, projLabel, sessionId, agent, result, dedup) {
11161
11501
  const fsVerdict = analyzeFsOperation(command);