@node9/policy-engine 1.30.0 → 1.31.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 +66 -2
- package/dist/index.d.ts +66 -2
- package/dist/index.js +321 -1
- package/dist/index.mjs +313 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -165,6 +165,27 @@ declare const AST_FS_REGEX_RULES: Set<string>;
|
|
|
165
165
|
* the tool-managed cache allow-list. Used to gate `rm -rf` on home paths.
|
|
166
166
|
*/
|
|
167
167
|
declare function isProtectedHomePath(rawPath: string): boolean;
|
|
168
|
+
interface ShellDestination {
|
|
169
|
+
/** Extracted hostname, lowercased (e.g. "evil.com", "10.0.0.5"). */
|
|
170
|
+
host: string;
|
|
171
|
+
/** The network binary it belongs to (e.g. "curl"). */
|
|
172
|
+
binary: string;
|
|
173
|
+
/** The raw argument token the host came from (for UI / audit). */
|
|
174
|
+
raw: string;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Parse a destination host out of a single token. Handles scheme URLs
|
|
178
|
+
* (`https://h/p`), scheme-less curl targets (`evil.com/p`), `user@host:path`
|
|
179
|
+
* (scp/ssh), and `host:port`. Returns the lowercased hostname, or null if the
|
|
180
|
+
* token doesn't resolve to a plausible host. IPv6 literals are out of scope v1.
|
|
181
|
+
*/
|
|
182
|
+
declare function parseDestHost(token: string): string | null;
|
|
183
|
+
/**
|
|
184
|
+
* AST-extract every network destination host in a shell command. Walks each
|
|
185
|
+
* CallExpr; for curl/wget/scp/ssh/nc it resolves the destination argument(s)
|
|
186
|
+
* and parses the host. Deduplicated by host. Pure — no I/O, no DNS.
|
|
187
|
+
*/
|
|
188
|
+
declare function extractShellDestinations(command: string): ShellDestination[];
|
|
168
189
|
declare function analyzeFsOperation(command: string): FsOpVerdict | null;
|
|
169
190
|
interface ShellCommandAnalysis {
|
|
170
191
|
/** First word of every CallExpr — the command names invoked. */
|
|
@@ -181,6 +202,39 @@ interface ShellCommandAnalysis {
|
|
|
181
202
|
*/
|
|
182
203
|
declare function analyzeShellCommand(command: string): ShellCommandAnalysis;
|
|
183
204
|
|
|
205
|
+
interface EgressPolicy {
|
|
206
|
+
/** Master switch. Default false — opt-in, like dlp.pii. */
|
|
207
|
+
enabled: boolean;
|
|
208
|
+
/** Verdict for an UNKNOWN host (not in allow, not in deny, not private). */
|
|
209
|
+
mode: 'off' | 'review' | 'block';
|
|
210
|
+
/** Host globs always allowed (e.g. "*.github.com", "api.openai.com"). */
|
|
211
|
+
allow: string[];
|
|
212
|
+
/** Host globs always blocked — wins over allow/private. */
|
|
213
|
+
deny: string[];
|
|
214
|
+
/** Auto-allow localhost / RFC1918 / *.local. Default true. */
|
|
215
|
+
allowPrivate: boolean;
|
|
216
|
+
}
|
|
217
|
+
interface EgressVerdict {
|
|
218
|
+
verdict: 'block' | 'review';
|
|
219
|
+
host: string;
|
|
220
|
+
binary: string;
|
|
221
|
+
reason: string;
|
|
222
|
+
}
|
|
223
|
+
declare const DEFAULT_EGRESS_ALLOWLIST: readonly string[];
|
|
224
|
+
/** Glob host match: "*" = any, "*.x" = apex x + any subdomain, else exact. */
|
|
225
|
+
declare function hostMatches(host: string, pattern: string): boolean;
|
|
226
|
+
/** localhost / loopback / RFC1918 / link-local-ish — never a real exfil target. */
|
|
227
|
+
declare function isPrivateHost(host: string): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Evaluate extracted destinations against the egress policy. Precedence per
|
|
230
|
+
* host: deny (block) > private-allow > allow/default-allow (skip) > unknown
|
|
231
|
+
* (policy.mode). Returns the most severe actionable verdict across all
|
|
232
|
+
* destinations — a deny or block-mode-unknown short-circuits; otherwise the
|
|
233
|
+
* first review; null if everything is allowed (or policy disabled / mode off).
|
|
234
|
+
* Pure.
|
|
235
|
+
*/
|
|
236
|
+
declare function evaluateEgress(dests: readonly ShellDestination[], policy: EgressPolicy): EgressVerdict | null;
|
|
237
|
+
|
|
184
238
|
interface PipeChainAnalysis {
|
|
185
239
|
isPipeline: boolean;
|
|
186
240
|
hasSensitiveSource: boolean;
|
|
@@ -243,6 +297,9 @@ interface PolicyConfig {
|
|
|
243
297
|
enabled: boolean;
|
|
244
298
|
scanIgnoredTools: boolean;
|
|
245
299
|
};
|
|
300
|
+
/** Egress / destination control (GAP-5). Optional for back-compat with
|
|
301
|
+
* callers/tests that build a PolicyConfig without it. */
|
|
302
|
+
egress?: EgressPolicy;
|
|
246
303
|
};
|
|
247
304
|
settings: {
|
|
248
305
|
mode: string;
|
|
@@ -777,6 +834,13 @@ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
|
|
|
777
834
|
* per distinct pattern type, never multiple "Email" findings from one input.
|
|
778
835
|
*/
|
|
779
836
|
declare function detectPii(text: string): PiiPattern[];
|
|
837
|
+
declare const REALTIME_PII_PATTERNS: readonly PiiPattern[];
|
|
838
|
+
/**
|
|
839
|
+
* Realtime adapter for detectPii: walks a tool-args value (stringifying
|
|
840
|
+
* objects/arrays) and returns only the high-signal PII patterns found. Used by
|
|
841
|
+
* the authorize path to gate SSN / Credit Card in tool arguments. Pure.
|
|
842
|
+
*/
|
|
843
|
+
declare function detectArgsPii(args: unknown): PiiPattern[];
|
|
780
844
|
|
|
781
845
|
type CanonicalFindingType = 'smart-rule' | 'ast-fs-op' | 'dlp' | 'pii' | 'sensitive-file-read' | 'privilege-escalation' | 'destructive-op' | 'pipe-to-shell' | 'eval-of-remote' | 'loop' | 'long-output-redacted';
|
|
782
846
|
type CanonicalAgent = 'claude' | 'gemini' | 'codex' | 'shell';
|
|
@@ -918,7 +982,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
|
918
982
|
* files changed, this hash must change too, and you must consciously
|
|
919
983
|
* decide whether to bump CANONICAL_EXTRACTOR_VERSION."
|
|
920
984
|
*/
|
|
921
|
-
declare const CANONICAL_EXTRACTOR_HASH = "
|
|
985
|
+
declare const CANONICAL_EXTRACTOR_HASH = "dbe0199dae0f29f6";
|
|
922
986
|
declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
|
|
923
987
|
declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
|
|
924
988
|
/**
|
|
@@ -945,4 +1009,4 @@ declare function previewArgs(input: Record<string, unknown>, max: number): strin
|
|
|
945
1009
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
946
1010
|
declare const ENGINE_VERSION = "1.4.0";
|
|
947
1011
|
|
|
948
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, 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, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, getCompiledRegex, getNestedValue, isBashTool, isIgnoredTool, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, previewArgs, redactText, scanArgs, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1012
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, 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, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, scanArgs, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -165,6 +165,27 @@ declare const AST_FS_REGEX_RULES: Set<string>;
|
|
|
165
165
|
* the tool-managed cache allow-list. Used to gate `rm -rf` on home paths.
|
|
166
166
|
*/
|
|
167
167
|
declare function isProtectedHomePath(rawPath: string): boolean;
|
|
168
|
+
interface ShellDestination {
|
|
169
|
+
/** Extracted hostname, lowercased (e.g. "evil.com", "10.0.0.5"). */
|
|
170
|
+
host: string;
|
|
171
|
+
/** The network binary it belongs to (e.g. "curl"). */
|
|
172
|
+
binary: string;
|
|
173
|
+
/** The raw argument token the host came from (for UI / audit). */
|
|
174
|
+
raw: string;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Parse a destination host out of a single token. Handles scheme URLs
|
|
178
|
+
* (`https://h/p`), scheme-less curl targets (`evil.com/p`), `user@host:path`
|
|
179
|
+
* (scp/ssh), and `host:port`. Returns the lowercased hostname, or null if the
|
|
180
|
+
* token doesn't resolve to a plausible host. IPv6 literals are out of scope v1.
|
|
181
|
+
*/
|
|
182
|
+
declare function parseDestHost(token: string): string | null;
|
|
183
|
+
/**
|
|
184
|
+
* AST-extract every network destination host in a shell command. Walks each
|
|
185
|
+
* CallExpr; for curl/wget/scp/ssh/nc it resolves the destination argument(s)
|
|
186
|
+
* and parses the host. Deduplicated by host. Pure — no I/O, no DNS.
|
|
187
|
+
*/
|
|
188
|
+
declare function extractShellDestinations(command: string): ShellDestination[];
|
|
168
189
|
declare function analyzeFsOperation(command: string): FsOpVerdict | null;
|
|
169
190
|
interface ShellCommandAnalysis {
|
|
170
191
|
/** First word of every CallExpr — the command names invoked. */
|
|
@@ -181,6 +202,39 @@ interface ShellCommandAnalysis {
|
|
|
181
202
|
*/
|
|
182
203
|
declare function analyzeShellCommand(command: string): ShellCommandAnalysis;
|
|
183
204
|
|
|
205
|
+
interface EgressPolicy {
|
|
206
|
+
/** Master switch. Default false — opt-in, like dlp.pii. */
|
|
207
|
+
enabled: boolean;
|
|
208
|
+
/** Verdict for an UNKNOWN host (not in allow, not in deny, not private). */
|
|
209
|
+
mode: 'off' | 'review' | 'block';
|
|
210
|
+
/** Host globs always allowed (e.g. "*.github.com", "api.openai.com"). */
|
|
211
|
+
allow: string[];
|
|
212
|
+
/** Host globs always blocked — wins over allow/private. */
|
|
213
|
+
deny: string[];
|
|
214
|
+
/** Auto-allow localhost / RFC1918 / *.local. Default true. */
|
|
215
|
+
allowPrivate: boolean;
|
|
216
|
+
}
|
|
217
|
+
interface EgressVerdict {
|
|
218
|
+
verdict: 'block' | 'review';
|
|
219
|
+
host: string;
|
|
220
|
+
binary: string;
|
|
221
|
+
reason: string;
|
|
222
|
+
}
|
|
223
|
+
declare const DEFAULT_EGRESS_ALLOWLIST: readonly string[];
|
|
224
|
+
/** Glob host match: "*" = any, "*.x" = apex x + any subdomain, else exact. */
|
|
225
|
+
declare function hostMatches(host: string, pattern: string): boolean;
|
|
226
|
+
/** localhost / loopback / RFC1918 / link-local-ish — never a real exfil target. */
|
|
227
|
+
declare function isPrivateHost(host: string): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Evaluate extracted destinations against the egress policy. Precedence per
|
|
230
|
+
* host: deny (block) > private-allow > allow/default-allow (skip) > unknown
|
|
231
|
+
* (policy.mode). Returns the most severe actionable verdict across all
|
|
232
|
+
* destinations — a deny or block-mode-unknown short-circuits; otherwise the
|
|
233
|
+
* first review; null if everything is allowed (or policy disabled / mode off).
|
|
234
|
+
* Pure.
|
|
235
|
+
*/
|
|
236
|
+
declare function evaluateEgress(dests: readonly ShellDestination[], policy: EgressPolicy): EgressVerdict | null;
|
|
237
|
+
|
|
184
238
|
interface PipeChainAnalysis {
|
|
185
239
|
isPipeline: boolean;
|
|
186
240
|
hasSensitiveSource: boolean;
|
|
@@ -243,6 +297,9 @@ interface PolicyConfig {
|
|
|
243
297
|
enabled: boolean;
|
|
244
298
|
scanIgnoredTools: boolean;
|
|
245
299
|
};
|
|
300
|
+
/** Egress / destination control (GAP-5). Optional for back-compat with
|
|
301
|
+
* callers/tests that build a PolicyConfig without it. */
|
|
302
|
+
egress?: EgressPolicy;
|
|
246
303
|
};
|
|
247
304
|
settings: {
|
|
248
305
|
mode: string;
|
|
@@ -777,6 +834,13 @@ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
|
|
|
777
834
|
* per distinct pattern type, never multiple "Email" findings from one input.
|
|
778
835
|
*/
|
|
779
836
|
declare function detectPii(text: string): PiiPattern[];
|
|
837
|
+
declare const REALTIME_PII_PATTERNS: readonly PiiPattern[];
|
|
838
|
+
/**
|
|
839
|
+
* Realtime adapter for detectPii: walks a tool-args value (stringifying
|
|
840
|
+
* objects/arrays) and returns only the high-signal PII patterns found. Used by
|
|
841
|
+
* the authorize path to gate SSN / Credit Card in tool arguments. Pure.
|
|
842
|
+
*/
|
|
843
|
+
declare function detectArgsPii(args: unknown): PiiPattern[];
|
|
780
844
|
|
|
781
845
|
type CanonicalFindingType = 'smart-rule' | 'ast-fs-op' | 'dlp' | 'pii' | 'sensitive-file-read' | 'privilege-escalation' | 'destructive-op' | 'pipe-to-shell' | 'eval-of-remote' | 'loop' | 'long-output-redacted';
|
|
782
846
|
type CanonicalAgent = 'claude' | 'gemini' | 'codex' | 'shell';
|
|
@@ -918,7 +982,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
|
918
982
|
* files changed, this hash must change too, and you must consciously
|
|
919
983
|
* decide whether to bump CANONICAL_EXTRACTOR_VERSION."
|
|
920
984
|
*/
|
|
921
|
-
declare const CANONICAL_EXTRACTOR_HASH = "
|
|
985
|
+
declare const CANONICAL_EXTRACTOR_HASH = "dbe0199dae0f29f6";
|
|
922
986
|
declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
|
|
923
987
|
declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
|
|
924
988
|
/**
|
|
@@ -945,4 +1009,4 @@ declare function previewArgs(input: Record<string, unknown>, max: number): strin
|
|
|
945
1009
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
946
1010
|
declare const ENGINE_VERSION = "1.4.0";
|
|
947
1011
|
|
|
948
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, 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, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, getCompiledRegex, getNestedValue, isBashTool, isIgnoredTool, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, previewArgs, redactText, scanArgs, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1012
|
+
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, type FsOpVerdict, 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, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, scanArgs, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(src_exports, {
|
|
|
36
36
|
CANONICAL_EXTRACTOR_HASH: () => CANONICAL_EXTRACTOR_HASH,
|
|
37
37
|
CANONICAL_EXTRACTOR_VERSION: () => CANONICAL_EXTRACTOR_VERSION,
|
|
38
38
|
COST_PER_LOOP_ITER_USD: () => COST_PER_LOOP_ITER_USD,
|
|
39
|
+
DEFAULT_EGRESS_ALLOWLIST: () => DEFAULT_EGRESS_ALLOWLIST,
|
|
39
40
|
DESTRUCTIVE_OP_RE: () => DESTRUCTIVE_OP_RE,
|
|
40
41
|
DLP_PATTERNS: () => DLP_PATTERNS,
|
|
41
42
|
ENGINE_VERSION: () => ENGINE_VERSION,
|
|
@@ -45,6 +46,7 @@ __export(src_exports, {
|
|
|
45
46
|
LOOP_MAX_RECORDS: () => LOOP_MAX_RECORDS,
|
|
46
47
|
LOOP_THRESHOLD_FOR_WASTE: () => LOOP_THRESHOLD_FOR_WASTE,
|
|
47
48
|
PRIVILEGE_ESCALATION_RE: () => PRIVILEGE_ESCALATION_RE,
|
|
49
|
+
REALTIME_PII_PATTERNS: () => REALTIME_PII_PATTERNS,
|
|
48
50
|
SCAN_SIGNAL_WEIGHTS: () => SCAN_SIGNAL_WEIGHTS,
|
|
49
51
|
SENSITIVE_PATH_RE: () => SENSITIVE_PATH_RE,
|
|
50
52
|
SENSITIVE_PATH_REGEXES: () => SENSITIVE_PATH_REGEXES,
|
|
@@ -60,9 +62,11 @@ __export(src_exports, {
|
|
|
60
62
|
computeScanScore: () => computeScanScore,
|
|
61
63
|
computeSecurityScore: () => computeSecurityScore,
|
|
62
64
|
dedupeCanonicalFindings: () => dedupeCanonicalFindings,
|
|
65
|
+
detectArgsPii: () => detectArgsPii,
|
|
63
66
|
detectDangerousEval: () => detectDangerousEval,
|
|
64
67
|
detectDangerousShellExec: () => detectDangerousShellExec,
|
|
65
68
|
detectPii: () => detectPii,
|
|
69
|
+
evaluateEgress: () => evaluateEgress,
|
|
66
70
|
evaluateLoopWindow: () => evaluateLoopWindow,
|
|
67
71
|
evaluatePolicy: () => evaluatePolicy,
|
|
68
72
|
evaluateSmartConditions: () => evaluateSmartConditions,
|
|
@@ -71,10 +75,13 @@ __export(src_exports, {
|
|
|
71
75
|
extractNetworkTargets: () => extractNetworkTargets,
|
|
72
76
|
extractPositionalArgs: () => extractPositionalArgs,
|
|
73
77
|
extractSessionLevelFindings: () => extractSessionLevelFindings,
|
|
78
|
+
extractShellDestinations: () => extractShellDestinations,
|
|
74
79
|
getCompiledRegex: () => getCompiledRegex,
|
|
75
80
|
getNestedValue: () => getNestedValue,
|
|
81
|
+
hostMatches: () => hostMatches,
|
|
76
82
|
isBashTool: () => isBashTool,
|
|
77
83
|
isIgnoredTool: () => isIgnoredTool,
|
|
84
|
+
isPrivateHost: () => isPrivateHost,
|
|
78
85
|
isProtectedHomePath: () => isProtectedHomePath,
|
|
79
86
|
isShieldVerdict: () => isShieldVerdict,
|
|
80
87
|
matchSensitivePath: () => matchSensitivePath,
|
|
@@ -82,6 +89,7 @@ __export(src_exports, {
|
|
|
82
89
|
narrativeRuleLabel: () => narrativeRuleLabel,
|
|
83
90
|
normalizeCommandForPolicy: () => normalizeCommandForPolicy,
|
|
84
91
|
parseAllSshHostsFromCommand: () => parseAllSshHostsFromCommand,
|
|
92
|
+
parseDestHost: () => parseDestHost,
|
|
85
93
|
previewArgs: () => previewArgs,
|
|
86
94
|
redactText: () => redactText,
|
|
87
95
|
scanArgs: () => scanArgs,
|
|
@@ -1072,6 +1080,201 @@ function extractLiteralArgs(callExpr) {
|
|
|
1072
1080
|
}
|
|
1073
1081
|
return { name, flags, paths };
|
|
1074
1082
|
}
|
|
1083
|
+
var NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
|
|
1084
|
+
var VALUE_FLAGS = {
|
|
1085
|
+
curl: /* @__PURE__ */ new Set([
|
|
1086
|
+
"-d",
|
|
1087
|
+
"--data",
|
|
1088
|
+
"--data-ascii",
|
|
1089
|
+
"--data-binary",
|
|
1090
|
+
"--data-raw",
|
|
1091
|
+
"--data-urlencode",
|
|
1092
|
+
"-F",
|
|
1093
|
+
"--form",
|
|
1094
|
+
"-H",
|
|
1095
|
+
"--header",
|
|
1096
|
+
"-X",
|
|
1097
|
+
"--request",
|
|
1098
|
+
"-o",
|
|
1099
|
+
"--output",
|
|
1100
|
+
"-T",
|
|
1101
|
+
"--upload-file",
|
|
1102
|
+
"-u",
|
|
1103
|
+
"--user",
|
|
1104
|
+
"-e",
|
|
1105
|
+
"--referer",
|
|
1106
|
+
"-A",
|
|
1107
|
+
"--user-agent",
|
|
1108
|
+
"-b",
|
|
1109
|
+
"--cookie",
|
|
1110
|
+
"-c",
|
|
1111
|
+
"--cookie-jar",
|
|
1112
|
+
"--connect-to",
|
|
1113
|
+
"--resolve",
|
|
1114
|
+
"--cacert",
|
|
1115
|
+
"--cert",
|
|
1116
|
+
"--key",
|
|
1117
|
+
"-x",
|
|
1118
|
+
"--proxy",
|
|
1119
|
+
"-m",
|
|
1120
|
+
"--max-time",
|
|
1121
|
+
"--retry"
|
|
1122
|
+
]),
|
|
1123
|
+
wget: /* @__PURE__ */ new Set([
|
|
1124
|
+
"-O",
|
|
1125
|
+
"--output-document",
|
|
1126
|
+
"--post-data",
|
|
1127
|
+
"--post-file",
|
|
1128
|
+
"--header",
|
|
1129
|
+
"-U",
|
|
1130
|
+
"--user-agent",
|
|
1131
|
+
"--user",
|
|
1132
|
+
"--password",
|
|
1133
|
+
"-o",
|
|
1134
|
+
"--output-file",
|
|
1135
|
+
"-P",
|
|
1136
|
+
"--directory-prefix",
|
|
1137
|
+
"-t",
|
|
1138
|
+
"--tries",
|
|
1139
|
+
"-T",
|
|
1140
|
+
"--timeout"
|
|
1141
|
+
]),
|
|
1142
|
+
scp: /* @__PURE__ */ new Set(["-i", "-F", "-l", "-o", "-c", "-S", "-P", "-J", "-D", "-W"]),
|
|
1143
|
+
ssh: /* @__PURE__ */ new Set([
|
|
1144
|
+
"-i",
|
|
1145
|
+
"-p",
|
|
1146
|
+
"-o",
|
|
1147
|
+
"-l",
|
|
1148
|
+
"-F",
|
|
1149
|
+
"-c",
|
|
1150
|
+
"-L",
|
|
1151
|
+
"-R",
|
|
1152
|
+
"-D",
|
|
1153
|
+
"-W",
|
|
1154
|
+
"-b",
|
|
1155
|
+
"-e",
|
|
1156
|
+
"-m",
|
|
1157
|
+
"-O",
|
|
1158
|
+
"-Q",
|
|
1159
|
+
"-S",
|
|
1160
|
+
"-J",
|
|
1161
|
+
"-w",
|
|
1162
|
+
"-B",
|
|
1163
|
+
"-I",
|
|
1164
|
+
"-E"
|
|
1165
|
+
]),
|
|
1166
|
+
nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
|
|
1167
|
+
};
|
|
1168
|
+
function resolveWordLiteral(w) {
|
|
1169
|
+
const parts = w?.Parts || [];
|
|
1170
|
+
let s = "";
|
|
1171
|
+
for (const p of parts) {
|
|
1172
|
+
const t = syntax.NodeType(p);
|
|
1173
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1174
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1175
|
+
else if (t === "DblQuoted") {
|
|
1176
|
+
const inner = p.Parts || [];
|
|
1177
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1178
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1179
|
+
} else {
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return s;
|
|
1184
|
+
}
|
|
1185
|
+
function parseDestHost(token) {
|
|
1186
|
+
if (!token) return null;
|
|
1187
|
+
let t = token.trim();
|
|
1188
|
+
if (!t || t.startsWith("-")) return null;
|
|
1189
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) {
|
|
1190
|
+
try {
|
|
1191
|
+
const h = new URL(t).hostname.toLowerCase();
|
|
1192
|
+
return h || null;
|
|
1193
|
+
} catch {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
const at = t.lastIndexOf("@");
|
|
1198
|
+
if (at >= 0) t = t.slice(at + 1);
|
|
1199
|
+
t = t.split("/")[0];
|
|
1200
|
+
t = t.replace(/:\d+$/, "");
|
|
1201
|
+
t = t.split(":")[0];
|
|
1202
|
+
t = t.toLowerCase();
|
|
1203
|
+
if (t.length > 253) return null;
|
|
1204
|
+
if (t === "localhost") return t;
|
|
1205
|
+
if (/^[a-z0-9.-]+\.[a-z0-9.-]+$/.test(t)) return t;
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
function destTokensForBinary(binary, args) {
|
|
1209
|
+
const valueFlags = VALUE_FLAGS[binary] ?? /* @__PURE__ */ new Set();
|
|
1210
|
+
const positionals = [];
|
|
1211
|
+
const urlFlagValues = [];
|
|
1212
|
+
for (let i = 0; i < args.length; i++) {
|
|
1213
|
+
const tok = args[i];
|
|
1214
|
+
if (tok === null) continue;
|
|
1215
|
+
if (tok.startsWith("-")) {
|
|
1216
|
+
if (tok.startsWith("--url=")) {
|
|
1217
|
+
urlFlagValues.push(tok.slice("--url=".length));
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (tok === "--url") {
|
|
1221
|
+
const next = args[i + 1];
|
|
1222
|
+
if (typeof next === "string") urlFlagValues.push(next);
|
|
1223
|
+
i++;
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (tok.includes("=")) continue;
|
|
1227
|
+
if (valueFlags.has(tok)) i++;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
positionals.push(tok);
|
|
1231
|
+
}
|
|
1232
|
+
switch (binary) {
|
|
1233
|
+
case "curl":
|
|
1234
|
+
case "wget":
|
|
1235
|
+
return [...urlFlagValues, ...positionals];
|
|
1236
|
+
case "ssh":
|
|
1237
|
+
return positionals.slice(0, 1);
|
|
1238
|
+
case "scp":
|
|
1239
|
+
return positionals.filter((p) => p.includes(":") || p.includes("@"));
|
|
1240
|
+
case "nc":
|
|
1241
|
+
case "ncat":
|
|
1242
|
+
case "netcat":
|
|
1243
|
+
return positionals.slice(0, 1);
|
|
1244
|
+
default:
|
|
1245
|
+
return [];
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function extractShellDestinations(command) {
|
|
1249
|
+
const f = parseShared(command);
|
|
1250
|
+
if (f === PARSE_FAIL) return [];
|
|
1251
|
+
const out = [];
|
|
1252
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1253
|
+
try {
|
|
1254
|
+
syntax.Walk(f, (node) => {
|
|
1255
|
+
if (!node) return false;
|
|
1256
|
+
const n = node;
|
|
1257
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1258
|
+
const callArgs = n.Args || [];
|
|
1259
|
+
if (callArgs.length === 0) return true;
|
|
1260
|
+
const name = (resolveWordLiteral(callArgs[0]) || "").toLowerCase();
|
|
1261
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1262
|
+
const rest = callArgs.slice(1).map((a) => resolveWordLiteral(a));
|
|
1263
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1264
|
+
const host = parseDestHost(raw);
|
|
1265
|
+
if (!host) continue;
|
|
1266
|
+
const key = `${name}:${host}`;
|
|
1267
|
+
if (seen.has(key)) continue;
|
|
1268
|
+
seen.add(key);
|
|
1269
|
+
out.push({ host, binary: name, raw });
|
|
1270
|
+
}
|
|
1271
|
+
return true;
|
|
1272
|
+
});
|
|
1273
|
+
} catch {
|
|
1274
|
+
return out;
|
|
1275
|
+
}
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1075
1278
|
var FS_OP_CACHE_MAX = 5e3;
|
|
1076
1279
|
var fsOpCache = /* @__PURE__ */ new Map();
|
|
1077
1280
|
function analyzeFsOperation(command) {
|
|
@@ -1202,6 +1405,85 @@ function analyzeShellCommand(command) {
|
|
|
1202
1405
|
return { actions, paths, allTokens };
|
|
1203
1406
|
}
|
|
1204
1407
|
|
|
1408
|
+
// src/egress/index.ts
|
|
1409
|
+
var DEFAULT_EGRESS_ALLOWLIST = [
|
|
1410
|
+
"*.github.com",
|
|
1411
|
+
"*.githubusercontent.com",
|
|
1412
|
+
"*.npmjs.org",
|
|
1413
|
+
"pypi.org",
|
|
1414
|
+
"*.pythonhosted.org",
|
|
1415
|
+
"crates.io",
|
|
1416
|
+
"*.crates.io",
|
|
1417
|
+
"rubygems.org",
|
|
1418
|
+
"proxy.golang.org",
|
|
1419
|
+
"sum.golang.org",
|
|
1420
|
+
"*.anthropic.com",
|
|
1421
|
+
"*.openai.com",
|
|
1422
|
+
"*.googleapis.com",
|
|
1423
|
+
"*.docker.io",
|
|
1424
|
+
"*.docker.com",
|
|
1425
|
+
"deb.debian.org",
|
|
1426
|
+
"*.ubuntu.com"
|
|
1427
|
+
];
|
|
1428
|
+
function hostMatches(host, pattern) {
|
|
1429
|
+
const h = host.toLowerCase();
|
|
1430
|
+
const p = pattern.toLowerCase().trim();
|
|
1431
|
+
if (!p) return false;
|
|
1432
|
+
if (p === "*") return true;
|
|
1433
|
+
if (p.startsWith("*.")) {
|
|
1434
|
+
const suffix = p.slice(2);
|
|
1435
|
+
return h === suffix || h.endsWith("." + suffix);
|
|
1436
|
+
}
|
|
1437
|
+
return h === p;
|
|
1438
|
+
}
|
|
1439
|
+
function matchesAny(host, patterns) {
|
|
1440
|
+
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
function isPrivateHost(host) {
|
|
1444
|
+
const h = host.toLowerCase();
|
|
1445
|
+
if (h === "localhost" || h === "0.0.0.0") return true;
|
|
1446
|
+
if (h.endsWith(".local") || h.endsWith(".internal") || h.endsWith(".localhost")) return true;
|
|
1447
|
+
if (/^127\./.test(h)) return true;
|
|
1448
|
+
if (/^10\./.test(h)) return true;
|
|
1449
|
+
if (/^192\.168\./.test(h)) return true;
|
|
1450
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
function evaluateEgress(dests, policy) {
|
|
1454
|
+
if (!policy.enabled) return null;
|
|
1455
|
+
let review = null;
|
|
1456
|
+
for (const d of dests) {
|
|
1457
|
+
if (matchesAny(d.host, policy.deny)) {
|
|
1458
|
+
return {
|
|
1459
|
+
verdict: "block",
|
|
1460
|
+
host: d.host,
|
|
1461
|
+
binary: d.binary,
|
|
1462
|
+
reason: `Egress to ${d.host} is on the deny list.`
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
if (policy.allowPrivate && isPrivateHost(d.host)) continue;
|
|
1466
|
+
if (matchesAny(d.host, policy.allow) || matchesAny(d.host, DEFAULT_EGRESS_ALLOWLIST)) continue;
|
|
1467
|
+
if (policy.mode === "block") {
|
|
1468
|
+
return {
|
|
1469
|
+
verdict: "block",
|
|
1470
|
+
host: d.host,
|
|
1471
|
+
binary: d.binary,
|
|
1472
|
+
reason: `Egress to unknown host ${d.host} is blocked (egress policy: block).`
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
if (policy.mode === "review" && !review) {
|
|
1476
|
+
review = {
|
|
1477
|
+
verdict: "review",
|
|
1478
|
+
host: d.host,
|
|
1479
|
+
binary: d.binary,
|
|
1480
|
+
reason: `${d.binary} is sending data to an unrecognized host (${d.host}). Approve this destination?`
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return review;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1205
1487
|
// src/policy/pipe-chain.ts
|
|
1206
1488
|
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1207
1489
|
"cat",
|
|
@@ -1795,6 +2077,22 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1795
2077
|
}
|
|
1796
2078
|
const ptVerdict = pipeChainVerdict(shellCommand, isTrustedHost);
|
|
1797
2079
|
if (ptVerdict) return ptVerdict;
|
|
2080
|
+
if (config.policy.egress?.enabled) {
|
|
2081
|
+
const dests = extractShellDestinations(shellCommand);
|
|
2082
|
+
if (dests.length > 0) {
|
|
2083
|
+
const eg = evaluateEgress(dests, config.policy.egress);
|
|
2084
|
+
if (eg) {
|
|
2085
|
+
return {
|
|
2086
|
+
decision: eg.verdict,
|
|
2087
|
+
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
2088
|
+
reason: eg.reason,
|
|
2089
|
+
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
2090
|
+
ruleDescription: eg.reason,
|
|
2091
|
+
tier: eg.verdict === "block" ? 3 : 4
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
1798
2096
|
const firstToken = analyzed.actions[0] ?? "";
|
|
1799
2097
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1800
2098
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
@@ -3014,11 +3312,25 @@ function detectPii(text) {
|
|
|
3014
3312
|
if (PII_CC_RE.test(text)) found.add("Credit Card");
|
|
3015
3313
|
return [...found];
|
|
3016
3314
|
}
|
|
3315
|
+
var REALTIME_PII_PATTERNS = ["SSN", "Credit Card"];
|
|
3316
|
+
var MAX_PII_SCAN_BYTES = 1e5;
|
|
3317
|
+
function detectArgsPii(args) {
|
|
3318
|
+
if (args === null || args === void 0) return [];
|
|
3319
|
+
let text;
|
|
3320
|
+
try {
|
|
3321
|
+
text = typeof args === "string" ? args : JSON.stringify(args);
|
|
3322
|
+
} catch {
|
|
3323
|
+
return [];
|
|
3324
|
+
}
|
|
3325
|
+
if (typeof text !== "string") return [];
|
|
3326
|
+
if (text.length > MAX_PII_SCAN_BYTES) text = text.slice(0, MAX_PII_SCAN_BYTES);
|
|
3327
|
+
return detectPii(text).filter((p) => REALTIME_PII_PATTERNS.includes(p));
|
|
3328
|
+
}
|
|
3017
3329
|
|
|
3018
3330
|
// src/scan/canonical.ts
|
|
3019
3331
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
3020
3332
|
var CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
3021
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
3333
|
+
var CANONICAL_EXTRACTOR_HASH = "dbe0199dae0f29f6";
|
|
3022
3334
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
3023
3335
|
function extractCanonicalFindings(call, ctx) {
|
|
3024
3336
|
const out = [];
|
|
@@ -3373,6 +3685,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3373
3685
|
CANONICAL_EXTRACTOR_HASH,
|
|
3374
3686
|
CANONICAL_EXTRACTOR_VERSION,
|
|
3375
3687
|
COST_PER_LOOP_ITER_USD,
|
|
3688
|
+
DEFAULT_EGRESS_ALLOWLIST,
|
|
3376
3689
|
DESTRUCTIVE_OP_RE,
|
|
3377
3690
|
DLP_PATTERNS,
|
|
3378
3691
|
ENGINE_VERSION,
|
|
@@ -3382,6 +3695,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3382
3695
|
LOOP_MAX_RECORDS,
|
|
3383
3696
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
3384
3697
|
PRIVILEGE_ESCALATION_RE,
|
|
3698
|
+
REALTIME_PII_PATTERNS,
|
|
3385
3699
|
SCAN_SIGNAL_WEIGHTS,
|
|
3386
3700
|
SENSITIVE_PATH_RE,
|
|
3387
3701
|
SENSITIVE_PATH_REGEXES,
|
|
@@ -3397,9 +3711,11 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3397
3711
|
computeScanScore,
|
|
3398
3712
|
computeSecurityScore,
|
|
3399
3713
|
dedupeCanonicalFindings,
|
|
3714
|
+
detectArgsPii,
|
|
3400
3715
|
detectDangerousEval,
|
|
3401
3716
|
detectDangerousShellExec,
|
|
3402
3717
|
detectPii,
|
|
3718
|
+
evaluateEgress,
|
|
3403
3719
|
evaluateLoopWindow,
|
|
3404
3720
|
evaluatePolicy,
|
|
3405
3721
|
evaluateSmartConditions,
|
|
@@ -3408,10 +3724,13 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3408
3724
|
extractNetworkTargets,
|
|
3409
3725
|
extractPositionalArgs,
|
|
3410
3726
|
extractSessionLevelFindings,
|
|
3727
|
+
extractShellDestinations,
|
|
3411
3728
|
getCompiledRegex,
|
|
3412
3729
|
getNestedValue,
|
|
3730
|
+
hostMatches,
|
|
3413
3731
|
isBashTool,
|
|
3414
3732
|
isIgnoredTool,
|
|
3733
|
+
isPrivateHost,
|
|
3415
3734
|
isProtectedHomePath,
|
|
3416
3735
|
isShieldVerdict,
|
|
3417
3736
|
matchSensitivePath,
|
|
@@ -3419,6 +3738,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
3419
3738
|
narrativeRuleLabel,
|
|
3420
3739
|
normalizeCommandForPolicy,
|
|
3421
3740
|
parseAllSshHostsFromCommand,
|
|
3741
|
+
parseDestHost,
|
|
3422
3742
|
previewArgs,
|
|
3423
3743
|
redactText,
|
|
3424
3744
|
scanArgs,
|
package/dist/index.mjs
CHANGED
|
@@ -973,6 +973,201 @@ function extractLiteralArgs(callExpr) {
|
|
|
973
973
|
}
|
|
974
974
|
return { name, flags, paths };
|
|
975
975
|
}
|
|
976
|
+
var NET_BINARIES = /* @__PURE__ */ new Set(["curl", "wget", "scp", "ssh", "nc", "ncat", "netcat"]);
|
|
977
|
+
var VALUE_FLAGS = {
|
|
978
|
+
curl: /* @__PURE__ */ new Set([
|
|
979
|
+
"-d",
|
|
980
|
+
"--data",
|
|
981
|
+
"--data-ascii",
|
|
982
|
+
"--data-binary",
|
|
983
|
+
"--data-raw",
|
|
984
|
+
"--data-urlencode",
|
|
985
|
+
"-F",
|
|
986
|
+
"--form",
|
|
987
|
+
"-H",
|
|
988
|
+
"--header",
|
|
989
|
+
"-X",
|
|
990
|
+
"--request",
|
|
991
|
+
"-o",
|
|
992
|
+
"--output",
|
|
993
|
+
"-T",
|
|
994
|
+
"--upload-file",
|
|
995
|
+
"-u",
|
|
996
|
+
"--user",
|
|
997
|
+
"-e",
|
|
998
|
+
"--referer",
|
|
999
|
+
"-A",
|
|
1000
|
+
"--user-agent",
|
|
1001
|
+
"-b",
|
|
1002
|
+
"--cookie",
|
|
1003
|
+
"-c",
|
|
1004
|
+
"--cookie-jar",
|
|
1005
|
+
"--connect-to",
|
|
1006
|
+
"--resolve",
|
|
1007
|
+
"--cacert",
|
|
1008
|
+
"--cert",
|
|
1009
|
+
"--key",
|
|
1010
|
+
"-x",
|
|
1011
|
+
"--proxy",
|
|
1012
|
+
"-m",
|
|
1013
|
+
"--max-time",
|
|
1014
|
+
"--retry"
|
|
1015
|
+
]),
|
|
1016
|
+
wget: /* @__PURE__ */ new Set([
|
|
1017
|
+
"-O",
|
|
1018
|
+
"--output-document",
|
|
1019
|
+
"--post-data",
|
|
1020
|
+
"--post-file",
|
|
1021
|
+
"--header",
|
|
1022
|
+
"-U",
|
|
1023
|
+
"--user-agent",
|
|
1024
|
+
"--user",
|
|
1025
|
+
"--password",
|
|
1026
|
+
"-o",
|
|
1027
|
+
"--output-file",
|
|
1028
|
+
"-P",
|
|
1029
|
+
"--directory-prefix",
|
|
1030
|
+
"-t",
|
|
1031
|
+
"--tries",
|
|
1032
|
+
"-T",
|
|
1033
|
+
"--timeout"
|
|
1034
|
+
]),
|
|
1035
|
+
scp: /* @__PURE__ */ new Set(["-i", "-F", "-l", "-o", "-c", "-S", "-P", "-J", "-D", "-W"]),
|
|
1036
|
+
ssh: /* @__PURE__ */ new Set([
|
|
1037
|
+
"-i",
|
|
1038
|
+
"-p",
|
|
1039
|
+
"-o",
|
|
1040
|
+
"-l",
|
|
1041
|
+
"-F",
|
|
1042
|
+
"-c",
|
|
1043
|
+
"-L",
|
|
1044
|
+
"-R",
|
|
1045
|
+
"-D",
|
|
1046
|
+
"-W",
|
|
1047
|
+
"-b",
|
|
1048
|
+
"-e",
|
|
1049
|
+
"-m",
|
|
1050
|
+
"-O",
|
|
1051
|
+
"-Q",
|
|
1052
|
+
"-S",
|
|
1053
|
+
"-J",
|
|
1054
|
+
"-w",
|
|
1055
|
+
"-B",
|
|
1056
|
+
"-I",
|
|
1057
|
+
"-E"
|
|
1058
|
+
]),
|
|
1059
|
+
nc: /* @__PURE__ */ new Set(["-p", "-s", "-w", "-X", "-x", "-e", "-g", "-G", "-i", "-O", "-T", "-q", "-m"])
|
|
1060
|
+
};
|
|
1061
|
+
function resolveWordLiteral(w) {
|
|
1062
|
+
const parts = w?.Parts || [];
|
|
1063
|
+
let s = "";
|
|
1064
|
+
for (const p of parts) {
|
|
1065
|
+
const t = syntax.NodeType(p);
|
|
1066
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1067
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1068
|
+
else if (t === "DblQuoted") {
|
|
1069
|
+
const inner = p.Parts || [];
|
|
1070
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1071
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1072
|
+
} else {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return s;
|
|
1077
|
+
}
|
|
1078
|
+
function parseDestHost(token) {
|
|
1079
|
+
if (!token) return null;
|
|
1080
|
+
let t = token.trim();
|
|
1081
|
+
if (!t || t.startsWith("-")) return null;
|
|
1082
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) {
|
|
1083
|
+
try {
|
|
1084
|
+
const h = new URL(t).hostname.toLowerCase();
|
|
1085
|
+
return h || null;
|
|
1086
|
+
} catch {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
const at = t.lastIndexOf("@");
|
|
1091
|
+
if (at >= 0) t = t.slice(at + 1);
|
|
1092
|
+
t = t.split("/")[0];
|
|
1093
|
+
t = t.replace(/:\d+$/, "");
|
|
1094
|
+
t = t.split(":")[0];
|
|
1095
|
+
t = t.toLowerCase();
|
|
1096
|
+
if (t.length > 253) return null;
|
|
1097
|
+
if (t === "localhost") return t;
|
|
1098
|
+
if (/^[a-z0-9.-]+\.[a-z0-9.-]+$/.test(t)) return t;
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
function destTokensForBinary(binary, args) {
|
|
1102
|
+
const valueFlags = VALUE_FLAGS[binary] ?? /* @__PURE__ */ new Set();
|
|
1103
|
+
const positionals = [];
|
|
1104
|
+
const urlFlagValues = [];
|
|
1105
|
+
for (let i = 0; i < args.length; i++) {
|
|
1106
|
+
const tok = args[i];
|
|
1107
|
+
if (tok === null) continue;
|
|
1108
|
+
if (tok.startsWith("-")) {
|
|
1109
|
+
if (tok.startsWith("--url=")) {
|
|
1110
|
+
urlFlagValues.push(tok.slice("--url=".length));
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (tok === "--url") {
|
|
1114
|
+
const next = args[i + 1];
|
|
1115
|
+
if (typeof next === "string") urlFlagValues.push(next);
|
|
1116
|
+
i++;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (tok.includes("=")) continue;
|
|
1120
|
+
if (valueFlags.has(tok)) i++;
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
positionals.push(tok);
|
|
1124
|
+
}
|
|
1125
|
+
switch (binary) {
|
|
1126
|
+
case "curl":
|
|
1127
|
+
case "wget":
|
|
1128
|
+
return [...urlFlagValues, ...positionals];
|
|
1129
|
+
case "ssh":
|
|
1130
|
+
return positionals.slice(0, 1);
|
|
1131
|
+
case "scp":
|
|
1132
|
+
return positionals.filter((p) => p.includes(":") || p.includes("@"));
|
|
1133
|
+
case "nc":
|
|
1134
|
+
case "ncat":
|
|
1135
|
+
case "netcat":
|
|
1136
|
+
return positionals.slice(0, 1);
|
|
1137
|
+
default:
|
|
1138
|
+
return [];
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function extractShellDestinations(command) {
|
|
1142
|
+
const f = parseShared(command);
|
|
1143
|
+
if (f === PARSE_FAIL) return [];
|
|
1144
|
+
const out = [];
|
|
1145
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1146
|
+
try {
|
|
1147
|
+
syntax.Walk(f, (node) => {
|
|
1148
|
+
if (!node) return false;
|
|
1149
|
+
const n = node;
|
|
1150
|
+
if (syntax.NodeType(n) !== "CallExpr") return true;
|
|
1151
|
+
const callArgs = n.Args || [];
|
|
1152
|
+
if (callArgs.length === 0) return true;
|
|
1153
|
+
const name = (resolveWordLiteral(callArgs[0]) || "").toLowerCase();
|
|
1154
|
+
if (!NET_BINARIES.has(name)) return true;
|
|
1155
|
+
const rest = callArgs.slice(1).map((a) => resolveWordLiteral(a));
|
|
1156
|
+
for (const raw of destTokensForBinary(name, rest)) {
|
|
1157
|
+
const host = parseDestHost(raw);
|
|
1158
|
+
if (!host) continue;
|
|
1159
|
+
const key = `${name}:${host}`;
|
|
1160
|
+
if (seen.has(key)) continue;
|
|
1161
|
+
seen.add(key);
|
|
1162
|
+
out.push({ host, binary: name, raw });
|
|
1163
|
+
}
|
|
1164
|
+
return true;
|
|
1165
|
+
});
|
|
1166
|
+
} catch {
|
|
1167
|
+
return out;
|
|
1168
|
+
}
|
|
1169
|
+
return out;
|
|
1170
|
+
}
|
|
976
1171
|
var FS_OP_CACHE_MAX = 5e3;
|
|
977
1172
|
var fsOpCache = /* @__PURE__ */ new Map();
|
|
978
1173
|
function analyzeFsOperation(command) {
|
|
@@ -1103,6 +1298,85 @@ function analyzeShellCommand(command) {
|
|
|
1103
1298
|
return { actions, paths, allTokens };
|
|
1104
1299
|
}
|
|
1105
1300
|
|
|
1301
|
+
// src/egress/index.ts
|
|
1302
|
+
var DEFAULT_EGRESS_ALLOWLIST = [
|
|
1303
|
+
"*.github.com",
|
|
1304
|
+
"*.githubusercontent.com",
|
|
1305
|
+
"*.npmjs.org",
|
|
1306
|
+
"pypi.org",
|
|
1307
|
+
"*.pythonhosted.org",
|
|
1308
|
+
"crates.io",
|
|
1309
|
+
"*.crates.io",
|
|
1310
|
+
"rubygems.org",
|
|
1311
|
+
"proxy.golang.org",
|
|
1312
|
+
"sum.golang.org",
|
|
1313
|
+
"*.anthropic.com",
|
|
1314
|
+
"*.openai.com",
|
|
1315
|
+
"*.googleapis.com",
|
|
1316
|
+
"*.docker.io",
|
|
1317
|
+
"*.docker.com",
|
|
1318
|
+
"deb.debian.org",
|
|
1319
|
+
"*.ubuntu.com"
|
|
1320
|
+
];
|
|
1321
|
+
function hostMatches(host, pattern) {
|
|
1322
|
+
const h = host.toLowerCase();
|
|
1323
|
+
const p = pattern.toLowerCase().trim();
|
|
1324
|
+
if (!p) return false;
|
|
1325
|
+
if (p === "*") return true;
|
|
1326
|
+
if (p.startsWith("*.")) {
|
|
1327
|
+
const suffix = p.slice(2);
|
|
1328
|
+
return h === suffix || h.endsWith("." + suffix);
|
|
1329
|
+
}
|
|
1330
|
+
return h === p;
|
|
1331
|
+
}
|
|
1332
|
+
function matchesAny(host, patterns) {
|
|
1333
|
+
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
function isPrivateHost(host) {
|
|
1337
|
+
const h = host.toLowerCase();
|
|
1338
|
+
if (h === "localhost" || h === "0.0.0.0") return true;
|
|
1339
|
+
if (h.endsWith(".local") || h.endsWith(".internal") || h.endsWith(".localhost")) return true;
|
|
1340
|
+
if (/^127\./.test(h)) return true;
|
|
1341
|
+
if (/^10\./.test(h)) return true;
|
|
1342
|
+
if (/^192\.168\./.test(h)) return true;
|
|
1343
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
function evaluateEgress(dests, policy) {
|
|
1347
|
+
if (!policy.enabled) return null;
|
|
1348
|
+
let review = null;
|
|
1349
|
+
for (const d of dests) {
|
|
1350
|
+
if (matchesAny(d.host, policy.deny)) {
|
|
1351
|
+
return {
|
|
1352
|
+
verdict: "block",
|
|
1353
|
+
host: d.host,
|
|
1354
|
+
binary: d.binary,
|
|
1355
|
+
reason: `Egress to ${d.host} is on the deny list.`
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
if (policy.allowPrivate && isPrivateHost(d.host)) continue;
|
|
1359
|
+
if (matchesAny(d.host, policy.allow) || matchesAny(d.host, DEFAULT_EGRESS_ALLOWLIST)) continue;
|
|
1360
|
+
if (policy.mode === "block") {
|
|
1361
|
+
return {
|
|
1362
|
+
verdict: "block",
|
|
1363
|
+
host: d.host,
|
|
1364
|
+
binary: d.binary,
|
|
1365
|
+
reason: `Egress to unknown host ${d.host} is blocked (egress policy: block).`
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
if (policy.mode === "review" && !review) {
|
|
1369
|
+
review = {
|
|
1370
|
+
verdict: "review",
|
|
1371
|
+
host: d.host,
|
|
1372
|
+
binary: d.binary,
|
|
1373
|
+
reason: `${d.binary} is sending data to an unrecognized host (${d.host}). Approve this destination?`
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return review;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1106
1380
|
// src/policy/pipe-chain.ts
|
|
1107
1381
|
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
1108
1382
|
"cat",
|
|
@@ -1696,6 +1970,22 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
1696
1970
|
}
|
|
1697
1971
|
const ptVerdict = pipeChainVerdict(shellCommand, isTrustedHost);
|
|
1698
1972
|
if (ptVerdict) return ptVerdict;
|
|
1973
|
+
if (config.policy.egress?.enabled) {
|
|
1974
|
+
const dests = extractShellDestinations(shellCommand);
|
|
1975
|
+
if (dests.length > 0) {
|
|
1976
|
+
const eg = evaluateEgress(dests, config.policy.egress);
|
|
1977
|
+
if (eg) {
|
|
1978
|
+
return {
|
|
1979
|
+
decision: eg.verdict,
|
|
1980
|
+
blockedByLabel: eg.verdict === "block" ? "\u{1F310} Node9 Egress (Blocked)" : "\u{1F310} Node9 Egress (Review)",
|
|
1981
|
+
reason: eg.reason,
|
|
1982
|
+
ruleName: `egress:${eg.binary}:${eg.host}`,
|
|
1983
|
+
ruleDescription: eg.reason,
|
|
1984
|
+
tier: eg.verdict === "block" ? 3 : 4
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1699
1989
|
const firstToken = analyzed.actions[0] ?? "";
|
|
1700
1990
|
if (["ssh", "scp", "rsync"].includes(firstToken)) {
|
|
1701
1991
|
const rawTokens = shellCommand.trim().split(/\s+/);
|
|
@@ -2915,11 +3205,25 @@ function detectPii(text) {
|
|
|
2915
3205
|
if (PII_CC_RE.test(text)) found.add("Credit Card");
|
|
2916
3206
|
return [...found];
|
|
2917
3207
|
}
|
|
3208
|
+
var REALTIME_PII_PATTERNS = ["SSN", "Credit Card"];
|
|
3209
|
+
var MAX_PII_SCAN_BYTES = 1e5;
|
|
3210
|
+
function detectArgsPii(args) {
|
|
3211
|
+
if (args === null || args === void 0) return [];
|
|
3212
|
+
let text;
|
|
3213
|
+
try {
|
|
3214
|
+
text = typeof args === "string" ? args : JSON.stringify(args);
|
|
3215
|
+
} catch {
|
|
3216
|
+
return [];
|
|
3217
|
+
}
|
|
3218
|
+
if (typeof text !== "string") return [];
|
|
3219
|
+
if (text.length > MAX_PII_SCAN_BYTES) text = text.slice(0, MAX_PII_SCAN_BYTES);
|
|
3220
|
+
return detectPii(text).filter((p) => REALTIME_PII_PATTERNS.includes(p));
|
|
3221
|
+
}
|
|
2918
3222
|
|
|
2919
3223
|
// src/scan/canonical.ts
|
|
2920
3224
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
2921
3225
|
var CANONICAL_EXTRACTOR_VERSION = "canonical-v4";
|
|
2922
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
3226
|
+
var CANONICAL_EXTRACTOR_HASH = "dbe0199dae0f29f6";
|
|
2923
3227
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
2924
3228
|
function extractCanonicalFindings(call, ctx) {
|
|
2925
3229
|
const out = [];
|
|
@@ -3273,6 +3577,7 @@ export {
|
|
|
3273
3577
|
CANONICAL_EXTRACTOR_HASH,
|
|
3274
3578
|
CANONICAL_EXTRACTOR_VERSION,
|
|
3275
3579
|
COST_PER_LOOP_ITER_USD,
|
|
3580
|
+
DEFAULT_EGRESS_ALLOWLIST,
|
|
3276
3581
|
DESTRUCTIVE_OP_RE,
|
|
3277
3582
|
DLP_PATTERNS,
|
|
3278
3583
|
ENGINE_VERSION,
|
|
@@ -3282,6 +3587,7 @@ export {
|
|
|
3282
3587
|
LOOP_MAX_RECORDS,
|
|
3283
3588
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
3284
3589
|
PRIVILEGE_ESCALATION_RE,
|
|
3590
|
+
REALTIME_PII_PATTERNS,
|
|
3285
3591
|
SCAN_SIGNAL_WEIGHTS,
|
|
3286
3592
|
SENSITIVE_PATH_RE,
|
|
3287
3593
|
SENSITIVE_PATH_REGEXES,
|
|
@@ -3297,9 +3603,11 @@ export {
|
|
|
3297
3603
|
computeScanScore,
|
|
3298
3604
|
computeSecurityScore,
|
|
3299
3605
|
dedupeCanonicalFindings,
|
|
3606
|
+
detectArgsPii,
|
|
3300
3607
|
detectDangerousEval,
|
|
3301
3608
|
detectDangerousShellExec,
|
|
3302
3609
|
detectPii,
|
|
3610
|
+
evaluateEgress,
|
|
3303
3611
|
evaluateLoopWindow,
|
|
3304
3612
|
evaluatePolicy,
|
|
3305
3613
|
evaluateSmartConditions,
|
|
@@ -3308,10 +3616,13 @@ export {
|
|
|
3308
3616
|
extractNetworkTargets,
|
|
3309
3617
|
extractPositionalArgs,
|
|
3310
3618
|
extractSessionLevelFindings,
|
|
3619
|
+
extractShellDestinations,
|
|
3311
3620
|
getCompiledRegex,
|
|
3312
3621
|
getNestedValue,
|
|
3622
|
+
hostMatches,
|
|
3313
3623
|
isBashTool,
|
|
3314
3624
|
isIgnoredTool,
|
|
3625
|
+
isPrivateHost,
|
|
3315
3626
|
isProtectedHomePath,
|
|
3316
3627
|
isShieldVerdict,
|
|
3317
3628
|
matchSensitivePath,
|
|
@@ -3319,6 +3630,7 @@ export {
|
|
|
3319
3630
|
narrativeRuleLabel,
|
|
3320
3631
|
normalizeCommandForPolicy,
|
|
3321
3632
|
parseAllSshHostsFromCommand,
|
|
3633
|
+
parseDestHost,
|
|
3322
3634
|
previewArgs,
|
|
3323
3635
|
redactText,
|
|
3324
3636
|
scanArgs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.31.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",
|