@node9/proxy 2.13.0 → 2.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1162,13 +1162,32 @@ var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
1162
1162
  })
1163
1163
  );
1164
1164
  var SENSITIVE_PATH_PATTERNS = [
1165
- /[/\\]\.ssh[/\\]/i,
1166
- /[/\\]\.aws[/\\]/i,
1165
+ /[/\\]\.ssh([/\\]|$)/i,
1166
+ /[/\\]\.aws([/\\]|$)/i,
1167
1167
  /[/\\]\.config[/\\]gcloud[/\\]/i,
1168
1168
  /[/\\]\.azure[/\\]/i,
1169
1169
  /[/\\]\.kube[/\\]config$/i,
1170
- /[/\\]\.env($|\.)/i,
1171
- // .env, .env.local, .env.production — not .envoy
1170
+ // ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
1171
+ // (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
1172
+ // structural suffix chain rather than a hand-written list, `example|sample|
1173
+ // template` exempt because a fixture stays a fixture whatever follows, and
1174
+ // `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
1175
+ // committed template, `.env.test.local` is gitignored and holds real values.
1176
+ //
1177
+ // It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
1178
+ // blocked while `cat .env.example` allowed: the same file, opposite verdicts,
1179
+ // decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
1180
+ // which is the contract that now holds these copies in step, and stage 5 of
1181
+ // doc/credential-jail-architecture.md, which replaces them with one generated
1182
+ // source.
1183
+ // ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
1184
+ // fixture whatever follows it -- `.env.example.md` is documentation -- but
1185
+ // `.env.example.local` is gitignored by the `.env*.local` convention and holds
1186
+ // real values, exactly the reasoning that anchors `(?!\.test$)` rather than
1187
+ // using `\b`. Without this branch the fixture exemption also bought a two-step
1188
+ // bypass: `cp .env .env.sample`, then read the copy.
1189
+ /[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
1190
+ // .env + any suffix chain; fixtures exempt unless .local
1172
1191
  /[/\\]\.git-credentials$/i,
1173
1192
  /[/\\]\.npmrc$/i,
1174
1193
  /[/\\]\.docker[/\\]config\.json$/i,
@@ -1670,6 +1689,10 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
1670
1689
  "od",
1671
1690
  "xxd",
1672
1691
  "hexdump",
1692
+ // Emits the file's bytes, re-encoded, so it is a read by the set's own test
1693
+ // ("does it emit file contents"). Absent until 2026-09-10, which is why
1694
+ // `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
1695
+ "base64",
1673
1696
  "strings",
1674
1697
  "sort",
1675
1698
  "uniq",
@@ -1678,7 +1701,11 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
1678
1701
  "dd"
1679
1702
  ]);
1680
1703
  var FS_OP_PRESCREEN_RE = new RegExp(
1681
- `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
1704
+ // A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
1705
+ // reader right after `"` / `'`, and without these two characters the
1706
+ // prescreen rejected every string-wrapped read before the parser ran.
1707
+ // Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
1708
+ `(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
1682
1709
  );
1683
1710
  var HOME_CACHE_ALLOWLIST = [
1684
1711
  ".cache",
@@ -1699,12 +1726,12 @@ var SENSITIVE_PATH_RULES = [
1699
1726
  {
1700
1727
  rule: "shield:project-jail:block-read-ssh",
1701
1728
  reason: "Reading SSH private keys is blocked by project-jail shield",
1702
- match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
1729
+ match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
1703
1730
  },
1704
1731
  {
1705
1732
  rule: "shield:project-jail:block-read-aws",
1706
1733
  reason: "Reading AWS credentials is blocked by project-jail shield",
1707
- match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
1734
+ match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
1708
1735
  },
1709
1736
  {
1710
1737
  // Mirrors the JSON shield's `.env` pattern (project-jail.json's
@@ -1748,7 +1775,9 @@ var SENSITIVE_PATH_RULES = [
1748
1775
  // symmetry — silently exempts every `.env.test.*` file.
1749
1776
  //
1750
1777
  // shields.test.ts:983-995 is the canonical contract; keep both in step.
1751
- match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$/i.test(p)
1778
+ match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
1779
+ p
1780
+ )
1752
1781
  },
1753
1782
  {
1754
1783
  // verdict: 'review' (not 'block') is a deliberate design choice
@@ -1959,15 +1988,14 @@ function listOps() {
1959
1988
  return _listOps;
1960
1989
  }
1961
1990
  var WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
1991
+ var FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
1962
1992
  var INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
1963
1993
  function unwrapCommandHead(words) {
1964
1994
  let i = 0;
1965
1995
  while (i < words.length) {
1966
1996
  const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
1967
1997
  if (head === "find") {
1968
- const x = words.findIndex(
1969
- (w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
1970
- );
1998
+ const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
1971
1999
  if (x < 0) break;
1972
2000
  i = x + 1;
1973
2001
  continue;
@@ -1989,7 +2017,11 @@ function unwrapCommandHead(words) {
1989
2017
  if (t.startsWith("-")) {
1990
2018
  i++;
1991
2019
  const nxt = words[i];
1992
- if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()))
2020
+ if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
2021
+ // `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
2022
+ // swallowed and the jail needed a looser fallback whose cost was a
2023
+ // false positive on `sudo echo cat X`.
2024
+ !FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
1993
2025
  i++;
1994
2026
  continue;
1995
2027
  }
@@ -2110,36 +2142,29 @@ function isProtectedHomePath(rawPath) {
2110
2142
  }
2111
2143
  function extractLiteralArgs(callExpr) {
2112
2144
  const args = callExpr.Args || [];
2113
- if (args.length === 0) return { name: "", flags: [], paths: [] };
2114
- const litFromWord = (w) => {
2115
- const parts = w?.Parts || [];
2116
- let s = "";
2117
- for (const p of parts) {
2118
- const t = syntax.NodeType(p);
2119
- if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
2120
- else if (t === "SglQuoted") s += p.Value ?? "";
2121
- else if (t === "DblQuoted") {
2122
- const inner = p.Parts || [];
2123
- if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
2124
- s += inner.map((ip) => ip.Value ?? "").join("");
2125
- } else {
2126
- return null;
2127
- }
2128
- }
2129
- return s;
2130
- };
2131
- const name = (litFromWord(args[0]) || "").toLowerCase();
2145
+ if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
2146
+ const words = args.map((a) => resolveWordLiteral(a));
2147
+ const name = (words[0] ?? "").toLowerCase();
2132
2148
  const flags = [];
2133
2149
  const paths = [];
2134
- for (let i = 1; i < args.length; i++) {
2135
- const v = litFromWord(args[i]);
2150
+ for (let i = 1; i < words.length; i++) {
2151
+ const v = words[i];
2136
2152
  if (v === null) continue;
2137
2153
  if (v.startsWith("-")) flags.push(v);
2138
2154
  else paths.push(v);
2139
2155
  }
2140
- return { name, flags, paths };
2156
+ return { name, flags, paths, words };
2141
2157
  }
2142
- var NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
2158
+ var NET_BINARIES = /* @__PURE__ */ new Set([
2159
+ "curl",
2160
+ "wget",
2161
+ "scp",
2162
+ "ssh",
2163
+ "nc",
2164
+ "ncat",
2165
+ "netcat",
2166
+ "rsync"
2167
+ ]);
2143
2168
  var VALUE_FLAGS = {
2144
2169
  curl: /* @__PURE__ */ new Set([
2145
2170
  "-d",
@@ -2424,6 +2449,9 @@ function deriveRedirOp(sample) {
2424
2449
  }
2425
2450
  }
2426
2451
  var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
2452
+ var REDIR_FILE_IN_OPS = new Set(
2453
+ [deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
2454
+ );
2427
2455
  var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
2428
2456
  deriveRedirOp("cat <<X\nX"),
2429
2457
  deriveRedirOp("cat <<-X\nX")
@@ -2488,16 +2516,21 @@ function isRmCreatedInCommandCleanup(command) {
2488
2516
  }
2489
2517
  return sawRm && ok;
2490
2518
  }
2491
- function analyzeFsOperationImpl(command) {
2519
+ function analyzeFsOperationImpl(command, depth = 0) {
2492
2520
  const f = parseShared(command);
2493
2521
  if (f === PARSE_FAIL) return null;
2494
2522
  let result = null;
2495
2523
  try {
2496
2524
  syntax.Walk(f, (node) => {
2497
- if (!node || result) return false;
2525
+ if (!node || result?.verdict === "block") return false;
2498
2526
  const n = node;
2499
- if (syntax.NodeType(n) !== "CallExpr") return true;
2500
- const { name, flags, paths } = extractLiteralArgs(n);
2527
+ const nodeType = syntax.NodeType(n);
2528
+ if (nodeType === "Stmt") {
2529
+ result = stricter(result, jailedRedirectRead(n));
2530
+ return result?.verdict !== "block";
2531
+ }
2532
+ if (nodeType !== "CallExpr") return true;
2533
+ const { name, flags, paths, words } = extractLiteralArgs(n);
2501
2534
  if (!name) return true;
2502
2535
  if (name === "rm") {
2503
2536
  const flagStr = flags.join("").toLowerCase();
@@ -2526,19 +2559,22 @@ function analyzeFsOperationImpl(command) {
2526
2559
  }
2527
2560
  }
2528
2561
  }
2529
- if (FS_READ_TOOLS.has(name)) {
2530
- for (const p of paths) {
2531
- for (const sp of SENSITIVE_PATH_RULES) {
2532
- if (sp.match(p)) {
2533
- result = {
2534
- ruleName: sp.rule,
2535
- verdict: sp.verdict ?? "block",
2536
- reason: sp.reason,
2537
- path: p
2538
- };
2539
- return false;
2540
- }
2562
+ if (depth < 1) {
2563
+ const payload = literalShellPayload(words, name);
2564
+ if (payload !== null) {
2565
+ const inner = analyzeFsOperationImpl(payload, depth + 1);
2566
+ if (inner) {
2567
+ result = inner;
2568
+ return false;
2541
2569
  }
2570
+ return true;
2571
+ }
2572
+ }
2573
+ const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
2574
+ if (readPaths) {
2575
+ for (const p of readPaths) {
2576
+ result = stricter(result, matchSensitivePath2(p));
2577
+ if (result?.verdict === "block") return false;
2542
2578
  }
2543
2579
  }
2544
2580
  return true;
@@ -2548,6 +2584,57 @@ function analyzeFsOperationImpl(command) {
2548
2584
  return null;
2549
2585
  }
2550
2586
  }
2587
+ function stricter(a, b) {
2588
+ if (!a) return b;
2589
+ if (!b) return a;
2590
+ return b.verdict === "block" && a.verdict !== "block" ? b : a;
2591
+ }
2592
+ function matchSensitivePath2(p) {
2593
+ for (const sp of SENSITIVE_PATH_RULES) {
2594
+ if (sp.match(p))
2595
+ return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
2596
+ }
2597
+ return null;
2598
+ }
2599
+ var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
2600
+ var positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
2601
+ function wrappedReadPaths(words, name) {
2602
+ if (name === "find") {
2603
+ const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
2604
+ if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
2605
+ const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
2606
+ return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
2607
+ }
2608
+ if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
2609
+ const h = unwrapCommandHead(words);
2610
+ return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
2611
+ }
2612
+ function literalShellPayload(words, name) {
2613
+ const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
2614
+ const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
2615
+ if (head === "eval") {
2616
+ const rest = words.slice(h + 1);
2617
+ if (rest.length === 0 || rest.some((w) => w === null)) return null;
2618
+ return rest.join(" ");
2619
+ }
2620
+ if (SHELL_INTERPRETERS.has(head)) {
2621
+ const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
2622
+ if (c < 0) return null;
2623
+ return words[c + 1] ?? null;
2624
+ }
2625
+ return null;
2626
+ }
2627
+ function jailedRedirectRead(stmt) {
2628
+ const redirs = stmt.Redirs || [];
2629
+ for (const r of redirs) {
2630
+ if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
2631
+ const p = resolveWordLiteral(r.Word);
2632
+ if (p === null || p === "") continue;
2633
+ const hit = matchSensitivePath2(p);
2634
+ if (hit) return hit;
2635
+ }
2636
+ return null;
2637
+ }
2551
2638
  function analyzeShellCommand(command) {
2552
2639
  const actions = [];
2553
2640
  const paths = [];
@@ -2680,21 +2767,7 @@ function evaluateEgress(dests, policy) {
2680
2767
  }
2681
2768
  return review;
2682
2769
  }
2683
- var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
2684
- "cat",
2685
- "head",
2686
- "tail",
2687
- "grep",
2688
- "awk",
2689
- "sed",
2690
- "cut",
2691
- "sort",
2692
- "tee",
2693
- "less",
2694
- "more",
2695
- "strings",
2696
- "xxd"
2697
- ]);
2770
+ var SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
2698
2771
  var SINK_COMMANDS = /* @__PURE__ */ new Set([
2699
2772
  "curl",
2700
2773
  "wget",
@@ -2725,16 +2798,25 @@ var OBFUSCATORS = /* @__PURE__ */ new Set([
2725
2798
  "node"
2726
2799
  ]);
2727
2800
  var SENSITIVE_PATTERNS = [
2728
- /(?:^|\/)\.env(?:\.|$)/i,
2729
- // .env, .env.local, .env.production
2801
+ // Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
2802
+ /(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
2803
+ // .env chain; fixtures exempt unless .local
2730
2804
  /id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
2731
2805
  // SSH private keys
2732
2806
  /\.pem$|\.key$|\.p12$|\.pfx$/i,
2733
2807
  // certificate files
2734
- /(?:^|\/)\.ssh\//i,
2735
- // ~/.ssh/ directory
2736
- /(?:^|\/)\.aws\/credentials/i,
2737
- // AWS credentials
2808
+ // The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
2809
+ // the directory counts wherever it appears, while the directory ITSELF counts
2810
+ // only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
2811
+ // `config/.ssh` is more likely a search pattern than a read. These are
2812
+ // extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
2813
+ // contract as the shell tier, so the same boundary is the right one.
2814
+ // Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
2815
+ // identical pipeline naming a file inside that directory.
2816
+ /(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
2817
+ // ~/.ssh/ and ~/.ssh
2818
+ /(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
2819
+ // AWS creds + dir
2738
2820
  /(?:^|\/)\.netrc$/i,
2739
2821
  // netrc (stores HTTP credentials)
2740
2822
  /(?:^|\/)(passwd|shadow|sudoers)$/i,
@@ -2768,8 +2850,8 @@ function splitOnPipe(cmd) {
2768
2850
  if (current.trim()) segments2.push(current.trim());
2769
2851
  return segments2.filter(Boolean);
2770
2852
  }
2771
- function positionalTokens(segment) {
2772
- return segment.split(/\s+/).slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
2853
+ function positionalTokens(tokens) {
2854
+ return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
2773
2855
  }
2774
2856
  function analyzePipeChain(command) {
2775
2857
  const segments2 = splitOnPipe(command);
@@ -2792,8 +2874,10 @@ function analyzePipeChain(command) {
2792
2874
  for (const segment of segments2) {
2793
2875
  const tokens = segment.split(/\s+/).filter(Boolean);
2794
2876
  if (tokens.length === 0) continue;
2795
- const binary = tokens[0].toLowerCase();
2796
- const args = positionalTokens(segment);
2877
+ const h = unwrapCommandHead(tokens);
2878
+ const head = h < tokens.length ? h : 0;
2879
+ const binary = tokens[head].toLowerCase();
2880
+ const args = positionalTokens(tokens.slice(head));
2797
2881
  if (SOURCE_COMMANDS.has(binary)) {
2798
2882
  sourceFiles.push(...args);
2799
2883
  if (args.some(isSensitivePath)) hasSensitiveSource = true;
@@ -4192,7 +4276,7 @@ var project_jail_default = {
4192
4276
  {
4193
4277
  field: "command",
4194
4278
  op: "matches",
4195
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
4279
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
4196
4280
  flags: "i"
4197
4281
  }
4198
4282
  ],
@@ -4206,7 +4290,7 @@ var project_jail_default = {
4206
4290
  {
4207
4291
  field: "command",
4208
4292
  op: "matches",
4209
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
4293
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
4210
4294
  flags: "i"
4211
4295
  }
4212
4296
  ],
@@ -4220,7 +4304,7 @@ var project_jail_default = {
4220
4304
  {
4221
4305
  field: "command",
4222
4306
  op: "matches",
4223
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
4307
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
4224
4308
  flags: "i"
4225
4309
  }
4226
4310
  ],
@@ -4234,7 +4318,7 @@ var project_jail_default = {
4234
4318
  {
4235
4319
  field: "command",
4236
4320
  op: "matches",
4237
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
4321
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
4238
4322
  flags: "i"
4239
4323
  }
4240
4324
  ],
@@ -4248,7 +4332,7 @@ var project_jail_default = {
4248
4332
  {
4249
4333
  field: "file_path",
4250
4334
  op: "matches",
4251
- value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
4335
+ value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
4252
4336
  flags: "i"
4253
4337
  }
4254
4338
  ],
@@ -4262,7 +4346,7 @@ var project_jail_default = {
4262
4346
  {
4263
4347
  field: "file_path",
4264
4348
  op: "matches",
4265
- value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
4349
+ value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
4266
4350
  flags: "i"
4267
4351
  }
4268
4352
  ],
@@ -4276,7 +4360,7 @@ var project_jail_default = {
4276
4360
  {
4277
4361
  field: "file_path",
4278
4362
  op: "matches",
4279
- value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
4363
+ value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
4280
4364
  flags: "i"
4281
4365
  }
4282
4366
  ],
@@ -7053,12 +7137,13 @@ function extractFilePaths(toolName, args) {
7053
7137
  }
7054
7138
  return paths.filter(Boolean);
7055
7139
  }
7140
+ var NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
7056
7141
  function isNetworkTool(toolName, args) {
7057
7142
  const t = toolName.toLowerCase();
7058
7143
  if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
7059
7144
  const a = args;
7060
7145
  const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
7061
- return /\b(curl|wget|scp|rsync|nc|ncat|netcat|ssh)\b/.test(cmd);
7146
+ return NETWORK_COMMAND_RE.test(cmd);
7062
7147
  }
7063
7148
  return false;
7064
7149
  }