@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.js
CHANGED
|
@@ -888,9 +888,70 @@ var MESSAGE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
888
888
|
]);
|
|
889
889
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish", "dash", "ksh"]);
|
|
890
890
|
var DOWNLOAD_CMDS = /* @__PURE__ */ new Set(["curl", "wget"]);
|
|
891
|
+
function isCatHeredocOrLit(part) {
|
|
892
|
+
if (!part) return false;
|
|
893
|
+
const t = syntax.NodeType(part);
|
|
894
|
+
if (t === "Lit") return true;
|
|
895
|
+
if (t !== "CmdSubst") return false;
|
|
896
|
+
const stmts = part.Stmts || [];
|
|
897
|
+
if (stmts.length !== 1) return false;
|
|
898
|
+
const stmt = stmts[0];
|
|
899
|
+
const redirs = stmt.Redirs || stmt.Cmd?.Redirs || [];
|
|
900
|
+
const hasHeredoc = redirs.some((r) => r && r.Hdoc);
|
|
901
|
+
if (!hasHeredoc) return false;
|
|
902
|
+
const cmd = stmt.Cmd;
|
|
903
|
+
if (!cmd || syntax.NodeType(cmd) !== "CallExpr") return false;
|
|
904
|
+
const firstArg = cmd.Args?.[0]?.Parts || [];
|
|
905
|
+
if (firstArg.length !== 1 || syntax.NodeType(firstArg[0]) !== "Lit") return false;
|
|
906
|
+
return (firstArg[0].Value || "").toLowerCase() === "cat";
|
|
907
|
+
}
|
|
908
|
+
var NORMALIZE_CACHE_MAX = 5e3;
|
|
909
|
+
var normalizeCache = /* @__PURE__ */ new Map();
|
|
910
|
+
var AST_CACHE_MAX = 5e3;
|
|
911
|
+
var astCache = /* @__PURE__ */ new Map();
|
|
912
|
+
var PARSE_FAIL = /* @__PURE__ */ Symbol("parse-fail");
|
|
913
|
+
function parseShared(command) {
|
|
914
|
+
const cached = astCache.get(command);
|
|
915
|
+
if (cached !== void 0) {
|
|
916
|
+
astCache.delete(command);
|
|
917
|
+
astCache.set(command, cached);
|
|
918
|
+
return cached;
|
|
919
|
+
}
|
|
920
|
+
let parsed;
|
|
921
|
+
try {
|
|
922
|
+
parsed = sharedParser.Parse(command, "cmd");
|
|
923
|
+
} catch {
|
|
924
|
+
parsed = PARSE_FAIL;
|
|
925
|
+
}
|
|
926
|
+
if (astCache.size >= AST_CACHE_MAX) {
|
|
927
|
+
const oldest = astCache.keys().next().value;
|
|
928
|
+
if (oldest !== void 0) astCache.delete(oldest);
|
|
929
|
+
}
|
|
930
|
+
astCache.set(command, parsed);
|
|
931
|
+
return parsed;
|
|
932
|
+
}
|
|
933
|
+
function cachedNormalize(command, compute) {
|
|
934
|
+
const hit = normalizeCache.get(command);
|
|
935
|
+
if (hit !== void 0) {
|
|
936
|
+
normalizeCache.delete(command);
|
|
937
|
+
normalizeCache.set(command, hit);
|
|
938
|
+
return hit;
|
|
939
|
+
}
|
|
940
|
+
const result = compute();
|
|
941
|
+
if (normalizeCache.size >= NORMALIZE_CACHE_MAX) {
|
|
942
|
+
const oldest = normalizeCache.keys().next().value;
|
|
943
|
+
if (oldest !== void 0) normalizeCache.delete(oldest);
|
|
944
|
+
}
|
|
945
|
+
normalizeCache.set(command, result);
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
891
948
|
function normalizeCommandForPolicy(command) {
|
|
949
|
+
return cachedNormalize(command, () => normalizeCommandForPolicyImpl(command));
|
|
950
|
+
}
|
|
951
|
+
function normalizeCommandForPolicyImpl(command) {
|
|
952
|
+
const f = parseShared(command);
|
|
953
|
+
if (f === PARSE_FAIL) return command;
|
|
892
954
|
try {
|
|
893
|
-
const f = sharedParser.Parse(command, "cmd");
|
|
894
955
|
const strips = [];
|
|
895
956
|
syntax.Walk(f, (node) => {
|
|
896
957
|
if (!node) return false;
|
|
@@ -912,7 +973,11 @@ function normalizeCommandForPolicy(command) {
|
|
|
912
973
|
} else if (nt === "DblQuoted") {
|
|
913
974
|
const innerParts = quotedNode.Parts || [];
|
|
914
975
|
const allLit = innerParts.length === 0 || innerParts.every((p) => syntax.NodeType(p) === "Lit");
|
|
915
|
-
if (allLit)
|
|
976
|
+
if (allLit) {
|
|
977
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
978
|
+
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
979
|
+
strips.push([next.Pos().Offset(), next.End().Offset()]);
|
|
980
|
+
}
|
|
916
981
|
}
|
|
917
982
|
}
|
|
918
983
|
return true;
|
|
@@ -980,6 +1045,202 @@ function detectDangerousShellExec(command) {
|
|
|
980
1045
|
return null;
|
|
981
1046
|
}
|
|
982
1047
|
}
|
|
1048
|
+
var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
1049
|
+
"cat",
|
|
1050
|
+
"less",
|
|
1051
|
+
"head",
|
|
1052
|
+
"tail",
|
|
1053
|
+
"bat",
|
|
1054
|
+
"more",
|
|
1055
|
+
"open",
|
|
1056
|
+
"print",
|
|
1057
|
+
"nano",
|
|
1058
|
+
"vim",
|
|
1059
|
+
"vi",
|
|
1060
|
+
"emacs",
|
|
1061
|
+
"code",
|
|
1062
|
+
"type"
|
|
1063
|
+
]);
|
|
1064
|
+
var FS_OP_PRESCREEN_RE = /(?:^|[\s|;&(`\n])(?:rm|cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\b/;
|
|
1065
|
+
var HOME_CACHE_ALLOWLIST = [
|
|
1066
|
+
".cache",
|
|
1067
|
+
".npm/_npx",
|
|
1068
|
+
".npm/_cacache",
|
|
1069
|
+
".cargo/registry",
|
|
1070
|
+
".gradle/caches",
|
|
1071
|
+
".gradle/.tmp",
|
|
1072
|
+
".m2/repository",
|
|
1073
|
+
".pnpm-store",
|
|
1074
|
+
".yarn/cache",
|
|
1075
|
+
".yarn/.cache",
|
|
1076
|
+
".cache/pip",
|
|
1077
|
+
".local/share/Trash",
|
|
1078
|
+
".rustup/downloads"
|
|
1079
|
+
];
|
|
1080
|
+
var SENSITIVE_PATH_RULES = [
|
|
1081
|
+
{
|
|
1082
|
+
rule: "shield:project-jail:block-read-ssh",
|
|
1083
|
+
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1084
|
+
match: (p) => /(^|[\\/])\.ssh[\\/]/i.test(p)
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
rule: "shield:project-jail:block-read-aws",
|
|
1088
|
+
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
1089
|
+
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
rule: "shield:project-jail:block-read-env",
|
|
1093
|
+
reason: "Reading .env files is blocked by project-jail shield",
|
|
1094
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.local|\.production|\.staging)?$/i.test(p)
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
rule: "shield:project-jail:block-read-credentials",
|
|
1098
|
+
reason: "Reading credential files is blocked by project-jail shield",
|
|
1099
|
+
match: (p) => /(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials)$/i.test(
|
|
1100
|
+
p
|
|
1101
|
+
)
|
|
1102
|
+
}
|
|
1103
|
+
];
|
|
1104
|
+
var BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1105
|
+
"bash",
|
|
1106
|
+
"execute_bash",
|
|
1107
|
+
"run_shell_command",
|
|
1108
|
+
"shell",
|
|
1109
|
+
"exec_command"
|
|
1110
|
+
]);
|
|
1111
|
+
function isBashTool(toolName) {
|
|
1112
|
+
return BASH_TOOL_NAMES.has(toolName.toLowerCase());
|
|
1113
|
+
}
|
|
1114
|
+
var AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
1115
|
+
"block-rm-rf-home",
|
|
1116
|
+
"shield:project-jail:block-read-ssh",
|
|
1117
|
+
"shield:project-jail:block-read-aws",
|
|
1118
|
+
"shield:project-jail:block-read-env",
|
|
1119
|
+
"shield:project-jail:block-read-credentials"
|
|
1120
|
+
]);
|
|
1121
|
+
function isProtectedHomePath(rawPath) {
|
|
1122
|
+
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
1123
|
+
let underHome = false;
|
|
1124
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
|
|
1125
|
+
p = p.replace(/^~[\\/]?/, "");
|
|
1126
|
+
underHome = true;
|
|
1127
|
+
} else if (/^\/home\/[^/]+/.test(p) || /^\/root(\/|$)/.test(p)) {
|
|
1128
|
+
p = p.replace(/^\/home\/[^/]+[\\/]?|^\/root[\\/]?/, "");
|
|
1129
|
+
underHome = true;
|
|
1130
|
+
}
|
|
1131
|
+
if (!underHome) return false;
|
|
1132
|
+
if (p === "" || p === "." || p === "./") return true;
|
|
1133
|
+
for (const safe of HOME_CACHE_ALLOWLIST) {
|
|
1134
|
+
if (p === safe || p.startsWith(safe + "/") || p.startsWith(safe + "\\")) {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return true;
|
|
1139
|
+
}
|
|
1140
|
+
function extractLiteralArgs(callExpr) {
|
|
1141
|
+
const args = callExpr.Args || [];
|
|
1142
|
+
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
1143
|
+
const litFromWord = (w) => {
|
|
1144
|
+
const parts = w?.Parts || [];
|
|
1145
|
+
let s = "";
|
|
1146
|
+
for (const p of parts) {
|
|
1147
|
+
const t = syntax.NodeType(p);
|
|
1148
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1149
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1150
|
+
else if (t === "DblQuoted") {
|
|
1151
|
+
const inner = p.Parts || [];
|
|
1152
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1153
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1154
|
+
} else {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return s;
|
|
1159
|
+
};
|
|
1160
|
+
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
1161
|
+
const flags = [];
|
|
1162
|
+
const paths = [];
|
|
1163
|
+
for (let i = 1; i < args.length; i++) {
|
|
1164
|
+
const v = litFromWord(args[i]);
|
|
1165
|
+
if (v === null) continue;
|
|
1166
|
+
if (v.startsWith("-")) flags.push(v);
|
|
1167
|
+
else paths.push(v);
|
|
1168
|
+
}
|
|
1169
|
+
return { name, flags, paths };
|
|
1170
|
+
}
|
|
1171
|
+
var FS_OP_CACHE_MAX = 5e3;
|
|
1172
|
+
var fsOpCache = /* @__PURE__ */ new Map();
|
|
1173
|
+
function analyzeFsOperation(command) {
|
|
1174
|
+
if (!FS_OP_PRESCREEN_RE.test(command)) return null;
|
|
1175
|
+
if (fsOpCache.has(command)) {
|
|
1176
|
+
const hit = fsOpCache.get(command) ?? null;
|
|
1177
|
+
fsOpCache.delete(command);
|
|
1178
|
+
fsOpCache.set(command, hit);
|
|
1179
|
+
return hit;
|
|
1180
|
+
}
|
|
1181
|
+
const computed = analyzeFsOperationImpl(command);
|
|
1182
|
+
if (fsOpCache.size >= FS_OP_CACHE_MAX) {
|
|
1183
|
+
const oldest = fsOpCache.keys().next().value;
|
|
1184
|
+
if (oldest !== void 0) fsOpCache.delete(oldest);
|
|
1185
|
+
}
|
|
1186
|
+
fsOpCache.set(command, computed);
|
|
1187
|
+
return computed;
|
|
1188
|
+
}
|
|
1189
|
+
function analyzeFsOperationImpl(command) {
|
|
1190
|
+
const f = parseShared(command);
|
|
1191
|
+
if (f === PARSE_FAIL) return null;
|
|
1192
|
+
let result = null;
|
|
1193
|
+
try {
|
|
1194
|
+
syntax.Walk(f, (node) => {
|
|
1195
|
+
if (!node || result) return false;
|
|
1196
|
+
const n = node;
|
|
1197
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1198
|
+
const { name, flags, paths } = extractLiteralArgs(n);
|
|
1199
|
+
if (!name) return true;
|
|
1200
|
+
if (name === "rm") {
|
|
1201
|
+
const flagStr = flags.join("").toLowerCase();
|
|
1202
|
+
const hasR = /[r]/.test(flagStr) || flags.includes("--recursive");
|
|
1203
|
+
const hasF = /[f]/.test(flagStr) || flags.includes("--force");
|
|
1204
|
+
if (hasR && hasF) {
|
|
1205
|
+
for (const p of paths) {
|
|
1206
|
+
if (isProtectedHomePath(p)) {
|
|
1207
|
+
result = {
|
|
1208
|
+
ruleName: "block-rm-rf-home",
|
|
1209
|
+
verdict: "block",
|
|
1210
|
+
reason: "Recursive delete of home directory is irreversible",
|
|
1211
|
+
path: p
|
|
1212
|
+
};
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
if (p === "/" || /^\/+$/.test(p)) {
|
|
1216
|
+
result = {
|
|
1217
|
+
ruleName: "block-rm-rf-home",
|
|
1218
|
+
verdict: "block",
|
|
1219
|
+
reason: "Recursive delete of root is catastrophic",
|
|
1220
|
+
path: p
|
|
1221
|
+
};
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (FS_READ_TOOLS.has(name)) {
|
|
1228
|
+
for (const p of paths) {
|
|
1229
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1230
|
+
if (sp.match(p)) {
|
|
1231
|
+
result = { ruleName: sp.rule, verdict: "block", reason: sp.reason, path: p };
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return true;
|
|
1238
|
+
});
|
|
1239
|
+
return result;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
983
1244
|
function analyzeShellCommand(command) {
|
|
984
1245
|
const actions = [];
|
|
985
1246
|
const paths = [];
|
|
@@ -1401,10 +1662,18 @@ function getNestedValue(obj, path13) {
|
|
|
1401
1662
|
function evaluateSmartConditions(args, rule) {
|
|
1402
1663
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
1403
1664
|
const mode = rule.conditionMode ?? "all";
|
|
1665
|
+
const fieldCache = /* @__PURE__ */ new Map();
|
|
1666
|
+
const resolveField = (field) => {
|
|
1667
|
+
if (fieldCache.has(field)) return fieldCache.get(field) ?? null;
|
|
1668
|
+
const rawVal = getNestedValue(args, field);
|
|
1669
|
+
const rawStr = rawVal !== null && rawVal !== void 0 ? String(rawVal) : null;
|
|
1670
|
+
const stripped = field === "command" && rawStr !== null ? normalizeCommandForPolicy(rawStr) : rawStr;
|
|
1671
|
+
const val = stripped !== null ? stripped.replace(/\s+/g, " ").trim() : null;
|
|
1672
|
+
fieldCache.set(field, val);
|
|
1673
|
+
return val;
|
|
1674
|
+
};
|
|
1404
1675
|
const results = rule.conditions.map((cond) => {
|
|
1405
|
-
const
|
|
1406
|
-
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1407
|
-
const val = cond.field === "command" && normalized !== null ? normalizeCommandForPolicy(normalized) : normalized;
|
|
1676
|
+
const val = resolveField(cond.field);
|
|
1408
1677
|
switch (cond.op) {
|
|
1409
1678
|
case "exists":
|
|
1410
1679
|
return val !== null && val !== "";
|
|
@@ -1456,6 +1725,43 @@ function isSqlTool(toolName, toolInspection) {
|
|
|
1456
1725
|
return fieldName === "sql" || fieldName === "query";
|
|
1457
1726
|
}
|
|
1458
1727
|
var SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
1728
|
+
function pipeChainVerdict(command, isTrustedHost2) {
|
|
1729
|
+
const pipeAnalysis = analyzePipeChain(command);
|
|
1730
|
+
if (!pipeAnalysis.isPipeline) return null;
|
|
1731
|
+
if (pipeAnalysis.risk !== "critical" && pipeAnalysis.risk !== "high") return null;
|
|
1732
|
+
const sinks = pipeAnalysis.sinkTargets;
|
|
1733
|
+
const allTrusted = sinks.length > 0 && sinks.every((host) => isTrustedHost2 ? isTrustedHost2(host) : false);
|
|
1734
|
+
if (pipeAnalysis.risk === "critical") {
|
|
1735
|
+
if (allTrusted) {
|
|
1736
|
+
return {
|
|
1737
|
+
decision: "review",
|
|
1738
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1739
|
+
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1740
|
+
tier: 3
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
return {
|
|
1744
|
+
decision: "block",
|
|
1745
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1746
|
+
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1747
|
+
tier: 3
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
if (allTrusted) {
|
|
1751
|
+
return {
|
|
1752
|
+
decision: "allow",
|
|
1753
|
+
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1754
|
+
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1755
|
+
tier: 3
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
return {
|
|
1759
|
+
decision: "review",
|
|
1760
|
+
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1761
|
+
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1762
|
+
tier: 3
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1459
1765
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
1460
1766
|
const { agent, cwd, activeEnvironment } = context;
|
|
1461
1767
|
const { checkProvenance: checkProvenance2, isTrustedHost: isTrustedHost2 } = hooks;
|
|
@@ -1471,9 +1777,27 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1471
1777
|
}
|
|
1472
1778
|
}
|
|
1473
1779
|
if (wouldBeIgnored) return { decision: "allow" };
|
|
1780
|
+
const bashCommand = agent !== "Terminal" && isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : null;
|
|
1781
|
+
if (bashCommand !== null) {
|
|
1782
|
+
const pipeVerdict = pipeChainVerdict(bashCommand, isTrustedHost2);
|
|
1783
|
+
if (pipeVerdict) return pipeVerdict;
|
|
1784
|
+
const fsVerdict = analyzeFsOperation(bashCommand);
|
|
1785
|
+
if (fsVerdict) {
|
|
1786
|
+
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
1787
|
+
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
1788
|
+
return {
|
|
1789
|
+
decision: fsVerdict.verdict,
|
|
1790
|
+
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
1791
|
+
reason: fsVerdict.reason,
|
|
1792
|
+
tier: 2,
|
|
1793
|
+
ruleName: fsVerdict.ruleName,
|
|
1794
|
+
ruleDescription: fsVerdict.reason
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1474
1798
|
if (config.policy.smartRules.length > 0) {
|
|
1475
1799
|
const matchedRule = config.policy.smartRules.find(
|
|
1476
|
-
(rule) => matchesPattern(toolName, rule.tool) && evaluateSmartConditions(args, rule)
|
|
1800
|
+
(rule) => matchesPattern(toolName, rule.tool) && !(bashCommand !== null && rule.name && AST_FS_REGEX_RULES.has(rule.name)) && evaluateSmartConditions(args, rule)
|
|
1477
1801
|
);
|
|
1478
1802
|
if (matchedRule) {
|
|
1479
1803
|
if (matchedRule.verdict === "allow")
|
|
@@ -1531,41 +1855,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1531
1855
|
tier: 3
|
|
1532
1856
|
};
|
|
1533
1857
|
}
|
|
1534
|
-
const
|
|
1535
|
-
if (
|
|
1536
|
-
const sinks = pipeAnalysis.sinkTargets;
|
|
1537
|
-
const allTrusted = sinks.length > 0 && sinks.every((host) => isTrustedHost2 ? isTrustedHost2(host) : false);
|
|
1538
|
-
if (pipeAnalysis.risk === "critical") {
|
|
1539
|
-
if (allTrusted) {
|
|
1540
|
-
return {
|
|
1541
|
-
decision: "review",
|
|
1542
|
-
blockedByLabel: "Node9: Pipe-Chain to Trusted Host (obfuscated)",
|
|
1543
|
-
reason: `Obfuscated pipe to trusted host(s): ${sinks.join(", ")} \u2014 requires approval`,
|
|
1544
|
-
tier: 3
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
return {
|
|
1548
|
-
decision: "block",
|
|
1549
|
-
blockedByLabel: "Node9: Pipe-Chain Exfiltration (critical)",
|
|
1550
|
-
reason: `Sensitive file piped through obfuscator to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1551
|
-
tier: 3
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
if (allTrusted) {
|
|
1555
|
-
return {
|
|
1556
|
-
decision: "allow",
|
|
1557
|
-
blockedByLabel: "Node9: Pipe-Chain to Trusted Host",
|
|
1558
|
-
reason: `Sensitive file piped to trusted host(s): ${sinks.join(", ")}`,
|
|
1559
|
-
tier: 3
|
|
1560
|
-
};
|
|
1561
|
-
}
|
|
1562
|
-
return {
|
|
1563
|
-
decision: "review",
|
|
1564
|
-
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
1565
|
-
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
1566
|
-
tier: 3
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1858
|
+
const ptVerdict = pipeChainVerdict(shellCommand, isTrustedHost2);
|
|
1859
|
+
if (ptVerdict) return ptVerdict;
|
|
1569
1860
|
const firstToken = analyzed.actions[0] ?? "";
|
|
1570
1861
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1571
1862
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
@@ -2431,6 +2722,7 @@ function evaluateLoopWindow(records, tool, args, threshold, windowMs, now) {
|
|
|
2431
2722
|
const nextRecords = fresh.slice(-LOOP_MAX_RECORDS);
|
|
2432
2723
|
return { nextRecords, count, looping: count >= threshold };
|
|
2433
2724
|
}
|
|
2725
|
+
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
2434
2726
|
|
|
2435
2727
|
// src/shields.ts
|
|
2436
2728
|
var USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
|