@node9/policy-engine 2.16.2 → 2.17.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 +30 -12
- package/dist/index.d.ts +30 -12
- package/dist/index.js +48 -17
- package/dist/index.mjs +47 -17
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -402,6 +402,15 @@ interface ShellCommandAnalysis {
|
|
|
402
402
|
*/
|
|
403
403
|
declare function analyzeShellCommand(command: string): ShellCommandAnalysis;
|
|
404
404
|
|
|
405
|
+
/**
|
|
406
|
+
* A destination a tool call will reach, whichever carrier declared it: a
|
|
407
|
+
* shell command (extractShellDestinations) or a tool argument
|
|
408
|
+
* (extractToolDestinations). The interface is still spelled ShellDestination
|
|
409
|
+
* where it lives (shell/index.ts, which is hashed by the extractor-version
|
|
410
|
+
* gate, so a rename there is not free); this is the name that says what the
|
|
411
|
+
* shape is for.
|
|
412
|
+
*/
|
|
413
|
+
type Destination = ShellDestination;
|
|
405
414
|
interface EgressPolicy {
|
|
406
415
|
/** Master switch. Default false — opt-in, like dlp.pii. */
|
|
407
416
|
enabled: boolean;
|
|
@@ -459,7 +468,7 @@ declare function isPrivateHost(host: string): boolean;
|
|
|
459
468
|
* first review; null if everything is allowed (or policy disabled / mode off).
|
|
460
469
|
* Pure.
|
|
461
470
|
*/
|
|
462
|
-
declare function evaluateEgress(dests: readonly
|
|
471
|
+
declare function evaluateEgress(dests: readonly Destination[], policy: EgressPolicy): EgressVerdict | null;
|
|
463
472
|
|
|
464
473
|
interface PipeChainAnalysis {
|
|
465
474
|
isPipeline: boolean;
|
|
@@ -628,16 +637,6 @@ declare function resolvePinned(matches: SmartRule[]): SmartRule | undefined;
|
|
|
628
637
|
* Returns a reason string if dangerous, null if safe.
|
|
629
638
|
*/
|
|
630
639
|
declare function checkDangerousSql(sql: string): string | null;
|
|
631
|
-
/**
|
|
632
|
-
* Stateless policy evaluation. Same waterfall as the original
|
|
633
|
-
* proxy/src/policy/index.ts:evaluatePolicy, but config + context + I/O
|
|
634
|
-
* hooks come in as parameters so this function works in any host.
|
|
635
|
-
*
|
|
636
|
-
* Returns 'allow' for ignored tools, the matched smart-rule verdict,
|
|
637
|
-
* inline-execution review, eval-detection verdict, pipe-chain verdict,
|
|
638
|
-
* provenance verdict, sandbox allow, dangerous-word review, or strict-mode
|
|
639
|
-
* fallback. See the design doc for the full tier table.
|
|
640
|
-
*/
|
|
641
640
|
declare function evaluatePolicy(config: PolicyConfig, toolName: string, args?: unknown, context?: PolicyContext, hooks?: PolicyHostHooks): Promise<PolicyVerdict>;
|
|
642
641
|
/** Returns true when toolName matches the config's ignoredTools list. */
|
|
643
642
|
declare function isIgnoredTool(toolName: string, config: PolicyConfig): boolean;
|
|
@@ -1464,6 +1463,25 @@ declare function ssrfFloor(tokens: ReadonlyArray<{
|
|
|
1464
1463
|
* `[]` in a path means "every element of this array".
|
|
1465
1464
|
*/
|
|
1466
1465
|
declare const DESTINATION_ARGS: ReadonlyMap<string, readonly string[]>;
|
|
1466
|
+
/**
|
|
1467
|
+
* The hosts a non-shell tool call will reach, in the SAME shape the shell
|
|
1468
|
+
* extractor produces, so both feed one evaluateEgress (G10).
|
|
1469
|
+
*
|
|
1470
|
+
* Measured 2026-09-20 on dev e748b4a, egress { mode: 'block', allow: [] }:
|
|
1471
|
+
* `curl https://evil.example.com/` denied, the same URL through WebFetch, MCP
|
|
1472
|
+
* fetch and browser navigate allowed. The allowlist was shell-only, and an
|
|
1473
|
+
* agent that wants to exfiltrate does not need curl. The extraction below is
|
|
1474
|
+
* what the floor above already does; the policy was never applied to it.
|
|
1475
|
+
*
|
|
1476
|
+
* Same closed table, same helpers, for the same reason: a tool is covered
|
|
1477
|
+
* because it is LISTED, never because an argument looked like a URL. A tool
|
|
1478
|
+
* not in DESTINATION_ARGS yields [] and is invisible to the egress policy,
|
|
1479
|
+
* exactly as it is invisible to the floor. `binary` carries the tool name so
|
|
1480
|
+
* the reason string and the audit row say which carrier it was.
|
|
1481
|
+
*
|
|
1482
|
+
* Never throws: this runs on the hook path for every tool call.
|
|
1483
|
+
*/
|
|
1484
|
+
declare function extractToolDestinations(toolName: string, args: unknown): Destination[];
|
|
1467
1485
|
interface DestinationHit extends SsrfMatch {
|
|
1468
1486
|
/** The path that carried it, for the message and the audit row. */
|
|
1469
1487
|
argPath: string;
|
|
@@ -1483,4 +1501,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1483
1501
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1484
1502
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1485
1503
|
|
|
1486
|
-
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 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, 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 };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -402,6 +402,15 @@ interface ShellCommandAnalysis {
|
|
|
402
402
|
*/
|
|
403
403
|
declare function analyzeShellCommand(command: string): ShellCommandAnalysis;
|
|
404
404
|
|
|
405
|
+
/**
|
|
406
|
+
* A destination a tool call will reach, whichever carrier declared it: a
|
|
407
|
+
* shell command (extractShellDestinations) or a tool argument
|
|
408
|
+
* (extractToolDestinations). The interface is still spelled ShellDestination
|
|
409
|
+
* where it lives (shell/index.ts, which is hashed by the extractor-version
|
|
410
|
+
* gate, so a rename there is not free); this is the name that says what the
|
|
411
|
+
* shape is for.
|
|
412
|
+
*/
|
|
413
|
+
type Destination = ShellDestination;
|
|
405
414
|
interface EgressPolicy {
|
|
406
415
|
/** Master switch. Default false — opt-in, like dlp.pii. */
|
|
407
416
|
enabled: boolean;
|
|
@@ -459,7 +468,7 @@ declare function isPrivateHost(host: string): boolean;
|
|
|
459
468
|
* first review; null if everything is allowed (or policy disabled / mode off).
|
|
460
469
|
* Pure.
|
|
461
470
|
*/
|
|
462
|
-
declare function evaluateEgress(dests: readonly
|
|
471
|
+
declare function evaluateEgress(dests: readonly Destination[], policy: EgressPolicy): EgressVerdict | null;
|
|
463
472
|
|
|
464
473
|
interface PipeChainAnalysis {
|
|
465
474
|
isPipeline: boolean;
|
|
@@ -628,16 +637,6 @@ declare function resolvePinned(matches: SmartRule[]): SmartRule | undefined;
|
|
|
628
637
|
* Returns a reason string if dangerous, null if safe.
|
|
629
638
|
*/
|
|
630
639
|
declare function checkDangerousSql(sql: string): string | null;
|
|
631
|
-
/**
|
|
632
|
-
* Stateless policy evaluation. Same waterfall as the original
|
|
633
|
-
* proxy/src/policy/index.ts:evaluatePolicy, but config + context + I/O
|
|
634
|
-
* hooks come in as parameters so this function works in any host.
|
|
635
|
-
*
|
|
636
|
-
* Returns 'allow' for ignored tools, the matched smart-rule verdict,
|
|
637
|
-
* inline-execution review, eval-detection verdict, pipe-chain verdict,
|
|
638
|
-
* provenance verdict, sandbox allow, dangerous-word review, or strict-mode
|
|
639
|
-
* fallback. See the design doc for the full tier table.
|
|
640
|
-
*/
|
|
641
640
|
declare function evaluatePolicy(config: PolicyConfig, toolName: string, args?: unknown, context?: PolicyContext, hooks?: PolicyHostHooks): Promise<PolicyVerdict>;
|
|
642
641
|
/** Returns true when toolName matches the config's ignoredTools list. */
|
|
643
642
|
declare function isIgnoredTool(toolName: string, config: PolicyConfig): boolean;
|
|
@@ -1464,6 +1463,25 @@ declare function ssrfFloor(tokens: ReadonlyArray<{
|
|
|
1464
1463
|
* `[]` in a path means "every element of this array".
|
|
1465
1464
|
*/
|
|
1466
1465
|
declare const DESTINATION_ARGS: ReadonlyMap<string, readonly string[]>;
|
|
1466
|
+
/**
|
|
1467
|
+
* The hosts a non-shell tool call will reach, in the SAME shape the shell
|
|
1468
|
+
* extractor produces, so both feed one evaluateEgress (G10).
|
|
1469
|
+
*
|
|
1470
|
+
* Measured 2026-09-20 on dev e748b4a, egress { mode: 'block', allow: [] }:
|
|
1471
|
+
* `curl https://evil.example.com/` denied, the same URL through WebFetch, MCP
|
|
1472
|
+
* fetch and browser navigate allowed. The allowlist was shell-only, and an
|
|
1473
|
+
* agent that wants to exfiltrate does not need curl. The extraction below is
|
|
1474
|
+
* what the floor above already does; the policy was never applied to it.
|
|
1475
|
+
*
|
|
1476
|
+
* Same closed table, same helpers, for the same reason: a tool is covered
|
|
1477
|
+
* because it is LISTED, never because an argument looked like a URL. A tool
|
|
1478
|
+
* not in DESTINATION_ARGS yields [] and is invisible to the egress policy,
|
|
1479
|
+
* exactly as it is invisible to the floor. `binary` carries the tool name so
|
|
1480
|
+
* the reason string and the audit row say which carrier it was.
|
|
1481
|
+
*
|
|
1482
|
+
* Never throws: this runs on the hook path for every tool call.
|
|
1483
|
+
*/
|
|
1484
|
+
declare function extractToolDestinations(toolName: string, args: unknown): Destination[];
|
|
1467
1485
|
interface DestinationHit extends SsrfMatch {
|
|
1468
1486
|
/** The path that carried it, for the message and the audit row. */
|
|
1469
1487
|
argPath: string;
|
|
@@ -1483,4 +1501,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1483
1501
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1484
1502
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1485
1503
|
|
|
1486
|
-
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 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, 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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ __export(src_exports, {
|
|
|
90
90
|
extractSessionLevelFindings: () => extractSessionLevelFindings,
|
|
91
91
|
extractShellDestTokens: () => extractShellDestTokens,
|
|
92
92
|
extractShellDestinations: () => extractShellDestinations,
|
|
93
|
+
extractToolDestinations: () => extractToolDestinations,
|
|
93
94
|
fileOperandFlagsOf: () => fileOperandFlagsOf,
|
|
94
95
|
getCompiledRegex: () => getCompiledRegex,
|
|
95
96
|
getNestedValue: () => getNestedValue,
|
|
@@ -3445,18 +3446,18 @@ var DEFAULT_EGRESS_ALLOWLIST = [
|
|
|
3445
3446
|
"*.ubuntu.com"
|
|
3446
3447
|
];
|
|
3447
3448
|
function hostMatches(host, pattern) {
|
|
3448
|
-
const
|
|
3449
|
-
const p = pattern.toLowerCase().trim();
|
|
3449
|
+
const p = pattern.trim().toLowerCase();
|
|
3450
3450
|
if (!p) return false;
|
|
3451
3451
|
if (p === "*") return true;
|
|
3452
|
+
const h = canonicalHost(host);
|
|
3452
3453
|
if (p.startsWith("*.")) {
|
|
3453
|
-
const suffix = p.slice(2);
|
|
3454
|
+
const suffix = canonicalHost(p.slice(2));
|
|
3454
3455
|
return h === suffix || h.endsWith("." + suffix);
|
|
3455
3456
|
}
|
|
3456
|
-
return h === p;
|
|
3457
|
+
return h === canonicalHost(p);
|
|
3457
3458
|
}
|
|
3458
3459
|
function matchesAny(host, patterns) {
|
|
3459
|
-
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
3460
|
+
for (const p of patterns ?? []) if (hostMatches(host, p)) return true;
|
|
3460
3461
|
return false;
|
|
3461
3462
|
}
|
|
3462
3463
|
var PRIVATE_HOST_SUFFIXES = [".local", ".internal", ".localhost"];
|
|
@@ -3467,13 +3468,17 @@ function isUniqueLocalV6(host) {
|
|
|
3467
3468
|
return g !== null && (g[0] & 65024) === 64512;
|
|
3468
3469
|
}
|
|
3469
3470
|
function isPrivateHost(host) {
|
|
3470
|
-
const h = host.trim().toLowerCase();
|
|
3471
|
+
const h = host.trim().toLowerCase().replace(/\.$/, "");
|
|
3471
3472
|
const m = classifySsrf(h);
|
|
3472
3473
|
if (m) return m.kind === "address" && (m.tier === "private" || m.tier === "unspecified");
|
|
3473
3474
|
if (h === "localhost") return true;
|
|
3474
3475
|
if (PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s))) return true;
|
|
3475
3476
|
return isUniqueLocalV6(h);
|
|
3476
3477
|
}
|
|
3478
|
+
function canonicalHost(host) {
|
|
3479
|
+
const ip = normalizeIpLiteral(host);
|
|
3480
|
+
return ip ?? host.trim().toLowerCase().replace(/\.$/, "");
|
|
3481
|
+
}
|
|
3477
3482
|
function evaluateEgress(dests, policy) {
|
|
3478
3483
|
if (!policy.enabled) return null;
|
|
3479
3484
|
let review = null;
|
|
@@ -3867,6 +3872,22 @@ function hostOf(value) {
|
|
|
3867
3872
|
return null;
|
|
3868
3873
|
}
|
|
3869
3874
|
}
|
|
3875
|
+
function extractToolDestinations(toolName, args) {
|
|
3876
|
+
try {
|
|
3877
|
+
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3878
|
+
if (!paths) return [];
|
|
3879
|
+
const out = [];
|
|
3880
|
+
for (const path of paths) {
|
|
3881
|
+
for (const value of valuesAt(args, path)) {
|
|
3882
|
+
const host = hostOf(value);
|
|
3883
|
+
if (host) out.push({ host, binary: toolName, raw: value });
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
return out;
|
|
3887
|
+
} catch {
|
|
3888
|
+
return [];
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3870
3891
|
function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
3871
3892
|
try {
|
|
3872
3893
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
@@ -3985,6 +4006,16 @@ function pipeChainVerdict(command, isTrustedHost, highAction = "review") {
|
|
|
3985
4006
|
tier: 3
|
|
3986
4007
|
};
|
|
3987
4008
|
}
|
|
4009
|
+
function egressPolicyVerdict(eg) {
|
|
4010
|
+
return {
|
|
4011
|
+
decision: eg.verdict,
|
|
4012
|
+
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
4013
|
+
reason: eg.reason,
|
|
4014
|
+
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
4015
|
+
ruleDescription: eg.reason,
|
|
4016
|
+
tier: eg.verdict === "block" ? 3 : 4
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
3988
4019
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
3989
4020
|
const { agent, cwd, activeEnvironment } = context;
|
|
3990
4021
|
const { checkProvenance, isTrustedHost } = hooks;
|
|
@@ -4019,7 +4050,14 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4019
4050
|
};
|
|
4020
4051
|
}
|
|
4021
4052
|
}
|
|
4022
|
-
|
|
4053
|
+
const pendingToolEgress = config.policy.egress?.enabled ? (() => {
|
|
4054
|
+
const dests = extractToolDestinations(toolName, args);
|
|
4055
|
+
const eg = dests.length > 0 ? evaluateEgress(dests, config.policy.egress) : null;
|
|
4056
|
+
return eg ? egressPolicyVerdict(eg) : void 0;
|
|
4057
|
+
})() : void 0;
|
|
4058
|
+
if (wouldBeIgnored && !context.skipIgnoredFastPath) {
|
|
4059
|
+
return pendingToolEgress ?? { decision: "allow" };
|
|
4060
|
+
}
|
|
4023
4061
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
4024
4062
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
4025
4063
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
@@ -4108,6 +4146,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4108
4146
|
}
|
|
4109
4147
|
let allTokens = [];
|
|
4110
4148
|
let pathTokens = [];
|
|
4149
|
+
if (pendingToolEgress) return pendingToolEgress;
|
|
4111
4150
|
const shellCommand = extractShellCommand(toolName, args, config.policy.toolInspection);
|
|
4112
4151
|
if (shellCommand) {
|
|
4113
4152
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
@@ -4173,16 +4212,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4173
4212
|
const dests = extractShellDestinations(shellCommand);
|
|
4174
4213
|
if (dests.length > 0) {
|
|
4175
4214
|
const eg = evaluateEgress(dests, config.policy.egress);
|
|
4176
|
-
if (eg)
|
|
4177
|
-
return {
|
|
4178
|
-
decision: eg.verdict,
|
|
4179
|
-
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
4180
|
-
reason: eg.reason,
|
|
4181
|
-
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
4182
|
-
ruleDescription: eg.reason,
|
|
4183
|
-
tier: eg.verdict === "block" ? 3 : 4
|
|
4184
|
-
};
|
|
4185
|
-
}
|
|
4215
|
+
if (eg) return egressPolicyVerdict(eg);
|
|
4186
4216
|
}
|
|
4187
4217
|
}
|
|
4188
4218
|
const firstToken = analyzed.actions[0] ?? "";
|
|
@@ -6106,6 +6136,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
6106
6136
|
extractSessionLevelFindings,
|
|
6107
6137
|
extractShellDestTokens,
|
|
6108
6138
|
extractShellDestinations,
|
|
6139
|
+
extractToolDestinations,
|
|
6109
6140
|
fileOperandFlagsOf,
|
|
6110
6141
|
getCompiledRegex,
|
|
6111
6142
|
getNestedValue,
|
package/dist/index.mjs
CHANGED
|
@@ -3305,18 +3305,18 @@ var DEFAULT_EGRESS_ALLOWLIST = [
|
|
|
3305
3305
|
"*.ubuntu.com"
|
|
3306
3306
|
];
|
|
3307
3307
|
function hostMatches(host, pattern) {
|
|
3308
|
-
const
|
|
3309
|
-
const p = pattern.toLowerCase().trim();
|
|
3308
|
+
const p = pattern.trim().toLowerCase();
|
|
3310
3309
|
if (!p) return false;
|
|
3311
3310
|
if (p === "*") return true;
|
|
3311
|
+
const h = canonicalHost(host);
|
|
3312
3312
|
if (p.startsWith("*.")) {
|
|
3313
|
-
const suffix = p.slice(2);
|
|
3313
|
+
const suffix = canonicalHost(p.slice(2));
|
|
3314
3314
|
return h === suffix || h.endsWith("." + suffix);
|
|
3315
3315
|
}
|
|
3316
|
-
return h === p;
|
|
3316
|
+
return h === canonicalHost(p);
|
|
3317
3317
|
}
|
|
3318
3318
|
function matchesAny(host, patterns) {
|
|
3319
|
-
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
3319
|
+
for (const p of patterns ?? []) if (hostMatches(host, p)) return true;
|
|
3320
3320
|
return false;
|
|
3321
3321
|
}
|
|
3322
3322
|
var PRIVATE_HOST_SUFFIXES = [".local", ".internal", ".localhost"];
|
|
@@ -3327,13 +3327,17 @@ function isUniqueLocalV6(host) {
|
|
|
3327
3327
|
return g !== null && (g[0] & 65024) === 64512;
|
|
3328
3328
|
}
|
|
3329
3329
|
function isPrivateHost(host) {
|
|
3330
|
-
const h = host.trim().toLowerCase();
|
|
3330
|
+
const h = host.trim().toLowerCase().replace(/\.$/, "");
|
|
3331
3331
|
const m = classifySsrf(h);
|
|
3332
3332
|
if (m) return m.kind === "address" && (m.tier === "private" || m.tier === "unspecified");
|
|
3333
3333
|
if (h === "localhost") return true;
|
|
3334
3334
|
if (PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s))) return true;
|
|
3335
3335
|
return isUniqueLocalV6(h);
|
|
3336
3336
|
}
|
|
3337
|
+
function canonicalHost(host) {
|
|
3338
|
+
const ip = normalizeIpLiteral(host);
|
|
3339
|
+
return ip ?? host.trim().toLowerCase().replace(/\.$/, "");
|
|
3340
|
+
}
|
|
3337
3341
|
function evaluateEgress(dests, policy) {
|
|
3338
3342
|
if (!policy.enabled) return null;
|
|
3339
3343
|
let review = null;
|
|
@@ -3727,6 +3731,22 @@ function hostOf(value) {
|
|
|
3727
3731
|
return null;
|
|
3728
3732
|
}
|
|
3729
3733
|
}
|
|
3734
|
+
function extractToolDestinations(toolName, args) {
|
|
3735
|
+
try {
|
|
3736
|
+
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3737
|
+
if (!paths) return [];
|
|
3738
|
+
const out = [];
|
|
3739
|
+
for (const path of paths) {
|
|
3740
|
+
for (const value of valuesAt(args, path)) {
|
|
3741
|
+
const host = hostOf(value);
|
|
3742
|
+
if (host) out.push({ host, binary: toolName, raw: value });
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return out;
|
|
3746
|
+
} catch {
|
|
3747
|
+
return [];
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3730
3750
|
function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
3731
3751
|
try {
|
|
3732
3752
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
@@ -3845,6 +3865,16 @@ function pipeChainVerdict(command, isTrustedHost, highAction = "review") {
|
|
|
3845
3865
|
tier: 3
|
|
3846
3866
|
};
|
|
3847
3867
|
}
|
|
3868
|
+
function egressPolicyVerdict(eg) {
|
|
3869
|
+
return {
|
|
3870
|
+
decision: eg.verdict,
|
|
3871
|
+
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
3872
|
+
reason: eg.reason,
|
|
3873
|
+
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
3874
|
+
ruleDescription: eg.reason,
|
|
3875
|
+
tier: eg.verdict === "block" ? 3 : 4
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3848
3878
|
async function evaluatePolicy(config, toolName, args, context = {}, hooks = {}) {
|
|
3849
3879
|
const { agent, cwd, activeEnvironment } = context;
|
|
3850
3880
|
const { checkProvenance, isTrustedHost } = hooks;
|
|
@@ -3879,7 +3909,14 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
3879
3909
|
};
|
|
3880
3910
|
}
|
|
3881
3911
|
}
|
|
3882
|
-
|
|
3912
|
+
const pendingToolEgress = config.policy.egress?.enabled ? (() => {
|
|
3913
|
+
const dests = extractToolDestinations(toolName, args);
|
|
3914
|
+
const eg = dests.length > 0 ? evaluateEgress(dests, config.policy.egress) : null;
|
|
3915
|
+
return eg ? egressPolicyVerdict(eg) : void 0;
|
|
3916
|
+
})() : void 0;
|
|
3917
|
+
if (wouldBeIgnored && !context.skipIgnoredFastPath) {
|
|
3918
|
+
return pendingToolEgress ?? { decision: "allow" };
|
|
3919
|
+
}
|
|
3883
3920
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
3884
3921
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
3885
3922
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
@@ -3968,6 +4005,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
3968
4005
|
}
|
|
3969
4006
|
let allTokens = [];
|
|
3970
4007
|
let pathTokens = [];
|
|
4008
|
+
if (pendingToolEgress) return pendingToolEgress;
|
|
3971
4009
|
const shellCommand = extractShellCommand(toolName, args, config.policy.toolInspection);
|
|
3972
4010
|
if (shellCommand) {
|
|
3973
4011
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
@@ -4033,16 +4071,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
4033
4071
|
const dests = extractShellDestinations(shellCommand);
|
|
4034
4072
|
if (dests.length > 0) {
|
|
4035
4073
|
const eg = evaluateEgress(dests, config.policy.egress);
|
|
4036
|
-
if (eg)
|
|
4037
|
-
return {
|
|
4038
|
-
decision: eg.verdict,
|
|
4039
|
-
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
4040
|
-
reason: eg.reason,
|
|
4041
|
-
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
4042
|
-
ruleDescription: eg.reason,
|
|
4043
|
-
tier: eg.verdict === "block" ? 3 : 4
|
|
4044
|
-
};
|
|
4045
|
-
}
|
|
4074
|
+
if (eg) return egressPolicyVerdict(eg);
|
|
4046
4075
|
}
|
|
4047
4076
|
}
|
|
4048
4077
|
const firstToken = analyzed.actions[0] ?? "";
|
|
@@ -5965,6 +5994,7 @@ export {
|
|
|
5965
5994
|
extractSessionLevelFindings,
|
|
5966
5995
|
extractShellDestTokens,
|
|
5967
5996
|
extractShellDestinations,
|
|
5997
|
+
extractToolDestinations,
|
|
5968
5998
|
fileOperandFlagsOf,
|
|
5969
5999
|
getCompiledRegex,
|
|
5970
6000
|
getNestedValue,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.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",
|