@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.
- package/dist/cli.js +1054 -634
- package/dist/cli.mjs +1048 -628
- package/dist/dashboard.mjs +20 -1
- package/dist/index.js +94 -2
- package/dist/index.mjs +94 -2
- package/dist/scan-ink.mjs +19 -0
- package/package.json +1 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -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"
|
|
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"
|
|
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.
|
|
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",
|