@node9/proxy 1.18.2 → 1.19.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 +1163 -152
- package/dist/cli.mjs +1163 -152
- package/dist/index.js +333 -41
- package/dist/index.mjs +333 -41
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -858,9 +858,70 @@ var MESSAGE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
858
858
|
]);
|
|
859
859
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish", "dash", "ksh"]);
|
|
860
860
|
var DOWNLOAD_CMDS = /* @__PURE__ */ new Set(["curl", "wget"]);
|
|
861
|
+
function isCatHeredocOrLit(part) {
|
|
862
|
+
if (!part) return false;
|
|
863
|
+
const t = syntax.NodeType(part);
|
|
864
|
+
if (t === "Lit") return true;
|
|
865
|
+
if (t !== "CmdSubst") return false;
|
|
866
|
+
const stmts = part.Stmts || [];
|
|
867
|
+
if (stmts.length !== 1) return false;
|
|
868
|
+
const stmt = stmts[0];
|
|
869
|
+
const redirs = stmt.Redirs || stmt.Cmd?.Redirs || [];
|
|
870
|
+
const hasHeredoc = redirs.some((r) => r && r.Hdoc);
|
|
871
|
+
if (!hasHeredoc) return false;
|
|
872
|
+
const cmd = stmt.Cmd;
|
|
873
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return false;
|
|
874
|
+
const firstArg = cmd.Args?.[0]?.Parts || [];
|
|
875
|
+
if (firstArg.length !== 1 || syntax.NodeType(firstArg[0]) !== "Lit") return false;
|
|
876
|
+
return (firstArg[0].Value || "").toLowerCase() === "cat";
|
|
877
|
+
}
|
|
878
|
+
var NORMALIZE_CACHE_MAX = 5e3;
|
|
879
|
+
var normalizeCache = /* @__PURE__ */ new Map();
|
|
880
|
+
var AST_CACHE_MAX = 5e3;
|
|
881
|
+
var astCache = /* @__PURE__ */ new Map();
|
|
882
|
+
var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
883
|
+
function parseShared(command) {
|
|
884
|
+
const cached = astCache.get(command);
|
|
885
|
+
if (cached !== void 0) {
|
|
886
|
+
astCache.delete(command);
|
|
887
|
+
astCache.set(command, cached);
|
|
888
|
+
return cached;
|
|
889
|
+
}
|
|
890
|
+
let parsed;
|
|
891
|
+
try {
|
|
892
|
+
parsed = sharedParser.Parse(command, "cmd");
|
|
893
|
+
} catch {
|
|
894
|
+
parsed = PARSE_FAIL;
|
|
895
|
+
}
|
|
896
|
+
if (astCache.size >= AST_CACHE_MAX) {
|
|
897
|
+
const oldest = astCache.keys().next().value;
|
|
898
|
+
if (oldest !== void 0) astCache.delete(oldest);
|
|
899
|
+
}
|
|
900
|
+
astCache.set(command, parsed);
|
|
901
|
+
return parsed;
|
|
902
|
+
}
|
|
903
|
+
function cachedNormalize(command, compute) {
|
|
904
|
+
const hit = normalizeCache.get(command);
|
|
905
|
+
if (hit !== void 0) {
|
|
906
|
+
normalizeCache.delete(command);
|
|
907
|
+
normalizeCache.set(command, hit);
|
|
908
|
+
return hit;
|
|
909
|
+
}
|
|
910
|
+
const result = compute();
|
|
911
|
+
if (normalizeCache.size >= NORMALIZE_CACHE_MAX) {
|
|
912
|
+
const oldest = normalizeCache.keys().next().value;
|
|
913
|
+
if (oldest !== void 0) normalizeCache.delete(oldest);
|
|
914
|
+
}
|
|
915
|
+
normalizeCache.set(command, result);
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
861
918
|
function normalizeCommandForPolicy(command) {
|
|
919
|
+
return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
|
|
920
|
+
}
|
|
921
|
+
function normalizeCommandForPolicyImpl(command) {
|
|
922
|
+
const f = parseShared(command);
|
|
923
|
+
if (f === PARSE_FAIL) return command;
|
|
862
924
|
try {
|
|
863
|
-
const f = sharedParser.Parse(command, "cmd");
|
|
864
925
|
const strips = [];
|
|
865
926
|
syntax.Walk(f, (node) => {
|
|
866
927
|
if (!node) return false;
|
|
@@ -882,7 +943,11 @@ function normalizeCommandForPolicy(command) {
|
|
|
882
943
|
} else if (nt === "DblQuoted") {
|
|
883
944
|
const innerParts = quotedNode.Parts || [];
|
|
884
945
|
const allLit = innerParts.length === 0 || innerParts.every((p) => syntax.NodeType(p) === "Lit");
|
|
885
|
-
if (allLit)
|
|
946
|
+
if (allLit) {
|
|
947
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
948
|
+
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
949
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
950
|
+
}
|
|
886
951
|
}
|
|
887
952
|
}
|
|
888
953
|
return true;
|
|
@@ -950,6 +1015,202 @@ function detectDangerousShellExec(command) {
|
|
|
950
1015
|
return null;
|
|
951
1016
|
}
|
|
952
1017
|
}
|
|
1018
|
+
var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
1019
|
+
"cat",
|
|
1020
|
+
"less",
|
|
1021
|
+
"head",
|
|
1022
|
+
"tail",
|
|
1023
|
+
"bat",
|
|
1024
|
+
"more",
|
|
1025
|
+
"open",
|
|
1026
|
+
"print",
|
|
1027
|
+
"nano",
|
|
1028
|
+
"vim",
|
|
1029
|
+
"vi",
|
|
1030
|
+
"emacs",
|
|
1031
|
+
"code",
|
|
1032
|
+
"type"
|
|
1033
|
+
]);
|
|
1034
|
+
var FS_OP_PRESCREEN_RE = /(?:^|[\s|;&(`\n])(?:rm|cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\b/;
|
|
1035
|
+
var HOME_CACHE_ALLOWLIST = [
|
|
1036
|
+
".cache",
|
|
1037
|
+
".npm/_npx",
|
|
1038
|
+
".npm/_cacache",
|
|
1039
|
+
".cargo/registry",
|
|
1040
|
+
".gradle/caches",
|
|
1041
|
+
".gradle/.tmp",
|
|
1042
|
+
".m2/repository",
|
|
1043
|
+
".pnpm-store",
|
|
1044
|
+
".yarn/cache",
|
|
1045
|
+
".yarn/.cache",
|
|
1046
|
+
".cache/pip",
|
|
1047
|
+
".local/share/Trash",
|
|
1048
|
+
".rustup/downloads"
|
|
1049
|
+
];
|
|
1050
|
+
var SENSITIVE_PATH_RULES = [
|
|
1051
|
+
{
|
|
1052
|
+
rule: "shield:project-jail:block-read-ssh",
|
|
1053
|
+
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1054
|
+
match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
rule: "shield:project-jail:block-read-aws",
|
|
1058
|
+
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
1059
|
+
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
rule: "shield:project-jail:block-read-env",
|
|
1063
|
+
reason: "Reading .env files is blocked by project-jail shield",
|
|
1064
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.local|\.production|\.staging)?$/i.test(p)
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
rule: "shield:project-jail:block-read-credentials",
|
|
1068
|
+
reason: "Reading credential files is blocked by project-jail shield",
|
|
1069
|
+
match: (p) => /(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials)$/i.test(
|
|
1070
|
+
p
|
|
1071
|
+
)
|
|
1072
|
+
}
|
|
1073
|
+
];
|
|
1074
|
+
var BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1075
|
+
"bash",
|
|
1076
|
+
"execute_bash",
|
|
1077
|
+
"run_shell_command",
|
|
1078
|
+
"shell",
|
|
1079
|
+
"exec_command"
|
|
1080
|
+
]);
|
|
1081
|
+
function isBashTool(toolName) {
|
|
1082
|
+
return BASH_TOOL_NAMES.has(toolName.toLowerCase());
|
|
1083
|
+
}
|
|
1084
|
+
var AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
1085
|
+
"block-rm-rf-home",
|
|
1086
|
+
"shield:project-jail:block-read-ssh",
|
|
1087
|
+
"shield:project-jail:block-read-aws",
|
|
1088
|
+
"shield:project-jail:block-read-env",
|
|
1089
|
+
"shield:project-jail:block-read-credentials"
|
|
1090
|
+
]);
|
|
1091
|
+
function isProtectedHomePath(rawPath) {
|
|
1092
|
+
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
1093
|
+
let underHome = false;
|
|
1094
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
|
|
1095
|
+
p = p.replace(/^~[\\/]?/, "");
|
|
1096
|
+
underHome = true;
|
|
1097
|
+
} else if (/^\/home\/[^/]+/.test(p) || /^\/root(\/|$)/.test(p)) {
|
|
1098
|
+
p = p.replace(/^\/home\/[^/]+[\\/]?|^\/root[\\/]?/, "");
|
|
1099
|
+
underHome = true;
|
|
1100
|
+
}
|
|
1101
|
+
if (!underHome) return false;
|
|
1102
|
+
if (p === "" || p === "." || p === "./") return true;
|
|
1103
|
+
for (const safe of HOME_CACHE_ALLOWLIST) {
|
|
1104
|
+
if (p === safe || p.startsWith(safe + "/") || p.startsWith(safe + "\\")) {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
function extractLiteralArgs(callExpr) {
|
|
1111
|
+
const args = callExpr.Args || [];
|
|
1112
|
+
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
1113
|
+
const litFromWord = (w) => {
|
|
1114
|
+
const parts = w?.Parts || [];
|
|
1115
|
+
let s = "";
|
|
1116
|
+
for (const p of parts) {
|
|
1117
|
+
const t = syntax.NodeType(p);
|
|
1118
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1119
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1120
|
+
else if (t === "DblQuoted") {
|
|
1121
|
+
const inner = p.Parts || [];
|
|
1122
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1123
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1124
|
+
} else {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return s;
|
|
1129
|
+
};
|
|
1130
|
+
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
1131
|
+
const flags = [];
|
|
1132
|
+
const paths = [];
|
|
1133
|
+
for (let i = 1; i < args.length; i++) {
|
|
1134
|
+
const v = litFromWord(args[i]);
|
|
1135
|
+
if (v === null) continue;
|
|
1136
|
+
if (v.startsWith("-")) flags.push(v);
|
|
1137
|
+
else paths.push(v);
|
|
1138
|
+
}
|
|
1139
|
+
return { name, flags, paths };
|
|
1140
|
+
}
|
|
1141
|
+
var FS_OP_CACHE_MAX = 5e3;
|
|
1142
|
+
var fsOpCache = /* @__PURE__ */ new Map();
|
|
1143
|
+
function analyzeFsOperation(command) {
|
|
1144
|
+
if (!FS_OP_PRESCREEN_RE.test(command)) return null;
|
|
1145
|
+
if (fsOpCache.has(command)) {
|
|
1146
|
+
const hit = fsOpCache.get(command) ?? null;
|
|
1147
|
+
fsOpCache.delete(command);
|
|
1148
|
+
fsOpCache.set(command, hit);
|
|
1149
|
+
return hit;
|
|
1150
|
+
}
|
|
1151
|
+
const computed = analyzeFsOperationImpl(command);
|
|
1152
|
+
if (fsOpCache.size >= FS_OP_CACHE_MAX) {
|
|
1153
|
+
const oldest = fsOpCache.keys().next().value;
|
|
1154
|
+
if (oldest !== void 0) fsOpCache.delete(oldest);
|
|
1155
|
+
}
|
|
1156
|
+
fsOpCache.set(command, computed);
|
|
1157
|
+
return computed;
|
|
1158
|
+
}
|
|
1159
|
+
function analyzeFsOperationImpl(command) {
|
|
1160
|
+
const f = parseShared(command);
|
|
1161
|
+
if (f === PARSE_FAIL) return null;
|
|
1162
|
+
let result = null;
|
|
1163
|
+
try {
|
|
1164
|
+
syntax.Walk(f, (node) => {
|
|
1165
|
+
if (!node || result) return false;
|
|
1166
|
+
const n = node;
|
|
1167
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1168
|
+
const { name, flags, paths } = extractLiteralArgs(n);
|
|
1169
|
+
if (!name) return true;
|
|
1170
|
+
if (name === "rm") {
|
|
1171
|
+
const flagStr = flags.join("").toLowerCase();
|
|
1172
|
+
const hasR = /[r]/.test(flagStr) || flags.includes("--recursive");
|
|
1173
|
+
const hasF = /[f]/.test(flagStr) || flags.includes("--force");
|
|
1174
|
+
if (hasR && hasF) {
|
|
1175
|
+
for (const p of paths) {
|
|
1176
|
+
if (isProtectedHomePath(p)) {
|
|
1177
|
+
result = {
|
|
1178
|
+
ruleName: "block-rm-rf-home",
|
|
1179
|
+
verdict: "block",
|
|
1180
|
+
reason: "Recursive delete of home directory is irreversible",
|
|
1181
|
+
path: p
|
|
1182
|
+
};
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
if (p === "/" || /^\/+$/.test(p)) {
|
|
1186
|
+
result = {
|
|
1187
|
+
ruleName: "block-rm-rf-home",
|
|
1188
|
+
verdict: "block",
|
|
1189
|
+
reason: "Recursive delete of root is catastrophic",
|
|
1190
|
+
path: p
|
|
1191
|
+
};
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (FS_READ_TOOLS.has(name)) {
|
|
1198
|
+
for (const p of paths) {
|
|
1199
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1200
|
+
if (sp.match(p)) {
|
|
1201
|
+
result = { ruleName: sp.rule, verdict: "block", reason: sp.reason, path: p };
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return true;
|
|
1208
|
+
});
|
|
1209
|
+
return result;
|
|
1210
|
+
} catch {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
953
1214
|
function analyzeShellCommand(command) {
|
|
954
1215
|
const actions = [];
|
|
955
1216
|
const paths = [];
|
|
@@ -1371,10 +1632,18 @@ function getNestedValue(obj, path13) {
|
|
|
1371
1632
|
function evaluateSmartConditions(args, rule) {
|
|
1372
1633
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
1373
1634
|
const mode = rule.conditionMode ?? "all";
|
|
1635
|
+
const fieldCache = /* @__PURE__ */ new Map();
|
|
1636
|
+
const resolveField = (field) => {
|
|
1637
|
+
if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
|
|
1638
|
+
const rawVal = getNestedValue(args, field);
|
|
1639
|
+
const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
|
|
1640
|
+
const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
|
|
1641
|
+
const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
|
|
1642
|
+
fieldCache.set(field, val);
|
|
1643
|
+
return val;
|
|
1644
|
+
};
|
|
1374
1645
|
const results = rule.conditions.map((cond) => {
|
|
1375
|
-
const
|
|
1376
|
-
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1377
|
-
const val = cond.field === "command" && normalized !== null ? normalizeCommandForPolicy(normalized) : normalized;
|
|
1646
|
+
const val = resolveField(cond.field);
|
|
1378
1647
|
switch (cond.op) {
|
|
1379
1648
|
case "exists":
|
|
1380
1649
|
return val !== null && val !== "";
|
|
@@ -1426,6 +1695,43 @@ function isSqlTool(toolName, toolInspection) {
|
|
|
1426
1695
|
return fieldName === "sql" || fieldName === "query";
|
|
1427
1696
|
}
|
|
1428
1697
|
var SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
1698
|
+
function pipeChainVerdict(command, isTrustedHost2) {
|
|
1699
|
+
const pipeAnalysis = analyzePipeChain(command);
|
|
1700
|
+
if (!pipeAnalysis.isPipeline) return null;
|
|
1701
|
+
if (pipeAnalysis.risk !== "critical" && pipeAnalysis.risk !== "high") return null;
|
|
1702
|
+
const sinks = pipeAnalysis.sinkTargets;
|
|
1703
|
+
const allTrusted = sinks.length > 0 && sinks.every((host) => isTrustedHost2 ? isTrustedHost2(host) : false);
|
|
1704
|
+
if (pipeAnalysis.risk === "critical") {
|
|
1705
|
+
if (allTrusted) {
|
|
1706
|
+
return {
|
|
1707
|
+
decision: "review",
|
|
1708
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1709
|
+
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1710
|
+
tier: 3
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
return {
|
|
1714
|
+
decision: "block",
|
|
1715
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1716
|
+
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1717
|
+
tier: 3
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
if (allTrusted) {
|
|
1721
|
+
return {
|
|
1722
|
+
decision: "allow",
|
|
1723
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1724
|
+
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1725
|
+
tier: 3
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
return {
|
|
1729
|
+
decision: "review",
|
|
1730
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1731
|
+
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1732
|
+
tier: 3
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1429
1735
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
1430
1736
|
const { agent, cwd, activeEnvironment } = context;
|
|
1431
1737
|
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2 } = hooks;
|
|
@@ -1441,9 +1747,27 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1441
1747
|
}
|
|
1442
1748
|
}
|
|
1443
1749
|
if (wouldBeIgnored) return { decision: "allow" };
|
|
1750
|
+
const bashCommand = agent !== "Terminal" && isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : null;
|
|
1751
|
+
if (bashCommand !== null) {
|
|
1752
|
+
const pipeVerdict = pipeChainVerdict(bashCommand, isTrustedHost2);
|
|
1753
|
+
if (pipeVerdict) return pipeVerdict;
|
|
1754
|
+
const fsVerdict = analyzeFsOperation(bashCommand);
|
|
1755
|
+
if (fsVerdict) {
|
|
1756
|
+
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
1757
|
+
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
1758
|
+
return {
|
|
1759
|
+
decision: fsVerdict.verdict,
|
|
1760
|
+
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
1761
|
+
reason: fsVerdict.reason,
|
|
1762
|
+
tier: 2,
|
|
1763
|
+
ruleName: fsVerdict.ruleName,
|
|
1764
|
+
ruleDescription: fsVerdict.reason
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1444
1768
|
if (config.policy.smartRules.length > 0) {
|
|
1445
1769
|
const matchedRule = config.policy.smartRules.find(
|
|
1446
|
-
(rule) => matchesPattern(toolName, rule.tool) && evaluateSmartConditions(args, rule)
|
|
1770
|
+
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
|
|
1447
1771
|
);
|
|
1448
1772
|
if (matchedRule) {
|
|
1449
1773
|
if (matchedRule.verdict === "allow")
|
|
@@ -1501,41 +1825,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1501
1825
|
tier: 3
|
|
1502
1826
|
};
|
|
1503
1827
|
}
|
|
1504
|
-
const
|
|
1505
|
-
if (
|
|
1506
|
-
const sinks = pipeAnalysis.sinkTargets;
|
|
1507
|
-
const allTrusted = sinks.length > 0 && sinks.every((host) => isTrustedHost2 ? isTrustedHost2(host) : false);
|
|
1508
|
-
if (pipeAnalysis.risk === "critical") {
|
|
1509
|
-
if (allTrusted) {
|
|
1510
|
-
return {
|
|
1511
|
-
decision: "review",
|
|
1512
|
-
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1513
|
-
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1514
|
-
tier: 3
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
return {
|
|
1518
|
-
decision: "block",
|
|
1519
|
-
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1520
|
-
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1521
|
-
tier: 3
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
if (allTrusted) {
|
|
1525
|
-
return {
|
|
1526
|
-
decision: "allow",
|
|
1527
|
-
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1528
|
-
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1529
|
-
tier: 3
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
return {
|
|
1533
|
-
decision: "review",
|
|
1534
|
-
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1535
|
-
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1536
|
-
tier: 3
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1828
|
+
const ptVerdict = pipeChainVerdict(shellCommand, isTrustedHost2);
|
|
1829
|
+
if (ptVerdict) return ptVerdict;
|
|
1539
1830
|
const firstToken = analyzed.actions[0] ?? "";
|
|
1540
1831
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1541
1832
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
@@ -2401,6 +2692,7 @@ function evaluateLoopWindow(records, tool, args, threshold, windowMs, now) {
|
|
|
2401
2692
|
const nextRecords = fresh.slice(-LOOP_MAX_RECORDS);
|
|
2402
2693
|
return { nextRecords, count, looping: count >= threshold };
|
|
2403
2694
|
}
|
|
2695
|
+
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
2404
2696
|
|
|
2405
2697
|
// src/shields.ts
|
|
2406
2698
|
var USER_SHIELDS_DIR = path2.join(os2.homedir(), ".node9", "shields");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
"lint:fix": "eslint . --fix",
|
|
60
60
|
"format": "prettier --write .",
|
|
61
61
|
"format:check": "prettier --check .",
|
|
62
|
+
"check:extractor-version": "node scripts/check-extractor-version.mjs",
|
|
63
|
+
"bump-extractor-version": "node scripts/check-extractor-version.mjs --bump",
|
|
62
64
|
"fix": "npm run format && npm run lint:fix",
|
|
63
65
|
"validate": "npm run format && npm run lint && npm run typecheck && npm run test && npm run test:e2e && npm run build",
|
|
64
66
|
"test:e2e": "cross-env NODE9_TESTING=1 bash scripts/e2e.sh",
|