@node9/policy-engine 2.17.0 → 2.18.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 +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +36 -6
- package/dist/index.mjs +35 -6
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1438,6 +1438,33 @@ interface SsrfFloorOptions {
|
|
|
1438
1438
|
* those constantly (72 of 308 destinations on measured real history). */
|
|
1439
1439
|
ssrfStrict?: boolean;
|
|
1440
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Does this exemption list release this address?
|
|
1443
|
+
*
|
|
1444
|
+
* ONE function, because the shell floor and the declared-URL floor must answer
|
|
1445
|
+
* identically. They had two spellings of it — `normalizeIpLiteral(e) ?? e.trim()
|
|
1446
|
+
* .toLowerCase()` here and `classifySsrf(a)?.normalized ?? a` in
|
|
1447
|
+
* destinations.ts — and for an entry classifySsrf does not match they already
|
|
1448
|
+
* disagreed. Nothing depended on the difference, which is exactly when it is
|
|
1449
|
+
* cheap to remove.
|
|
1450
|
+
*
|
|
1451
|
+
* An entry is an exact address or a CIDR range. The range form exists so an
|
|
1452
|
+
* operator can exempt the mesh-VPN range they actually use (100.64.0.0/10)
|
|
1453
|
+
* instead of listing peers one at a time, which is what the settings panel's
|
|
1454
|
+
* "remove this default" control needs.
|
|
1455
|
+
*
|
|
1456
|
+
* ⚠ This answers "is it in the list", NOT "may it be released". The caller
|
|
1457
|
+
* keeps its `m.overridable` guard, and that guard is per ADDRESS, decided by
|
|
1458
|
+
* classifySsrf before this is consulted. So a range can never release a
|
|
1459
|
+
* protected address inside it: 100.100.100.200 (Alibaba IMDS) sits inside
|
|
1460
|
+
* 100.64.0.0/10 and is named in METADATA_ADDRESSES above the range check, so
|
|
1461
|
+
* it classifies as metadata / overridable:false and stays blocked with the
|
|
1462
|
+
* whole range exempted. Pinned by E2 in exempt-range.spec.ts.
|
|
1463
|
+
*
|
|
1464
|
+
* Never throws: this runs on the hook path for every tool call, and a bad
|
|
1465
|
+
* entry in a hand-edited config must not break every command on the machine.
|
|
1466
|
+
*/
|
|
1467
|
+
declare function ssrfExemptMatches(entries: readonly string[] | undefined, normalized: string | undefined): boolean;
|
|
1441
1468
|
/**
|
|
1442
1469
|
* The floor. Returns the first protected destination, or null.
|
|
1443
1470
|
*
|
|
@@ -1501,4 +1528,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1501
1528
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1502
1529
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1503
1530
|
|
|
1504
|
-
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, COMMAND_WRAPPERS, COPY_VERBS, 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 Destination, 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, MAX_BLAST_PATH, NET_BINARIES, PATTERN_VERB_NAMES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type PositionedArg, 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, extractToolDestinations, fileOperandFlagsOf, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, patternShapeOf, positionedArgs, previewArgs, redactText, resolvePinned, safeMessage, sampleCopyCommand, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, stripControlChars, stripTerminalEscapes, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1531
|
+
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, COMMAND_WRAPPERS, COPY_VERBS, 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 Destination, 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, MAX_BLAST_PATH, NET_BINARIES, PATTERN_VERB_NAMES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type PositionedArg, 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, extractToolDestinations, fileOperandFlagsOf, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, patternShapeOf, positionedArgs, previewArgs, redactText, resolvePinned, safeMessage, sampleCopyCommand, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfExemptMatches, ssrfFloor, ssrfReason, stripControlChars, stripTerminalEscapes, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -1438,6 +1438,33 @@ interface SsrfFloorOptions {
|
|
|
1438
1438
|
* those constantly (72 of 308 destinations on measured real history). */
|
|
1439
1439
|
ssrfStrict?: boolean;
|
|
1440
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Does this exemption list release this address?
|
|
1443
|
+
*
|
|
1444
|
+
* ONE function, because the shell floor and the declared-URL floor must answer
|
|
1445
|
+
* identically. They had two spellings of it — `normalizeIpLiteral(e) ?? e.trim()
|
|
1446
|
+
* .toLowerCase()` here and `classifySsrf(a)?.normalized ?? a` in
|
|
1447
|
+
* destinations.ts — and for an entry classifySsrf does not match they already
|
|
1448
|
+
* disagreed. Nothing depended on the difference, which is exactly when it is
|
|
1449
|
+
* cheap to remove.
|
|
1450
|
+
*
|
|
1451
|
+
* An entry is an exact address or a CIDR range. The range form exists so an
|
|
1452
|
+
* operator can exempt the mesh-VPN range they actually use (100.64.0.0/10)
|
|
1453
|
+
* instead of listing peers one at a time, which is what the settings panel's
|
|
1454
|
+
* "remove this default" control needs.
|
|
1455
|
+
*
|
|
1456
|
+
* ⚠ This answers "is it in the list", NOT "may it be released". The caller
|
|
1457
|
+
* keeps its `m.overridable` guard, and that guard is per ADDRESS, decided by
|
|
1458
|
+
* classifySsrf before this is consulted. So a range can never release a
|
|
1459
|
+
* protected address inside it: 100.100.100.200 (Alibaba IMDS) sits inside
|
|
1460
|
+
* 100.64.0.0/10 and is named in METADATA_ADDRESSES above the range check, so
|
|
1461
|
+
* it classifies as metadata / overridable:false and stays blocked with the
|
|
1462
|
+
* whole range exempted. Pinned by E2 in exempt-range.spec.ts.
|
|
1463
|
+
*
|
|
1464
|
+
* Never throws: this runs on the hook path for every tool call, and a bad
|
|
1465
|
+
* entry in a hand-edited config must not break every command on the machine.
|
|
1466
|
+
*/
|
|
1467
|
+
declare function ssrfExemptMatches(entries: readonly string[] | undefined, normalized: string | undefined): boolean;
|
|
1441
1468
|
/**
|
|
1442
1469
|
* The floor. Returns the first protected destination, or null.
|
|
1443
1470
|
*
|
|
@@ -1501,4 +1528,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1501
1528
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1502
1529
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1503
1530
|
|
|
1504
|
-
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, COMMAND_WRAPPERS, COPY_VERBS, 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 Destination, 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, MAX_BLAST_PATH, NET_BINARIES, PATTERN_VERB_NAMES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type PositionedArg, 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, extractToolDestinations, fileOperandFlagsOf, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, patternShapeOf, positionedArgs, previewArgs, redactText, resolvePinned, safeMessage, sampleCopyCommand, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, stripControlChars, stripTerminalEscapes, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1531
|
+
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, COMMAND_WRAPPERS, COPY_VERBS, 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 Destination, 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, MAX_BLAST_PATH, NET_BINARIES, PATTERN_VERB_NAMES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type PositionedArg, 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, extractToolDestinations, fileOperandFlagsOf, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, patternShapeOf, positionedArgs, previewArgs, redactText, resolvePinned, safeMessage, sampleCopyCommand, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfExemptMatches, ssrfFloor, ssrfReason, stripControlChars, stripTerminalEscapes, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.js
CHANGED
|
@@ -123,6 +123,7 @@ __export(src_exports, {
|
|
|
123
123
|
scanText: () => scanText,
|
|
124
124
|
sensitivePathMatch: () => sensitivePathMatch,
|
|
125
125
|
ssrfDestinationFloor: () => ssrfDestinationFloor,
|
|
126
|
+
ssrfExemptMatches: () => ssrfExemptMatches,
|
|
126
127
|
ssrfFloor: () => ssrfFloor,
|
|
127
128
|
ssrfReason: () => ssrfReason,
|
|
128
129
|
stripControlChars: () => stripControlChars,
|
|
@@ -3399,6 +3400,38 @@ function classifySsrf(host) {
|
|
|
3399
3400
|
return null;
|
|
3400
3401
|
}
|
|
3401
3402
|
}
|
|
3403
|
+
function ssrfExemptMatches(entries, normalized) {
|
|
3404
|
+
if (!entries?.length || !normalized) return false;
|
|
3405
|
+
const target = bitsOf(normalized);
|
|
3406
|
+
for (const raw of entries) {
|
|
3407
|
+
const entry = raw.trim().toLowerCase();
|
|
3408
|
+
if (!entry) continue;
|
|
3409
|
+
const slash = entry.indexOf("/");
|
|
3410
|
+
if (slash === -1) {
|
|
3411
|
+
if ((normalizeIpLiteral(entry) ?? entry) === normalized) return true;
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
if (!target) continue;
|
|
3415
|
+
const base = bitsOf(normalizeIpLiteral(entry.slice(0, slash)) ?? "");
|
|
3416
|
+
const prefixText = entry.slice(slash + 1);
|
|
3417
|
+
const prefix = /^\d+$/.test(prefixText) ? Number(prefixText) : NaN;
|
|
3418
|
+
if (!base || !Number.isInteger(prefix) || prefix < 0 || // A v4 range never matches a v6 address, and the reverse: the widths
|
|
3419
|
+
// differ, so `0.0.0.0/0` does not release `::1`.
|
|
3420
|
+
base.length !== target.length || prefix > base.length) {
|
|
3421
|
+
continue;
|
|
3422
|
+
}
|
|
3423
|
+
if (base.slice(0, prefix) === target.slice(0, prefix)) return true;
|
|
3424
|
+
}
|
|
3425
|
+
return false;
|
|
3426
|
+
}
|
|
3427
|
+
function bitsOf(normalized) {
|
|
3428
|
+
const o = v4Octets(normalized);
|
|
3429
|
+
if (o) {
|
|
3430
|
+
return o.every((n) => Number.isInteger(n) && n >= 0 && n <= 255) ? o.map((n) => n.toString(2).padStart(8, "0")).join("") : null;
|
|
3431
|
+
}
|
|
3432
|
+
const g = expandIpv6(normalized);
|
|
3433
|
+
return g ? g.map((n) => n.toString(2).padStart(16, "0")).join("") : null;
|
|
3434
|
+
}
|
|
3402
3435
|
var TIER_REASON = {
|
|
3403
3436
|
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
3404
3437
|
"link-local": "a link-local address",
|
|
@@ -3408,14 +3441,11 @@ var TIER_REASON = {
|
|
|
3408
3441
|
private: "a loopback or private address"
|
|
3409
3442
|
};
|
|
3410
3443
|
function ssrfFloor(tokens, opts = {}) {
|
|
3411
|
-
const exempt = new Set(
|
|
3412
|
-
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
3413
|
-
);
|
|
3414
3444
|
for (const { token, binary } of tokens) {
|
|
3415
3445
|
const m = classifySsrf(token);
|
|
3416
3446
|
if (!m) continue;
|
|
3417
3447
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3418
|
-
if (m.overridable &&
|
|
3448
|
+
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3419
3449
|
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
3420
3450
|
}
|
|
3421
3451
|
return null;
|
|
@@ -3892,7 +3922,6 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3892
3922
|
try {
|
|
3893
3923
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3894
3924
|
if (!paths) return null;
|
|
3895
|
-
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
3896
3925
|
for (const path of paths) {
|
|
3897
3926
|
for (const value of valuesAt(args, path)) {
|
|
3898
3927
|
const host = hostOf(value);
|
|
@@ -3900,7 +3929,7 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3900
3929
|
const m = classifySsrf(host);
|
|
3901
3930
|
if (!m) continue;
|
|
3902
3931
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3903
|
-
if (m.overridable &&
|
|
3932
|
+
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3904
3933
|
return { ...m, argPath: path, host, reason: ssrfReason(m, host) };
|
|
3905
3934
|
}
|
|
3906
3935
|
}
|
|
@@ -6169,6 +6198,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
6169
6198
|
scanText,
|
|
6170
6199
|
sensitivePathMatch,
|
|
6171
6200
|
ssrfDestinationFloor,
|
|
6201
|
+
ssrfExemptMatches,
|
|
6172
6202
|
ssrfFloor,
|
|
6173
6203
|
ssrfReason,
|
|
6174
6204
|
stripControlChars,
|
package/dist/index.mjs
CHANGED
|
@@ -3258,6 +3258,38 @@ function classifySsrf(host) {
|
|
|
3258
3258
|
return null;
|
|
3259
3259
|
}
|
|
3260
3260
|
}
|
|
3261
|
+
function ssrfExemptMatches(entries, normalized) {
|
|
3262
|
+
if (!entries?.length || !normalized) return false;
|
|
3263
|
+
const target = bitsOf(normalized);
|
|
3264
|
+
for (const raw of entries) {
|
|
3265
|
+
const entry = raw.trim().toLowerCase();
|
|
3266
|
+
if (!entry) continue;
|
|
3267
|
+
const slash = entry.indexOf("/");
|
|
3268
|
+
if (slash === -1) {
|
|
3269
|
+
if ((normalizeIpLiteral(entry) ?? entry) === normalized) return true;
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3272
|
+
if (!target) continue;
|
|
3273
|
+
const base = bitsOf(normalizeIpLiteral(entry.slice(0, slash)) ?? "");
|
|
3274
|
+
const prefixText = entry.slice(slash + 1);
|
|
3275
|
+
const prefix = /^\d+$/.test(prefixText) ? Number(prefixText) : NaN;
|
|
3276
|
+
if (!base || !Number.isInteger(prefix) || prefix < 0 || // A v4 range never matches a v6 address, and the reverse: the widths
|
|
3277
|
+
// differ, so `0.0.0.0/0` does not release `::1`.
|
|
3278
|
+
base.length !== target.length || prefix > base.length) {
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
if (base.slice(0, prefix) === target.slice(0, prefix)) return true;
|
|
3282
|
+
}
|
|
3283
|
+
return false;
|
|
3284
|
+
}
|
|
3285
|
+
function bitsOf(normalized) {
|
|
3286
|
+
const o = v4Octets(normalized);
|
|
3287
|
+
if (o) {
|
|
3288
|
+
return o.every((n) => Number.isInteger(n) && n >= 0 && n <= 255) ? o.map((n) => n.toString(2).padStart(8, "0")).join("") : null;
|
|
3289
|
+
}
|
|
3290
|
+
const g = expandIpv6(normalized);
|
|
3291
|
+
return g ? g.map((n) => n.toString(2).padStart(16, "0")).join("") : null;
|
|
3292
|
+
}
|
|
3261
3293
|
var TIER_REASON = {
|
|
3262
3294
|
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
3263
3295
|
"link-local": "a link-local address",
|
|
@@ -3267,14 +3299,11 @@ var TIER_REASON = {
|
|
|
3267
3299
|
private: "a loopback or private address"
|
|
3268
3300
|
};
|
|
3269
3301
|
function ssrfFloor(tokens, opts = {}) {
|
|
3270
|
-
const exempt = new Set(
|
|
3271
|
-
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
3272
|
-
);
|
|
3273
3302
|
for (const { token, binary } of tokens) {
|
|
3274
3303
|
const m = classifySsrf(token);
|
|
3275
3304
|
if (!m) continue;
|
|
3276
3305
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3277
|
-
if (m.overridable &&
|
|
3306
|
+
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3278
3307
|
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
3279
3308
|
}
|
|
3280
3309
|
return null;
|
|
@@ -3751,7 +3780,6 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3751
3780
|
try {
|
|
3752
3781
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3753
3782
|
if (!paths) return null;
|
|
3754
|
-
const exempt = new Set((opts.ssrfAllow ?? []).map((a) => classifySsrf(a)?.normalized ?? a));
|
|
3755
3783
|
for (const path of paths) {
|
|
3756
3784
|
for (const value of valuesAt(args, path)) {
|
|
3757
3785
|
const host = hostOf(value);
|
|
@@ -3759,7 +3787,7 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3759
3787
|
const m = classifySsrf(host);
|
|
3760
3788
|
if (!m) continue;
|
|
3761
3789
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3762
|
-
if (m.overridable &&
|
|
3790
|
+
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3763
3791
|
return { ...m, argPath: path, host, reason: ssrfReason(m, host) };
|
|
3764
3792
|
}
|
|
3765
3793
|
}
|
|
@@ -6027,6 +6055,7 @@ export {
|
|
|
6027
6055
|
scanText,
|
|
6028
6056
|
sensitivePathMatch,
|
|
6029
6057
|
ssrfDestinationFloor,
|
|
6058
|
+
ssrfExemptMatches,
|
|
6030
6059
|
ssrfFloor,
|
|
6031
6060
|
ssrfReason,
|
|
6032
6061
|
stripControlChars,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.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",
|