@node9/proxy 1.61.0 → 1.62.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.
@@ -443,6 +443,20 @@ function analyzeFsOperation(command) {
443
443
  fsOpCache.set(normalized, computed);
444
444
  return computed;
445
445
  }
446
+ function deriveRedirOp(sample) {
447
+ try {
448
+ const f = sharedParser.Parse(sample, "cmd");
449
+ let op = -1;
450
+ syntax.Walk(f, (node) => {
451
+ const n = node;
452
+ if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
453
+ return true;
454
+ });
455
+ return op;
456
+ } catch {
457
+ return -1;
458
+ }
459
+ }
446
460
  function analyzeFsOperationImpl(command) {
447
461
  const f = parseShared(command);
448
462
  if (f === PARSE_FAIL) return null;
@@ -655,7 +669,7 @@ function assertBuiltinShieldRegexesAreSafe() {
655
669
  }
656
670
  }
657
671
  }
658
- var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, COST_PER_LOOP_ITER_USD, LONG_OUTPUT_THRESHOLD_BYTES;
672
+ var ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_HEREDOC_OPS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, COST_PER_LOOP_ITER_USD, LONG_OUTPUT_THRESHOLD_BYTES;
659
673
  var init_dist = __esm({
660
674
  "packages/policy-engine/dist/index.mjs"() {
661
675
  "use strict";
@@ -1273,6 +1287,11 @@ var init_dist = __esm({
1273
1287
  ]);
1274
1288
  FS_OP_CACHE_MAX = 5e3;
1275
1289
  fsOpCache = /* @__PURE__ */ new Map();
1290
+ REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
1291
+ REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
1292
+ deriveRedirOp("cat <<X\nX"),
1293
+ deriveRedirOp("cat <<-X\nX")
1294
+ ]);
1276
1295
  MAX_REGEX_LENGTH = 100;
1277
1296
  REGEX_CACHE_MAX = 500;
1278
1297
  regexCache = /* @__PURE__ */ new Map();
package/dist/index.js CHANGED
@@ -1351,7 +1351,7 @@ function analyzeSqlDestructive(command) {
1351
1351
  description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
1352
1352
  };
1353
1353
  }
1354
- var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx", "+x"]);
1354
+ var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"]);
1355
1355
  var COMMAND_WRAPPERS = /* @__PURE__ */ new Set([
1356
1356
  "sudo",
1357
1357
  "doas",
@@ -1671,6 +1671,97 @@ function analyzeFsOperation(command) {
1671
1671
  fsOpCache.set(normalized, computed);
1672
1672
  return computed;
1673
1673
  }
1674
+ var stripDotSlash = (p) => p.replace(/^\.\//, "");
1675
+ function isSensitiveCleanupName(p) {
1676
+ const base = p.replace(/^.*[\\/]/, "");
1677
+ return /^\.env(\.|$)/i.test(base) || /(?:^|[\\/])\.(?:ssh|aws|gnupg|git)(?:[\\/]|$)/i.test(p) || /\.(?:pem|key|p12|pfx|crt)$/i.test(base) || /^\.?(?:netrc|npmrc|pgpass|htpasswd)$/i.test(base) || /^id_(?:rsa|dsa|ecdsa|ed25519)/i.test(base) || /credential/i.test(p) || /secret/i.test(base);
1678
+ }
1679
+ function isWaivableCleanupTarget(p) {
1680
+ if (/^[/~]/.test(p) || /^\$/.test(p)) return false;
1681
+ if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(p)) return false;
1682
+ if (/[*?[{]/.test(p)) return false;
1683
+ if (isSensitiveCleanupName(p)) return false;
1684
+ return true;
1685
+ }
1686
+ function deriveRedirOp(sample) {
1687
+ try {
1688
+ const f = sharedParser.Parse(sample, "cmd");
1689
+ let op = -1;
1690
+ syntax.Walk(f, (node) => {
1691
+ const n = node;
1692
+ if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
1693
+ return true;
1694
+ });
1695
+ return op;
1696
+ } catch {
1697
+ return -1;
1698
+ }
1699
+ }
1700
+ var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
1701
+ var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
1702
+ deriveRedirOp("cat <<X\nX"),
1703
+ deriveRedirOp("cat <<-X\nX")
1704
+ ]);
1705
+ function collectSameCommandCreations(f) {
1706
+ const created = /* @__PURE__ */ new Set();
1707
+ try {
1708
+ const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
1709
+ for (const stmt of stmts) {
1710
+ if (!stmt || !stmt.Cmd || syntax.NodeType(stmt.Cmd) !== "CallExpr") continue;
1711
+ const redirs = stmt.Redirs || [];
1712
+ if (!redirs.some((r) => r && REDIR_HEREDOC_OPS.has(r.Op))) continue;
1713
+ for (const r of redirs) {
1714
+ if (r && REDIR_TRUNCATE_OPS.has(r.Op) && r.N == null) {
1715
+ const w = resolveWordLiteral(r.Word);
1716
+ if (w) created.add(stripDotSlash(w));
1717
+ }
1718
+ }
1719
+ }
1720
+ } catch {
1721
+ return created;
1722
+ }
1723
+ return created;
1724
+ }
1725
+ function isRmCreatedInCommandCleanup(command) {
1726
+ if (!/\brm\b/.test(command)) return false;
1727
+ const f = parseShared(command);
1728
+ if (f === PARSE_FAIL) return false;
1729
+ const created = collectSameCommandCreations(f);
1730
+ if (created.size === 0) return false;
1731
+ let sawRm = false;
1732
+ let ok = true;
1733
+ try {
1734
+ syntax.Walk(f, (node) => {
1735
+ if (!node || !ok) return false;
1736
+ const n = node;
1737
+ if (syntax.NodeType(n) !== "CallExpr") return true;
1738
+ const args = n.Args || [];
1739
+ const name = (resolveWordLiteral(args[0]) ?? "").toLowerCase();
1740
+ if (name !== "rm") return true;
1741
+ sawRm = true;
1742
+ const { flags, paths } = extractLiteralArgs(n);
1743
+ if (args.length - 1 > flags.length + paths.length) {
1744
+ ok = false;
1745
+ return false;
1746
+ }
1747
+ if (paths.length === 0) {
1748
+ ok = false;
1749
+ return false;
1750
+ }
1751
+ for (const p of paths) {
1752
+ const np = stripDotSlash(p);
1753
+ if (!created.has(np) || !isWaivableCleanupTarget(np)) {
1754
+ ok = false;
1755
+ return false;
1756
+ }
1757
+ }
1758
+ return true;
1759
+ });
1760
+ } catch {
1761
+ return false;
1762
+ }
1763
+ return sawRm && ok;
1764
+ }
1674
1765
  function analyzeFsOperationImpl(command) {
1675
1766
  const f = parseShared(command);
1676
1767
  if (f === PARSE_FAIL) return null;
@@ -2398,8 +2489,9 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2398
2489
  }
2399
2490
  }
2400
2491
  if (config.policy.smartRules.length > 0) {
2492
+ const rmCleanupWaiver = bashCommand !== null && isRmCreatedInCommandCleanup(bashCommand);
2401
2493
  const matches = config.policy.smartRules.filter(
2402
- (rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
2494
+ (rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && !(rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") && evaluateSmartConditions(args, rule)
2403
2495
  );
2404
2496
  const matchedRule = resolvePinned(matches);
2405
2497
  if (matchedRule) {
package/dist/index.mjs CHANGED
@@ -1321,7 +1321,7 @@ function analyzeSqlDestructive(command) {
1321
1321
  description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
1322
1322
  };
1323
1323
  }
1324
- var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx", "+x"]);
1324
+ var CHMOD_OPEN_PERM_TOKENS = /* @__PURE__ */ new Set(["777", "0777", "a+rwx"]);
1325
1325
  var COMMAND_WRAPPERS = /* @__PURE__ */ new Set([
1326
1326
  "sudo",
1327
1327
  "doas",
@@ -1641,6 +1641,97 @@ function analyzeFsOperation(command) {
1641
1641
  fsOpCache.set(normalized, computed);
1642
1642
  return computed;
1643
1643
  }
1644
+ var stripDotSlash = (p) => p.replace(/^\.\//, "");
1645
+ function isSensitiveCleanupName(p) {
1646
+ const base = p.replace(/^.*[\\/]/, "");
1647
+ return /^\.env(\.|$)/i.test(base) || /(?:^|[\\/])\.(?:ssh|aws|gnupg|git)(?:[\\/]|$)/i.test(p) || /\.(?:pem|key|p12|pfx|crt)$/i.test(base) || /^\.?(?:netrc|npmrc|pgpass|htpasswd)$/i.test(base) || /^id_(?:rsa|dsa|ecdsa|ed25519)/i.test(base) || /credential/i.test(p) || /secret/i.test(base);
1648
+ }
1649
+ function isWaivableCleanupTarget(p) {
1650
+ if (/^[/~]/.test(p) || /^\$/.test(p)) return false;
1651
+ if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(p)) return false;
1652
+ if (/[*?[{]/.test(p)) return false;
1653
+ if (isSensitiveCleanupName(p)) return false;
1654
+ return true;
1655
+ }
1656
+ function deriveRedirOp(sample) {
1657
+ try {
1658
+ const f = sharedParser.Parse(sample, "cmd");
1659
+ let op = -1;
1660
+ syntax.Walk(f, (node) => {
1661
+ const n = node;
1662
+ if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
1663
+ return true;
1664
+ });
1665
+ return op;
1666
+ } catch {
1667
+ return -1;
1668
+ }
1669
+ }
1670
+ var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
1671
+ var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
1672
+ deriveRedirOp("cat <<X\nX"),
1673
+ deriveRedirOp("cat <<-X\nX")
1674
+ ]);
1675
+ function collectSameCommandCreations(f) {
1676
+ const created = /* @__PURE__ */ new Set();
1677
+ try {
1678
+ const stmts = Array.isArray(f?.Stmts) ? f.Stmts : [];
1679
+ for (const stmt of stmts) {
1680
+ if (!stmt || !stmt.Cmd || syntax.NodeType(stmt.Cmd) !== "CallExpr") continue;
1681
+ const redirs = stmt.Redirs || [];
1682
+ if (!redirs.some((r) => r && REDIR_HEREDOC_OPS.has(r.Op))) continue;
1683
+ for (const r of redirs) {
1684
+ if (r && REDIR_TRUNCATE_OPS.has(r.Op) && r.N == null) {
1685
+ const w = resolveWordLiteral(r.Word);
1686
+ if (w) created.add(stripDotSlash(w));
1687
+ }
1688
+ }
1689
+ }
1690
+ } catch {
1691
+ return created;
1692
+ }
1693
+ return created;
1694
+ }
1695
+ function isRmCreatedInCommandCleanup(command) {
1696
+ if (!/\brm\b/.test(command)) return false;
1697
+ const f = parseShared(command);
1698
+ if (f === PARSE_FAIL) return false;
1699
+ const created = collectSameCommandCreations(f);
1700
+ if (created.size === 0) return false;
1701
+ let sawRm = false;
1702
+ let ok = true;
1703
+ try {
1704
+ syntax.Walk(f, (node) => {
1705
+ if (!node || !ok) return false;
1706
+ const n = node;
1707
+ if (syntax.NodeType(n) !== "CallExpr") return true;
1708
+ const args = n.Args || [];
1709
+ const name = (resolveWordLiteral(args[0]) ?? "").toLowerCase();
1710
+ if (name !== "rm") return true;
1711
+ sawRm = true;
1712
+ const { flags, paths } = extractLiteralArgs(n);
1713
+ if (args.length - 1 > flags.length + paths.length) {
1714
+ ok = false;
1715
+ return false;
1716
+ }
1717
+ if (paths.length === 0) {
1718
+ ok = false;
1719
+ return false;
1720
+ }
1721
+ for (const p of paths) {
1722
+ const np = stripDotSlash(p);
1723
+ if (!created.has(np) || !isWaivableCleanupTarget(np)) {
1724
+ ok = false;
1725
+ return false;
1726
+ }
1727
+ }
1728
+ return true;
1729
+ });
1730
+ } catch {
1731
+ return false;
1732
+ }
1733
+ return sawRm && ok;
1734
+ }
1644
1735
  function analyzeFsOperationImpl(command) {
1645
1736
  const f = parseShared(command);
1646
1737
  if (f === PARSE_FAIL) return null;
@@ -2368,8 +2459,9 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
2368
2459
  }
2369
2460
  }
2370
2461
  if (config.policy.smartRules.length > 0) {
2462
+ const rmCleanupWaiver = bashCommand !== null && isRmCreatedInCommandCleanup(bashCommand);
2371
2463
  const matches = config.policy.smartRules.filter(
2372
- (rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
2464
+ (rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && !(rmCleanupWaiver && rule.name === "review-rm" && rule.verdict === "review") && evaluateSmartConditions(args, rule)
2373
2465
  );
2374
2466
  const matchedRule = resolvePinned(matches);
2375
2467
  if (matchedRule) {
package/dist/scan-ink.mjs CHANGED
@@ -818,6 +818,25 @@ function assertBuiltinPatternsAreSafe() {
818
818
  assertBuiltinPatternsAreSafe();
819
819
  var { syntax } = mvdanSh;
820
820
  var sharedParser = syntax.NewParser();
821
+ function deriveRedirOp(sample) {
822
+ try {
823
+ const f = sharedParser.Parse(sample, "cmd");
824
+ let op = -1;
825
+ syntax.Walk(f, (node) => {
826
+ const n = node;
827
+ if (n && syntax.NodeType(n) === "Redirect" && op < 0) op = n.Op;
828
+ return true;
829
+ });
830
+ return op;
831
+ } catch {
832
+ return -1;
833
+ }
834
+ }
835
+ var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
836
+ var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
837
+ deriveRedirOp("cat <<X\nX"),
838
+ deriveRedirOp("cat <<-X\nX")
839
+ ]);
821
840
  var aws_default = {
822
841
  name: "aws",
823
842
  description: "Protects AWS infrastructure from destructive AI operations",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.61.0",
3
+ "version": "1.62.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",