@node9/policy-engine 2.9.3 → 2.10.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/index.d.mts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +95 -7
- package/dist/index.mjs +91 -7
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1254,6 +1254,13 @@ declare const SSRF_MAX_HOST = 253;
|
|
|
1254
1254
|
* id are stripped here: the caller does NOT strip them (measured).
|
|
1255
1255
|
*/
|
|
1256
1256
|
declare function normalizeIpLiteral(host: string): string | null;
|
|
1257
|
+
/** Is this tier reachable by default, waiting on the `ssrfStrict` knob?
|
|
1258
|
+
* Exported so the non-shell path asks the SAME question rather than
|
|
1259
|
+
* approximating it with `overridable`, which is a different property. */
|
|
1260
|
+
declare function isStrictGatedTier(tier: SsrfTier): boolean;
|
|
1261
|
+
/** The sentence a user reads when the floor stops them. One builder, so the
|
|
1262
|
+
* shell path and the non-shell path cannot word the same block differently. */
|
|
1263
|
+
declare function ssrfReason(m: SsrfMatch, asWritten: string): string;
|
|
1257
1264
|
/**
|
|
1258
1265
|
* Classify a destination host. Returns null when it is not a protected address,
|
|
1259
1266
|
* which means the ordinary egress policy decides. Never throws.
|
|
@@ -1288,7 +1295,36 @@ declare function ssrfFloor(tokens: ReadonlyArray<{
|
|
|
1288
1295
|
binary: string;
|
|
1289
1296
|
}>, opts?: SsrfFloorOptions): SsrfVerdict | null;
|
|
1290
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* Tools whose named argument really is a destination the agent is about to
|
|
1300
|
+
* reach. Keyed on the BARE tool name (an `mcp__server__` prefix is stripped
|
|
1301
|
+
* before matching), because the browser tools arrive over MCP.
|
|
1302
|
+
*
|
|
1303
|
+
* Derived from real history, not imagination: walking 240,058 audit entries
|
|
1304
|
+
* produced exactly these argument paths. A tool is here because its network
|
|
1305
|
+
* semantics are declared by its name, not because it happens to carry a key
|
|
1306
|
+
* called `url` (founder call, 2026-09-08: ignore those).
|
|
1307
|
+
*
|
|
1308
|
+
* `[]` in a path means "every element of this array".
|
|
1309
|
+
*/
|
|
1310
|
+
declare const DESTINATION_ARGS: ReadonlyMap<string, readonly string[]>;
|
|
1311
|
+
interface DestinationHit extends SsrfMatch {
|
|
1312
|
+
/** The path that carried it, for the message and the audit row. */
|
|
1313
|
+
argPath: string;
|
|
1314
|
+
host: string;
|
|
1315
|
+
reason: string;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Judge a non-shell tool call. Returns the first protected destination, or
|
|
1319
|
+
* null. Never throws: this runs on the hook path, where an exception would
|
|
1320
|
+
* break every tool call on the machine.
|
|
1321
|
+
*/
|
|
1322
|
+
declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
1323
|
+
ssrfStrict?: boolean;
|
|
1324
|
+
ssrfAllow?: readonly string[];
|
|
1325
|
+
}): DestinationHit | null;
|
|
1326
|
+
|
|
1291
1327
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1292
1328
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1293
1329
|
|
|
1294
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfFloor, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1330
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTINATION_ARGS, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -1254,6 +1254,13 @@ declare const SSRF_MAX_HOST = 253;
|
|
|
1254
1254
|
* id are stripped here: the caller does NOT strip them (measured).
|
|
1255
1255
|
*/
|
|
1256
1256
|
declare function normalizeIpLiteral(host: string): string | null;
|
|
1257
|
+
/** Is this tier reachable by default, waiting on the `ssrfStrict` knob?
|
|
1258
|
+
* Exported so the non-shell path asks the SAME question rather than
|
|
1259
|
+
* approximating it with `overridable`, which is a different property. */
|
|
1260
|
+
declare function isStrictGatedTier(tier: SsrfTier): boolean;
|
|
1261
|
+
/** The sentence a user reads when the floor stops them. One builder, so the
|
|
1262
|
+
* shell path and the non-shell path cannot word the same block differently. */
|
|
1263
|
+
declare function ssrfReason(m: SsrfMatch, asWritten: string): string;
|
|
1257
1264
|
/**
|
|
1258
1265
|
* Classify a destination host. Returns null when it is not a protected address,
|
|
1259
1266
|
* which means the ordinary egress policy decides. Never throws.
|
|
@@ -1288,7 +1295,36 @@ declare function ssrfFloor(tokens: ReadonlyArray<{
|
|
|
1288
1295
|
binary: string;
|
|
1289
1296
|
}>, opts?: SsrfFloorOptions): SsrfVerdict | null;
|
|
1290
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* Tools whose named argument really is a destination the agent is about to
|
|
1300
|
+
* reach. Keyed on the BARE tool name (an `mcp__server__` prefix is stripped
|
|
1301
|
+
* before matching), because the browser tools arrive over MCP.
|
|
1302
|
+
*
|
|
1303
|
+
* Derived from real history, not imagination: walking 240,058 audit entries
|
|
1304
|
+
* produced exactly these argument paths. A tool is here because its network
|
|
1305
|
+
* semantics are declared by its name, not because it happens to carry a key
|
|
1306
|
+
* called `url` (founder call, 2026-09-08: ignore those).
|
|
1307
|
+
*
|
|
1308
|
+
* `[]` in a path means "every element of this array".
|
|
1309
|
+
*/
|
|
1310
|
+
declare const DESTINATION_ARGS: ReadonlyMap<string, readonly string[]>;
|
|
1311
|
+
interface DestinationHit extends SsrfMatch {
|
|
1312
|
+
/** The path that carried it, for the message and the audit row. */
|
|
1313
|
+
argPath: string;
|
|
1314
|
+
host: string;
|
|
1315
|
+
reason: string;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Judge a non-shell tool call. Returns the first protected destination, or
|
|
1319
|
+
* null. Never throws: this runs on the hook path, where an exception would
|
|
1320
|
+
* break every tool call on the machine.
|
|
1321
|
+
*/
|
|
1322
|
+
declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
1323
|
+
ssrfStrict?: boolean;
|
|
1324
|
+
ssrfAllow?: readonly string[];
|
|
1325
|
+
}): DestinationHit | null;
|
|
1326
|
+
|
|
1291
1327
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1292
1328
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1293
1329
|
|
|
1294
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfFloor, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1330
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTINATION_ARGS, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(src_exports, {
|
|
|
38
38
|
CANONICAL_EXTRACTOR_VERSION: () => CANONICAL_EXTRACTOR_VERSION,
|
|
39
39
|
COST_PER_LOOP_ITER_USD: () => COST_PER_LOOP_ITER_USD,
|
|
40
40
|
DEFAULT_EGRESS_ALLOWLIST: () => DEFAULT_EGRESS_ALLOWLIST,
|
|
41
|
+
DESTINATION_ARGS: () => DESTINATION_ARGS,
|
|
41
42
|
DESTRUCTIVE_OP_RE: () => DESTRUCTIVE_OP_RE,
|
|
42
43
|
DLP_PATTERNS: () => DLP_PATTERNS,
|
|
43
44
|
DLP_SCAN_LIMITS: () => DLP_SCAN_LIMITS,
|
|
@@ -93,6 +94,7 @@ __export(src_exports, {
|
|
|
93
94
|
isProtectedHomePath: () => isProtectedHomePath,
|
|
94
95
|
isShellShapedTool: () => isShellShapedTool,
|
|
95
96
|
isShieldVerdict: () => isShieldVerdict,
|
|
97
|
+
isStrictGatedTier: () => isStrictGatedTier,
|
|
96
98
|
matchCanary: () => matchCanary,
|
|
97
99
|
matchCanaryArgs: () => matchCanaryArgs,
|
|
98
100
|
matchSensitivePath: () => matchSensitivePath,
|
|
@@ -109,7 +111,9 @@ __export(src_exports, {
|
|
|
109
111
|
scanInjection: () => scanInjection,
|
|
110
112
|
scanText: () => scanText,
|
|
111
113
|
sensitivePathMatch: () => sensitivePathMatch,
|
|
114
|
+
ssrfDestinationFloor: () => ssrfDestinationFloor,
|
|
112
115
|
ssrfFloor: () => ssrfFloor,
|
|
116
|
+
ssrfReason: () => ssrfReason,
|
|
113
117
|
summarizeBlast: () => summarizeBlast,
|
|
114
118
|
summarizeScan: () => summarizeScan,
|
|
115
119
|
toScanFinding: () => toScanFinding,
|
|
@@ -2875,6 +2879,12 @@ var METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
|
2875
2879
|
"100.100.100.200"
|
|
2876
2880
|
]);
|
|
2877
2881
|
var STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
2882
|
+
function isStrictGatedTier(tier) {
|
|
2883
|
+
return STRICT_TIERS.has(tier);
|
|
2884
|
+
}
|
|
2885
|
+
function ssrfReason(m, asWritten) {
|
|
2886
|
+
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2887
|
+
}
|
|
2878
2888
|
var METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
2879
2889
|
var v4Octets = (a) => {
|
|
2880
2890
|
const p = a.split(".");
|
|
@@ -2933,18 +2943,76 @@ function ssrfFloor(tokens, opts = {}) {
|
|
|
2933
2943
|
for (const { token, binary } of tokens) {
|
|
2934
2944
|
const m = classifySsrf(token);
|
|
2935
2945
|
if (!m) continue;
|
|
2936
|
-
if (
|
|
2946
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2937
2947
|
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
2938
|
-
return {
|
|
2939
|
-
...m,
|
|
2940
|
-
host: token,
|
|
2941
|
-
binary,
|
|
2942
|
-
reason: `Blocked: ${token} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== token ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.")
|
|
2943
|
-
};
|
|
2948
|
+
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2944
2949
|
}
|
|
2945
2950
|
return null;
|
|
2946
2951
|
}
|
|
2947
2952
|
|
|
2953
|
+
// src/egress/destinations.ts
|
|
2954
|
+
var DESTINATION_ARGS = /* @__PURE__ */ new Map([
|
|
2955
|
+
["webfetch", ["url"]],
|
|
2956
|
+
["fetch", ["url", "uri"]],
|
|
2957
|
+
["navigate", ["url"]],
|
|
2958
|
+
["preview_start", ["url"]],
|
|
2959
|
+
["browser_batch", ["actions[].input.url"]]
|
|
2960
|
+
]);
|
|
2961
|
+
function bareToolName(toolName) {
|
|
2962
|
+
const parts = toolName.split("__");
|
|
2963
|
+
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
2964
|
+
}
|
|
2965
|
+
function valuesAt(args, path) {
|
|
2966
|
+
let cursors = [args];
|
|
2967
|
+
for (const rawSegment of path.split(".")) {
|
|
2968
|
+
const isArray = rawSegment.endsWith("[]");
|
|
2969
|
+
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
2970
|
+
const next = [];
|
|
2971
|
+
for (const cursor of cursors) {
|
|
2972
|
+
if (cursor === null || typeof cursor !== "object") continue;
|
|
2973
|
+
const child = cursor[key];
|
|
2974
|
+
if (isArray) {
|
|
2975
|
+
if (Array.isArray(child)) next.push(...child);
|
|
2976
|
+
} else if (child !== void 0) {
|
|
2977
|
+
next.push(child);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
cursors = next;
|
|
2981
|
+
if (cursors.length === 0) return [];
|
|
2982
|
+
}
|
|
2983
|
+
return cursors.filter((v) => typeof v === "string");
|
|
2984
|
+
}
|
|
2985
|
+
function hostOf(value) {
|
|
2986
|
+
try {
|
|
2987
|
+
const u = new URL(value);
|
|
2988
|
+
const h = u.hostname;
|
|
2989
|
+
return h.startsWith("[") && h.endsWith("]") ? h.slice(1, -1) : h;
|
|
2990
|
+
} catch {
|
|
2991
|
+
return null;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
2995
|
+
try {
|
|
2996
|
+
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
2997
|
+
if (!paths) return null;
|
|
2998
|
+
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
2999
|
+
for (const path of paths) {
|
|
3000
|
+
for (const value of valuesAt(args, path)) {
|
|
3001
|
+
const host = hostOf(value);
|
|
3002
|
+
if (!host) continue;
|
|
3003
|
+
const m = classifySsrf(host);
|
|
3004
|
+
if (!m) continue;
|
|
3005
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3006
|
+
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
3007
|
+
return { ...m, argPath: path, host, reason: ssrfReason(m, host) };
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return null;
|
|
3011
|
+
} catch {
|
|
3012
|
+
return null;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
|
|
2948
3016
|
// src/policy/index.ts
|
|
2949
3017
|
function resolveCheck(v) {
|
|
2950
3018
|
return v === "off" || v === "block" ? v : "review";
|
|
@@ -3058,6 +3126,22 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
3058
3126
|
};
|
|
3059
3127
|
}
|
|
3060
3128
|
}
|
|
3129
|
+
{
|
|
3130
|
+
const dest = ssrfDestinationFloor(toolName, args, {
|
|
3131
|
+
ssrfAllow: config.policy.egress?.ssrfAllow,
|
|
3132
|
+
ssrfStrict: config.policy.egress?.ssrfStrict
|
|
3133
|
+
});
|
|
3134
|
+
if (dest) {
|
|
3135
|
+
return {
|
|
3136
|
+
decision: "block",
|
|
3137
|
+
blockedByLabel: "\u{1F310} Node9 Egress (Protected Address)",
|
|
3138
|
+
reason: dest.reason,
|
|
3139
|
+
ruleName: `ssrf:${dest.tier}:${toolName}:${dest.host}`,
|
|
3140
|
+
ruleDescription: dest.reason,
|
|
3141
|
+
tier: 3
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3061
3145
|
if (wouldBeIgnored && !context.skipIgnoredFastPath) return { decision: "allow" };
|
|
3062
3146
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
3063
3147
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
@@ -5061,6 +5145,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5061
5145
|
CANONICAL_EXTRACTOR_VERSION,
|
|
5062
5146
|
COST_PER_LOOP_ITER_USD,
|
|
5063
5147
|
DEFAULT_EGRESS_ALLOWLIST,
|
|
5148
|
+
DESTINATION_ARGS,
|
|
5064
5149
|
DESTRUCTIVE_OP_RE,
|
|
5065
5150
|
DLP_PATTERNS,
|
|
5066
5151
|
DLP_SCAN_LIMITS,
|
|
@@ -5116,6 +5201,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5116
5201
|
isProtectedHomePath,
|
|
5117
5202
|
isShellShapedTool,
|
|
5118
5203
|
isShieldVerdict,
|
|
5204
|
+
isStrictGatedTier,
|
|
5119
5205
|
matchCanary,
|
|
5120
5206
|
matchCanaryArgs,
|
|
5121
5207
|
matchSensitivePath,
|
|
@@ -5132,7 +5218,9 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5132
5218
|
scanInjection,
|
|
5133
5219
|
scanText,
|
|
5134
5220
|
sensitivePathMatch,
|
|
5221
|
+
ssrfDestinationFloor,
|
|
5135
5222
|
ssrfFloor,
|
|
5223
|
+
ssrfReason,
|
|
5136
5224
|
summarizeBlast,
|
|
5137
5225
|
summarizeScan,
|
|
5138
5226
|
toScanFinding,
|
package/dist/index.mjs
CHANGED
|
@@ -2752,6 +2752,12 @@ var METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
|
2752
2752
|
"100.100.100.200"
|
|
2753
2753
|
]);
|
|
2754
2754
|
var STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
2755
|
+
function isStrictGatedTier(tier) {
|
|
2756
|
+
return STRICT_TIERS.has(tier);
|
|
2757
|
+
}
|
|
2758
|
+
function ssrfReason(m, asWritten) {
|
|
2759
|
+
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
2760
|
+
}
|
|
2755
2761
|
var METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
2756
2762
|
var v4Octets = (a) => {
|
|
2757
2763
|
const p = a.split(".");
|
|
@@ -2810,18 +2816,76 @@ function ssrfFloor(tokens, opts = {}) {
|
|
|
2810
2816
|
for (const { token, binary } of tokens) {
|
|
2811
2817
|
const m = classifySsrf(token);
|
|
2812
2818
|
if (!m) continue;
|
|
2813
|
-
if (
|
|
2819
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2814
2820
|
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
2815
|
-
return {
|
|
2816
|
-
...m,
|
|
2817
|
-
host: token,
|
|
2818
|
-
binary,
|
|
2819
|
-
reason: `Blocked: ${token} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== token ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.")
|
|
2820
|
-
};
|
|
2821
|
+
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
2821
2822
|
}
|
|
2822
2823
|
return null;
|
|
2823
2824
|
}
|
|
2824
2825
|
|
|
2826
|
+
// src/egress/destinations.ts
|
|
2827
|
+
var DESTINATION_ARGS = /* @__PURE__ */ new Map([
|
|
2828
|
+
["webfetch", ["url"]],
|
|
2829
|
+
["fetch", ["url", "uri"]],
|
|
2830
|
+
["navigate", ["url"]],
|
|
2831
|
+
["preview_start", ["url"]],
|
|
2832
|
+
["browser_batch", ["actions[].input.url"]]
|
|
2833
|
+
]);
|
|
2834
|
+
function bareToolName(toolName) {
|
|
2835
|
+
const parts = toolName.split("__");
|
|
2836
|
+
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
2837
|
+
}
|
|
2838
|
+
function valuesAt(args, path) {
|
|
2839
|
+
let cursors = [args];
|
|
2840
|
+
for (const rawSegment of path.split(".")) {
|
|
2841
|
+
const isArray = rawSegment.endsWith("[]");
|
|
2842
|
+
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
2843
|
+
const next = [];
|
|
2844
|
+
for (const cursor of cursors) {
|
|
2845
|
+
if (cursor === null || typeof cursor !== "object") continue;
|
|
2846
|
+
const child = cursor[key];
|
|
2847
|
+
if (isArray) {
|
|
2848
|
+
if (Array.isArray(child)) next.push(...child);
|
|
2849
|
+
} else if (child !== void 0) {
|
|
2850
|
+
next.push(child);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
cursors = next;
|
|
2854
|
+
if (cursors.length === 0) return [];
|
|
2855
|
+
}
|
|
2856
|
+
return cursors.filter((v) => typeof v === "string");
|
|
2857
|
+
}
|
|
2858
|
+
function hostOf(value) {
|
|
2859
|
+
try {
|
|
2860
|
+
const u = new URL(value);
|
|
2861
|
+
const h = u.hostname;
|
|
2862
|
+
return h.startsWith("[") && h.endsWith("]") ? h.slice(1, -1) : h;
|
|
2863
|
+
} catch {
|
|
2864
|
+
return null;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
2868
|
+
try {
|
|
2869
|
+
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
2870
|
+
if (!paths) return null;
|
|
2871
|
+
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
2872
|
+
for (const path of paths) {
|
|
2873
|
+
for (const value of valuesAt(args, path)) {
|
|
2874
|
+
const host = hostOf(value);
|
|
2875
|
+
if (!host) continue;
|
|
2876
|
+
const m = classifySsrf(host);
|
|
2877
|
+
if (!m) continue;
|
|
2878
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
2879
|
+
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
2880
|
+
return { ...m, argPath: path, host, reason: ssrfReason(m, host) };
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
return null;
|
|
2884
|
+
} catch {
|
|
2885
|
+
return null;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2825
2889
|
// src/policy/index.ts
|
|
2826
2890
|
function resolveCheck(v) {
|
|
2827
2891
|
return v === "off" || v === "block" ? v : "review";
|
|
@@ -2935,6 +2999,22 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2935
2999
|
};
|
|
2936
3000
|
}
|
|
2937
3001
|
}
|
|
3002
|
+
{
|
|
3003
|
+
const dest = ssrfDestinationFloor(toolName, args, {
|
|
3004
|
+
ssrfAllow: config.policy.egress?.ssrfAllow,
|
|
3005
|
+
ssrfStrict: config.policy.egress?.ssrfStrict
|
|
3006
|
+
});
|
|
3007
|
+
if (dest) {
|
|
3008
|
+
return {
|
|
3009
|
+
decision: "block",
|
|
3010
|
+
blockedByLabel: "\u{1F310} Node9 Egress (Protected Address)",
|
|
3011
|
+
reason: dest.reason,
|
|
3012
|
+
ruleName: `ssrf:${dest.tier}:${toolName}:${dest.host}`,
|
|
3013
|
+
ruleDescription: dest.reason,
|
|
3014
|
+
tier: 3
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
2938
3018
|
if (wouldBeIgnored && !context.skipIgnoredFastPath) return { decision: "allow" };
|
|
2939
3019
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
2940
3020
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
@@ -4937,6 +5017,7 @@ export {
|
|
|
4937
5017
|
CANONICAL_EXTRACTOR_VERSION,
|
|
4938
5018
|
COST_PER_LOOP_ITER_USD,
|
|
4939
5019
|
DEFAULT_EGRESS_ALLOWLIST,
|
|
5020
|
+
DESTINATION_ARGS,
|
|
4940
5021
|
DESTRUCTIVE_OP_RE,
|
|
4941
5022
|
DLP_PATTERNS,
|
|
4942
5023
|
DLP_SCAN_LIMITS,
|
|
@@ -4992,6 +5073,7 @@ export {
|
|
|
4992
5073
|
isProtectedHomePath,
|
|
4993
5074
|
isShellShapedTool,
|
|
4994
5075
|
isShieldVerdict,
|
|
5076
|
+
isStrictGatedTier,
|
|
4995
5077
|
matchCanary,
|
|
4996
5078
|
matchCanaryArgs,
|
|
4997
5079
|
matchSensitivePath,
|
|
@@ -5008,7 +5090,9 @@ export {
|
|
|
5008
5090
|
scanInjection,
|
|
5009
5091
|
scanText,
|
|
5010
5092
|
sensitivePathMatch,
|
|
5093
|
+
ssrfDestinationFloor,
|
|
5011
5094
|
ssrfFloor,
|
|
5095
|
+
ssrfReason,
|
|
5012
5096
|
summarizeBlast,
|
|
5013
5097
|
summarizeScan,
|
|
5014
5098
|
toScanFinding,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Shared policy evaluation engine for node9 — DLP, smart rules, AST shell parsing, shields, loop detection. Pure functions, no I/O. Used by both node9-proxy and the node9 SaaS firewall.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://node9.ai",
|