@node9/policy-engine 2.12.0 → 2.13.1
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 +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +176 -86
- package/dist/index.mjs +173 -86
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -217,6 +217,7 @@ interface FsOpVerdict {
|
|
|
217
217
|
declare const BASH_TOOL_NAMES: Set<string>;
|
|
218
218
|
declare function isBashTool(toolName: string): boolean;
|
|
219
219
|
declare const AST_FS_REGEX_RULES: Set<string>;
|
|
220
|
+
declare const COMMAND_WRAPPERS: Set<string>;
|
|
220
221
|
/**
|
|
221
222
|
* Does `toolName` carry a shell command? True for BASH_TOOL_NAMES spellings and
|
|
222
223
|
* for any tool whose toolInspection field is `command` (e.g. `terminal.execute`).
|
|
@@ -235,6 +236,11 @@ declare function isShellShapedTool(toolName: string, toolInspection?: Record<str
|
|
|
235
236
|
* void them. An absent rule scope matches everything (callers' prior semantics).
|
|
236
237
|
*/
|
|
237
238
|
declare function toolMatchesRule(toolName: string, ruleTool: string | string[] | undefined, toolInspection?: Record<string, string>): boolean;
|
|
239
|
+
/** Strip leading wrappers/runners from a resolved arg list, returning the index
|
|
240
|
+
* of the real command head. Handles `sudo -u www python3`, `env -u FOO python3`,
|
|
241
|
+
* `timeout 5 python3`, `uv run python`, `conda run -n env python`,
|
|
242
|
+
* `chroot /mnt python3`. */
|
|
243
|
+
declare function unwrapCommandHead(words: (string | null)[]): number;
|
|
238
244
|
/**
|
|
239
245
|
* AST-aware inline-execution detector. Returns true when the command runs code
|
|
240
246
|
* supplied on the command line, via stdin, or via a pipe into a bare
|
|
@@ -255,6 +261,7 @@ interface ShellDestination {
|
|
|
255
261
|
/** The raw argument token the host came from (for UI / audit). */
|
|
256
262
|
raw: string;
|
|
257
263
|
}
|
|
264
|
+
declare const NET_BINARIES: Set<string>;
|
|
258
265
|
/**
|
|
259
266
|
* Parse a destination host out of a single token. Handles scheme URLs
|
|
260
267
|
* (`https://h/p`), scheme-less curl targets (`evil.com/p`), `user@host:path`
|
|
@@ -1196,7 +1203,7 @@ declare const LONG_OUTPUT_THRESHOLD_BYTES: number;
|
|
|
1196
1203
|
* and fails CI when the hash drifts without a version bump — forgetting
|
|
1197
1204
|
* is loud, not silent.
|
|
1198
1205
|
*/
|
|
1199
|
-
declare const CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
1206
|
+
declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
|
|
1200
1207
|
/**
|
|
1201
1208
|
* SHA-256 prefix of the detector-source files
|
|
1202
1209
|
* (canonical.ts + pii.ts + destructive-regex.ts).
|
|
@@ -1207,7 +1214,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
|
|
|
1207
1214
|
* files changed, this hash must change too, and you must consciously
|
|
1208
1215
|
* decide whether to bump CANONICAL_EXTRACTOR_VERSION."
|
|
1209
1216
|
*/
|
|
1210
|
-
declare const CANONICAL_EXTRACTOR_HASH = "
|
|
1217
|
+
declare const CANONICAL_EXTRACTOR_HASH = "4622a2f41696af04";
|
|
1211
1218
|
declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
|
|
1212
1219
|
declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
|
|
1213
1220
|
/**
|
|
@@ -1327,4 +1334,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1327
1334
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1328
1335
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1329
1336
|
|
|
1330
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTINATION_ARGS, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1337
|
+
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, 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, NET_BINARIES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -217,6 +217,7 @@ interface FsOpVerdict {
|
|
|
217
217
|
declare const BASH_TOOL_NAMES: Set<string>;
|
|
218
218
|
declare function isBashTool(toolName: string): boolean;
|
|
219
219
|
declare const AST_FS_REGEX_RULES: Set<string>;
|
|
220
|
+
declare const COMMAND_WRAPPERS: Set<string>;
|
|
220
221
|
/**
|
|
221
222
|
* Does `toolName` carry a shell command? True for BASH_TOOL_NAMES spellings and
|
|
222
223
|
* for any tool whose toolInspection field is `command` (e.g. `terminal.execute`).
|
|
@@ -235,6 +236,11 @@ declare function isShellShapedTool(toolName: string, toolInspection?: Record<str
|
|
|
235
236
|
* void them. An absent rule scope matches everything (callers' prior semantics).
|
|
236
237
|
*/
|
|
237
238
|
declare function toolMatchesRule(toolName: string, ruleTool: string | string[] | undefined, toolInspection?: Record<string, string>): boolean;
|
|
239
|
+
/** Strip leading wrappers/runners from a resolved arg list, returning the index
|
|
240
|
+
* of the real command head. Handles `sudo -u www python3`, `env -u FOO python3`,
|
|
241
|
+
* `timeout 5 python3`, `uv run python`, `conda run -n env python`,
|
|
242
|
+
* `chroot /mnt python3`. */
|
|
243
|
+
declare function unwrapCommandHead(words: (string | null)[]): number;
|
|
238
244
|
/**
|
|
239
245
|
* AST-aware inline-execution detector. Returns true when the command runs code
|
|
240
246
|
* supplied on the command line, via stdin, or via a pipe into a bare
|
|
@@ -255,6 +261,7 @@ interface ShellDestination {
|
|
|
255
261
|
/** The raw argument token the host came from (for UI / audit). */
|
|
256
262
|
raw: string;
|
|
257
263
|
}
|
|
264
|
+
declare const NET_BINARIES: Set<string>;
|
|
258
265
|
/**
|
|
259
266
|
* Parse a destination host out of a single token. Handles scheme URLs
|
|
260
267
|
* (`https://h/p`), scheme-less curl targets (`evil.com/p`), `user@host:path`
|
|
@@ -1196,7 +1203,7 @@ declare const LONG_OUTPUT_THRESHOLD_BYTES: number;
|
|
|
1196
1203
|
* and fails CI when the hash drifts without a version bump — forgetting
|
|
1197
1204
|
* is loud, not silent.
|
|
1198
1205
|
*/
|
|
1199
|
-
declare const CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
1206
|
+
declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
|
|
1200
1207
|
/**
|
|
1201
1208
|
* SHA-256 prefix of the detector-source files
|
|
1202
1209
|
* (canonical.ts + pii.ts + destructive-regex.ts).
|
|
@@ -1207,7 +1214,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
|
|
|
1207
1214
|
* files changed, this hash must change too, and you must consciously
|
|
1208
1215
|
* decide whether to bump CANONICAL_EXTRACTOR_VERSION."
|
|
1209
1216
|
*/
|
|
1210
|
-
declare const CANONICAL_EXTRACTOR_HASH = "
|
|
1217
|
+
declare const CANONICAL_EXTRACTOR_HASH = "4622a2f41696af04";
|
|
1211
1218
|
declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
|
|
1212
1219
|
declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
|
|
1213
1220
|
/**
|
|
@@ -1327,4 +1334,4 @@ declare function ssrfDestinationFloor(toolName: string, args: unknown, opts?: {
|
|
|
1327
1334
|
/** Engine version stamped on audit entries for future drift detection. */
|
|
1328
1335
|
declare const ENGINE_VERSION = "1.4.0";
|
|
1329
1336
|
|
|
1330
|
-
export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTINATION_ARGS, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
|
|
1337
|
+
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, 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, NET_BINARIES, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, isStrictGatedTier, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfDestinationFloor, ssrfFloor, ssrfReason, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, unwrapCommandHead, validateOverrides, validateRegex, validateShieldDefinition };
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(src_exports, {
|
|
|
36
36
|
CANARY_MIN_LENGTH: () => CANARY_MIN_LENGTH,
|
|
37
37
|
CANONICAL_EXTRACTOR_HASH: () => CANONICAL_EXTRACTOR_HASH,
|
|
38
38
|
CANONICAL_EXTRACTOR_VERSION: () => CANONICAL_EXTRACTOR_VERSION,
|
|
39
|
+
COMMAND_WRAPPERS: () => COMMAND_WRAPPERS,
|
|
39
40
|
COST_PER_LOOP_ITER_USD: () => COST_PER_LOOP_ITER_USD,
|
|
40
41
|
DEFAULT_EGRESS_ALLOWLIST: () => DEFAULT_EGRESS_ALLOWLIST,
|
|
41
42
|
DESTINATION_ARGS: () => DESTINATION_ARGS,
|
|
@@ -49,6 +50,7 @@ __export(src_exports, {
|
|
|
49
50
|
LONG_OUTPUT_THRESHOLD_BYTES: () => LONG_OUTPUT_THRESHOLD_BYTES,
|
|
50
51
|
LOOP_MAX_RECORDS: () => LOOP_MAX_RECORDS,
|
|
51
52
|
LOOP_THRESHOLD_FOR_WASTE: () => LOOP_THRESHOLD_FOR_WASTE,
|
|
53
|
+
NET_BINARIES: () => NET_BINARIES,
|
|
52
54
|
PRIVILEGE_ESCALATION_RE: () => PRIVILEGE_ESCALATION_RE,
|
|
53
55
|
REALTIME_PII_PATTERNS: () => REALTIME_PII_PATTERNS,
|
|
54
56
|
SCAN_SIGNAL_WEIGHTS: () => SCAN_SIGNAL_WEIGHTS,
|
|
@@ -119,6 +121,7 @@ __export(src_exports, {
|
|
|
119
121
|
toScanFinding: () => toScanFinding,
|
|
120
122
|
toolMatchesRule: () => toolMatchesRule,
|
|
121
123
|
truncateBlastPath: () => truncateBlastPath,
|
|
124
|
+
unwrapCommandHead: () => unwrapCommandHead,
|
|
122
125
|
validateOverrides: () => validateOverrides,
|
|
123
126
|
validateRegex: () => validateRegex,
|
|
124
127
|
validateShieldDefinition: () => validateShieldDefinition
|
|
@@ -844,13 +847,32 @@ var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
|
844
847
|
})
|
|
845
848
|
);
|
|
846
849
|
var SENSITIVE_PATH_PATTERNS = [
|
|
847
|
-
/[/\\]\.ssh[/\\]/i,
|
|
848
|
-
/[/\\]\.aws[/\\]/i,
|
|
850
|
+
/[/\\]\.ssh([/\\]|$)/i,
|
|
851
|
+
/[/\\]\.aws([/\\]|$)/i,
|
|
849
852
|
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
850
853
|
/[/\\]\.azure[/\\]/i,
|
|
851
854
|
/[/\\]\.kube[/\\]config$/i,
|
|
852
|
-
|
|
853
|
-
// .
|
|
855
|
+
// ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
|
|
856
|
+
// (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
|
|
857
|
+
// structural suffix chain rather than a hand-written list, `example|sample|
|
|
858
|
+
// template` exempt because a fixture stays a fixture whatever follows, and
|
|
859
|
+
// `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
|
|
860
|
+
// committed template, `.env.test.local` is gitignored and holds real values.
|
|
861
|
+
//
|
|
862
|
+
// It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
|
|
863
|
+
// blocked while `cat .env.example` allowed: the same file, opposite verdicts,
|
|
864
|
+
// decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
|
|
865
|
+
// which is the contract that now holds these copies in step, and stage 5 of
|
|
866
|
+
// doc/credential-jail-architecture.md, which replaces them with one generated
|
|
867
|
+
// source.
|
|
868
|
+
// ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
|
|
869
|
+
// fixture whatever follows it -- `.env.example.md` is documentation -- but
|
|
870
|
+
// `.env.example.local` is gitignored by the `.env*.local` convention and holds
|
|
871
|
+
// real values, exactly the reasoning that anchors `(?!\.test$)` rather than
|
|
872
|
+
// using `\b`. Without this branch the fixture exemption also bought a two-step
|
|
873
|
+
// bypass: `cp .env .env.sample`, then read the copy.
|
|
874
|
+
/[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
875
|
+
// .env + any suffix chain; fixtures exempt unless .local
|
|
854
876
|
/[/\\]\.git-credentials$/i,
|
|
855
877
|
/[/\\]\.npmrc$/i,
|
|
856
878
|
/[/\\]\.docker[/\\]config\.json$/i,
|
|
@@ -1409,6 +1431,10 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1409
1431
|
"od",
|
|
1410
1432
|
"xxd",
|
|
1411
1433
|
"hexdump",
|
|
1434
|
+
// Emits the file's bytes, re-encoded, so it is a read by the set's own test
|
|
1435
|
+
// ("does it emit file contents"). Absent until 2026-09-10, which is why
|
|
1436
|
+
// `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
|
|
1437
|
+
"base64",
|
|
1412
1438
|
"strings",
|
|
1413
1439
|
"sort",
|
|
1414
1440
|
"uniq",
|
|
@@ -1417,7 +1443,11 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1417
1443
|
"dd"
|
|
1418
1444
|
]);
|
|
1419
1445
|
var FS_OP_PRESCREEN_RE = new RegExp(
|
|
1420
|
-
|
|
1446
|
+
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
1447
|
+
// reader right after `"` / `'`, and without these two characters the
|
|
1448
|
+
// prescreen rejected every string-wrapped read before the parser ran.
|
|
1449
|
+
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
1450
|
+
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
1421
1451
|
);
|
|
1422
1452
|
var HOME_CACHE_ALLOWLIST = [
|
|
1423
1453
|
".cache",
|
|
@@ -1438,12 +1468,12 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1438
1468
|
{
|
|
1439
1469
|
rule: "shield:project-jail:block-read-ssh",
|
|
1440
1470
|
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1441
|
-
match: (p) => /(
|
|
1471
|
+
match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
|
|
1442
1472
|
},
|
|
1443
1473
|
{
|
|
1444
1474
|
rule: "shield:project-jail:block-read-aws",
|
|
1445
1475
|
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
1446
|
-
match: (p) => /(
|
|
1476
|
+
match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
|
|
1447
1477
|
},
|
|
1448
1478
|
{
|
|
1449
1479
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
@@ -1487,7 +1517,9 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1487
1517
|
// symmetry — silently exempts every `.env.test.*` file.
|
|
1488
1518
|
//
|
|
1489
1519
|
// shields.test.ts:983-995 is the canonical contract; keep both in step.
|
|
1490
|
-
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]
|
|
1520
|
+
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
|
|
1521
|
+
p
|
|
1522
|
+
)
|
|
1491
1523
|
},
|
|
1492
1524
|
{
|
|
1493
1525
|
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
@@ -1698,15 +1730,14 @@ function listOps() {
|
|
|
1698
1730
|
return _listOps;
|
|
1699
1731
|
}
|
|
1700
1732
|
var WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
|
|
1733
|
+
var FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
1701
1734
|
var INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
1702
1735
|
function unwrapCommandHead(words) {
|
|
1703
1736
|
let i = 0;
|
|
1704
1737
|
while (i < words.length) {
|
|
1705
1738
|
const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
|
|
1706
1739
|
if (head === "find") {
|
|
1707
|
-
const x = words.findIndex(
|
|
1708
|
-
(w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
|
|
1709
|
-
);
|
|
1740
|
+
const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1710
1741
|
if (x < 0) break;
|
|
1711
1742
|
i = x + 1;
|
|
1712
1743
|
continue;
|
|
@@ -1728,7 +1759,11 @@ function unwrapCommandHead(words) {
|
|
|
1728
1759
|
if (t.startsWith("-")) {
|
|
1729
1760
|
i++;
|
|
1730
1761
|
const nxt = words[i];
|
|
1731
|
-
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase())
|
|
1762
|
+
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
|
|
1763
|
+
// `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
|
|
1764
|
+
// swallowed and the jail needed a looser fallback whose cost was a
|
|
1765
|
+
// false positive on `sudo echo cat X`.
|
|
1766
|
+
!FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
|
|
1732
1767
|
i++;
|
|
1733
1768
|
continue;
|
|
1734
1769
|
}
|
|
@@ -1849,36 +1884,29 @@ function isProtectedHomePath(rawPath) {
|
|
|
1849
1884
|
}
|
|
1850
1885
|
function extractLiteralArgs(callExpr) {
|
|
1851
1886
|
const args = callExpr.Args || [];
|
|
1852
|
-
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
let s = "";
|
|
1856
|
-
for (const p of parts) {
|
|
1857
|
-
const t = syntax.NodeType(p);
|
|
1858
|
-
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1859
|
-
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1860
|
-
else if (t === "DblQuoted") {
|
|
1861
|
-
const inner = p.Parts || [];
|
|
1862
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1863
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1864
|
-
} else {
|
|
1865
|
-
return null;
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
return s;
|
|
1869
|
-
};
|
|
1870
|
-
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
1887
|
+
if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
|
|
1888
|
+
const words = args.map((a) => resolveWordLiteral(a));
|
|
1889
|
+
const name = (words[0] ?? "").toLowerCase();
|
|
1871
1890
|
const flags = [];
|
|
1872
1891
|
const paths = [];
|
|
1873
|
-
for (let i = 1; i <
|
|
1874
|
-
const v =
|
|
1892
|
+
for (let i = 1; i < words.length; i++) {
|
|
1893
|
+
const v = words[i];
|
|
1875
1894
|
if (v === null) continue;
|
|
1876
1895
|
if (v.startsWith("-")) flags.push(v);
|
|
1877
1896
|
else paths.push(v);
|
|
1878
1897
|
}
|
|
1879
|
-
return { name, flags, paths };
|
|
1898
|
+
return { name, flags, paths, words };
|
|
1880
1899
|
}
|
|
1881
|
-
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
1900
|
+
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
1901
|
+
"curl",
|
|
1902
|
+
"wget",
|
|
1903
|
+
"scp",
|
|
1904
|
+
"ssh",
|
|
1905
|
+
"nc",
|
|
1906
|
+
"ncat",
|
|
1907
|
+
"netcat",
|
|
1908
|
+
"rsync"
|
|
1909
|
+
]);
|
|
1882
1910
|
var VALUE_FLAGS = {
|
|
1883
1911
|
curl: /* @__PURE__ */ new Set([
|
|
1884
1912
|
"-d",
|
|
@@ -2163,6 +2191,9 @@ function deriveRedirOp(sample) {
|
|
|
2163
2191
|
}
|
|
2164
2192
|
}
|
|
2165
2193
|
var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
2194
|
+
var REDIR_FILE_IN_OPS = new Set(
|
|
2195
|
+
[deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
|
|
2196
|
+
);
|
|
2166
2197
|
var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
2167
2198
|
deriveRedirOp("cat <<X\nX"),
|
|
2168
2199
|
deriveRedirOp("cat <<-X\nX")
|
|
@@ -2227,16 +2258,21 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
2227
2258
|
}
|
|
2228
2259
|
return sawRm && ok;
|
|
2229
2260
|
}
|
|
2230
|
-
function analyzeFsOperationImpl(command) {
|
|
2261
|
+
function analyzeFsOperationImpl(command, depth = 0) {
|
|
2231
2262
|
const f = parseShared(command);
|
|
2232
2263
|
if (f === PARSE_FAIL) return null;
|
|
2233
2264
|
let result = null;
|
|
2234
2265
|
try {
|
|
2235
2266
|
syntax.Walk(f, (node) => {
|
|
2236
|
-
if (!node || result) return false;
|
|
2267
|
+
if (!node || result?.verdict === "block") return false;
|
|
2237
2268
|
const n = node;
|
|
2238
|
-
|
|
2239
|
-
|
|
2269
|
+
const nodeType = syntax.NodeType(n);
|
|
2270
|
+
if (nodeType === "Stmt") {
|
|
2271
|
+
result = stricter(result, jailedRedirectRead(n));
|
|
2272
|
+
return result?.verdict !== "block";
|
|
2273
|
+
}
|
|
2274
|
+
if (nodeType !== "CallExpr") return true;
|
|
2275
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2240
2276
|
if (!name) return true;
|
|
2241
2277
|
if (name === "rm") {
|
|
2242
2278
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -2265,19 +2301,22 @@ function analyzeFsOperationImpl(command) {
|
|
|
2265
2301
|
}
|
|
2266
2302
|
}
|
|
2267
2303
|
}
|
|
2268
|
-
if (
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
reason: sp.reason,
|
|
2276
|
-
path: p
|
|
2277
|
-
};
|
|
2278
|
-
return false;
|
|
2279
|
-
}
|
|
2304
|
+
if (depth < 1) {
|
|
2305
|
+
const payload = literalShellPayload(words, name);
|
|
2306
|
+
if (payload !== null) {
|
|
2307
|
+
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
2308
|
+
if (inner) {
|
|
2309
|
+
result = inner;
|
|
2310
|
+
return false;
|
|
2280
2311
|
}
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
|
|
2316
|
+
if (readPaths) {
|
|
2317
|
+
for (const p of readPaths) {
|
|
2318
|
+
result = stricter(result, matchSensitivePath2(p));
|
|
2319
|
+
if (result?.verdict === "block") return false;
|
|
2281
2320
|
}
|
|
2282
2321
|
}
|
|
2283
2322
|
return true;
|
|
@@ -2287,6 +2326,57 @@ function analyzeFsOperationImpl(command) {
|
|
|
2287
2326
|
return null;
|
|
2288
2327
|
}
|
|
2289
2328
|
}
|
|
2329
|
+
function stricter(a, b) {
|
|
2330
|
+
if (!a) return b;
|
|
2331
|
+
if (!b) return a;
|
|
2332
|
+
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
2333
|
+
}
|
|
2334
|
+
function matchSensitivePath2(p) {
|
|
2335
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
2336
|
+
if (sp.match(p))
|
|
2337
|
+
return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
|
|
2338
|
+
}
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
|
|
2342
|
+
var positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
|
|
2343
|
+
function wrappedReadPaths(words, name) {
|
|
2344
|
+
if (name === "find") {
|
|
2345
|
+
const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
|
|
2346
|
+
if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
|
|
2347
|
+
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
2348
|
+
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
2349
|
+
}
|
|
2350
|
+
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
2351
|
+
const h = unwrapCommandHead(words);
|
|
2352
|
+
return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
|
|
2353
|
+
}
|
|
2354
|
+
function literalShellPayload(words, name) {
|
|
2355
|
+
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
2356
|
+
const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
2357
|
+
if (head === "eval") {
|
|
2358
|
+
const rest = words.slice(h + 1);
|
|
2359
|
+
if (rest.length === 0 || rest.some((w) => w === null)) return null;
|
|
2360
|
+
return rest.join(" ");
|
|
2361
|
+
}
|
|
2362
|
+
if (SHELL_INTERPRETERS.has(head)) {
|
|
2363
|
+
const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
|
|
2364
|
+
if (c < 0) return null;
|
|
2365
|
+
return words[c + 1] ?? null;
|
|
2366
|
+
}
|
|
2367
|
+
return null;
|
|
2368
|
+
}
|
|
2369
|
+
function jailedRedirectRead(stmt) {
|
|
2370
|
+
const redirs = stmt.Redirs || [];
|
|
2371
|
+
for (const r of redirs) {
|
|
2372
|
+
if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
|
|
2373
|
+
const p = resolveWordLiteral(r.Word);
|
|
2374
|
+
if (p === null || p === "") continue;
|
|
2375
|
+
const hit = matchSensitivePath2(p);
|
|
2376
|
+
if (hit) return hit;
|
|
2377
|
+
}
|
|
2378
|
+
return null;
|
|
2379
|
+
}
|
|
2290
2380
|
function analyzeShellCommand(command) {
|
|
2291
2381
|
const actions = [];
|
|
2292
2382
|
const paths = [];
|
|
@@ -2423,21 +2513,7 @@ function evaluateEgress(dests, policy) {
|
|
|
2423
2513
|
}
|
|
2424
2514
|
|
|
2425
2515
|
// src/policy/pipe-chain.ts
|
|
2426
|
-
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
2427
|
-
"cat",
|
|
2428
|
-
"head",
|
|
2429
|
-
"tail",
|
|
2430
|
-
"grep",
|
|
2431
|
-
"awk",
|
|
2432
|
-
"sed",
|
|
2433
|
-
"cut",
|
|
2434
|
-
"sort",
|
|
2435
|
-
"tee",
|
|
2436
|
-
"less",
|
|
2437
|
-
"more",
|
|
2438
|
-
"strings",
|
|
2439
|
-
"xxd"
|
|
2440
|
-
]);
|
|
2516
|
+
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
2441
2517
|
var SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
2442
2518
|
"curl",
|
|
2443
2519
|
"wget",
|
|
@@ -2468,16 +2544,25 @@ var OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
|
2468
2544
|
"node"
|
|
2469
2545
|
]);
|
|
2470
2546
|
var SENSITIVE_PATTERNS = [
|
|
2471
|
-
/
|
|
2472
|
-
|
|
2547
|
+
// Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
|
|
2548
|
+
/(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
2549
|
+
// .env chain; fixtures exempt unless .local
|
|
2473
2550
|
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
2474
2551
|
// SSH private keys
|
|
2475
2552
|
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
2476
2553
|
// certificate files
|
|
2477
|
-
/
|
|
2478
|
-
//
|
|
2479
|
-
/
|
|
2480
|
-
//
|
|
2554
|
+
// The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
|
|
2555
|
+
// the directory counts wherever it appears, while the directory ITSELF counts
|
|
2556
|
+
// only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
|
|
2557
|
+
// `config/.ssh` is more likely a search pattern than a read. These are
|
|
2558
|
+
// extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
|
|
2559
|
+
// contract as the shell tier, so the same boundary is the right one.
|
|
2560
|
+
// Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
|
|
2561
|
+
// identical pipeline naming a file inside that directory.
|
|
2562
|
+
/(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
|
|
2563
|
+
// ~/.ssh/ and ~/.ssh
|
|
2564
|
+
/(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
|
|
2565
|
+
// AWS creds + dir
|
|
2481
2566
|
/(?:^|\/)\.netrc$/i,
|
|
2482
2567
|
// netrc (stores HTTP credentials)
|
|
2483
2568
|
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
@@ -2511,8 +2596,8 @@ function splitOnPipe(cmd) {
|
|
|
2511
2596
|
if (current.trim()) segments2.push(current.trim());
|
|
2512
2597
|
return segments2.filter(Boolean);
|
|
2513
2598
|
}
|
|
2514
|
-
function positionalTokens(
|
|
2515
|
-
return
|
|
2599
|
+
function positionalTokens(tokens) {
|
|
2600
|
+
return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
2516
2601
|
}
|
|
2517
2602
|
function analyzePipeChain(command) {
|
|
2518
2603
|
const segments2 = splitOnPipe(command);
|
|
@@ -2535,8 +2620,10 @@ function analyzePipeChain(command) {
|
|
|
2535
2620
|
for (const segment of segments2) {
|
|
2536
2621
|
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
2537
2622
|
if (tokens.length === 0) continue;
|
|
2538
|
-
const
|
|
2539
|
-
const
|
|
2623
|
+
const h = unwrapCommandHead(tokens);
|
|
2624
|
+
const head = h < tokens.length ? h : 0;
|
|
2625
|
+
const binary = tokens[head].toLowerCase();
|
|
2626
|
+
const args = positionalTokens(tokens.slice(head));
|
|
2540
2627
|
if (SOURCE_COMMANDS.has(binary)) {
|
|
2541
2628
|
sourceFiles.push(...args);
|
|
2542
2629
|
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
@@ -3979,7 +4066,7 @@ var project_jail_default = {
|
|
|
3979
4066
|
{
|
|
3980
4067
|
field: "command",
|
|
3981
4068
|
op: "matches",
|
|
3982
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
4069
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3983
4070
|
flags: "i"
|
|
3984
4071
|
}
|
|
3985
4072
|
],
|
|
@@ -3993,7 +4080,7 @@ var project_jail_default = {
|
|
|
3993
4080
|
{
|
|
3994
4081
|
field: "command",
|
|
3995
4082
|
op: "matches",
|
|
3996
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
4083
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3997
4084
|
flags: "i"
|
|
3998
4085
|
}
|
|
3999
4086
|
],
|
|
@@ -4007,7 +4094,7 @@ var project_jail_default = {
|
|
|
4007
4094
|
{
|
|
4008
4095
|
field: "command",
|
|
4009
4096
|
op: "matches",
|
|
4010
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
4097
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
4011
4098
|
flags: "i"
|
|
4012
4099
|
}
|
|
4013
4100
|
],
|
|
@@ -4021,7 +4108,7 @@ var project_jail_default = {
|
|
|
4021
4108
|
{
|
|
4022
4109
|
field: "command",
|
|
4023
4110
|
op: "matches",
|
|
4024
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
4111
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
4025
4112
|
flags: "i"
|
|
4026
4113
|
}
|
|
4027
4114
|
],
|
|
@@ -4035,7 +4122,7 @@ var project_jail_default = {
|
|
|
4035
4122
|
{
|
|
4036
4123
|
field: "file_path",
|
|
4037
4124
|
op: "matches",
|
|
4038
|
-
value: "(
|
|
4125
|
+
value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
|
|
4039
4126
|
flags: "i"
|
|
4040
4127
|
}
|
|
4041
4128
|
],
|
|
@@ -4049,7 +4136,7 @@ var project_jail_default = {
|
|
|
4049
4136
|
{
|
|
4050
4137
|
field: "file_path",
|
|
4051
4138
|
op: "matches",
|
|
4052
|
-
value: "(
|
|
4139
|
+
value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
|
|
4053
4140
|
flags: "i"
|
|
4054
4141
|
}
|
|
4055
4142
|
],
|
|
@@ -4063,7 +4150,7 @@ var project_jail_default = {
|
|
|
4063
4150
|
{
|
|
4064
4151
|
field: "file_path",
|
|
4065
4152
|
op: "matches",
|
|
4066
|
-
value: "(^|[\\/\\\\])\\.env(
|
|
4153
|
+
value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
|
|
4067
4154
|
flags: "i"
|
|
4068
4155
|
}
|
|
4069
4156
|
],
|
|
@@ -4506,7 +4593,7 @@ function summarizeBlast(result, opts = {}) {
|
|
|
4506
4593
|
// src/scan/destructive-regex.ts
|
|
4507
4594
|
var DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
|
|
4508
4595
|
var PRIVILEGE_ESCALATION_RE = /\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
4509
|
-
var SENSITIVE_PATH_RE =
|
|
4596
|
+
var SENSITIVE_PATH_RE = /[\\/]\.aws(?:[\\/]|$)|^\.aws[\\/]|[\\/]\.ssh(?:[\\/]|$)|^\.ssh[\\/]|(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
|
|
4510
4597
|
var FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
4511
4598
|
"read",
|
|
4512
4599
|
"read_file",
|
|
@@ -4766,8 +4853,8 @@ function matchCanaryArgs(args, values) {
|
|
|
4766
4853
|
|
|
4767
4854
|
// src/scan/canonical.ts
|
|
4768
4855
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
4769
|
-
var CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
4770
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
4856
|
+
var CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
|
|
4857
|
+
var CANONICAL_EXTRACTOR_HASH = "4622a2f41696af04";
|
|
4771
4858
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
4772
4859
|
function extractCanonicalFindings(call, ctx) {
|
|
4773
4860
|
const out = [];
|
|
@@ -5143,6 +5230,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5143
5230
|
CANARY_MIN_LENGTH,
|
|
5144
5231
|
CANONICAL_EXTRACTOR_HASH,
|
|
5145
5232
|
CANONICAL_EXTRACTOR_VERSION,
|
|
5233
|
+
COMMAND_WRAPPERS,
|
|
5146
5234
|
COST_PER_LOOP_ITER_USD,
|
|
5147
5235
|
DEFAULT_EGRESS_ALLOWLIST,
|
|
5148
5236
|
DESTINATION_ARGS,
|
|
@@ -5156,6 +5244,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5156
5244
|
LONG_OUTPUT_THRESHOLD_BYTES,
|
|
5157
5245
|
LOOP_MAX_RECORDS,
|
|
5158
5246
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
5247
|
+
NET_BINARIES,
|
|
5159
5248
|
PRIVILEGE_ESCALATION_RE,
|
|
5160
5249
|
REALTIME_PII_PATTERNS,
|
|
5161
5250
|
SCAN_SIGNAL_WEIGHTS,
|
|
@@ -5226,6 +5315,7 @@ var ENGINE_VERSION = "1.4.0";
|
|
|
5226
5315
|
toScanFinding,
|
|
5227
5316
|
toolMatchesRule,
|
|
5228
5317
|
truncateBlastPath,
|
|
5318
|
+
unwrapCommandHead,
|
|
5229
5319
|
validateOverrides,
|
|
5230
5320
|
validateRegex,
|
|
5231
5321
|
validateShieldDefinition
|
package/dist/index.mjs
CHANGED
|
@@ -717,13 +717,32 @@ var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
|
717
717
|
})
|
|
718
718
|
);
|
|
719
719
|
var SENSITIVE_PATH_PATTERNS = [
|
|
720
|
-
/[/\\]\.ssh[/\\]/i,
|
|
721
|
-
/[/\\]\.aws[/\\]/i,
|
|
720
|
+
/[/\\]\.ssh([/\\]|$)/i,
|
|
721
|
+
/[/\\]\.aws([/\\]|$)/i,
|
|
722
722
|
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
723
723
|
/[/\\]\.azure[/\\]/i,
|
|
724
724
|
/[/\\]\.kube[/\\]config$/i,
|
|
725
|
-
|
|
726
|
-
// .
|
|
725
|
+
// ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
|
|
726
|
+
// (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
|
|
727
|
+
// structural suffix chain rather than a hand-written list, `example|sample|
|
|
728
|
+
// template` exempt because a fixture stays a fixture whatever follows, and
|
|
729
|
+
// `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
|
|
730
|
+
// committed template, `.env.test.local` is gitignored and holds real values.
|
|
731
|
+
//
|
|
732
|
+
// It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
|
|
733
|
+
// blocked while `cat .env.example` allowed: the same file, opposite verdicts,
|
|
734
|
+
// decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
|
|
735
|
+
// which is the contract that now holds these copies in step, and stage 5 of
|
|
736
|
+
// doc/credential-jail-architecture.md, which replaces them with one generated
|
|
737
|
+
// source.
|
|
738
|
+
// ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
|
|
739
|
+
// fixture whatever follows it -- `.env.example.md` is documentation -- but
|
|
740
|
+
// `.env.example.local` is gitignored by the `.env*.local` convention and holds
|
|
741
|
+
// real values, exactly the reasoning that anchors `(?!\.test$)` rather than
|
|
742
|
+
// using `\b`. Without this branch the fixture exemption also bought a two-step
|
|
743
|
+
// bypass: `cp .env .env.sample`, then read the copy.
|
|
744
|
+
/[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
745
|
+
// .env + any suffix chain; fixtures exempt unless .local
|
|
727
746
|
/[/\\]\.git-credentials$/i,
|
|
728
747
|
/[/\\]\.npmrc$/i,
|
|
729
748
|
/[/\\]\.docker[/\\]config\.json$/i,
|
|
@@ -1282,6 +1301,10 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1282
1301
|
"od",
|
|
1283
1302
|
"xxd",
|
|
1284
1303
|
"hexdump",
|
|
1304
|
+
// Emits the file's bytes, re-encoded, so it is a read by the set's own test
|
|
1305
|
+
// ("does it emit file contents"). Absent until 2026-09-10, which is why
|
|
1306
|
+
// `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
|
|
1307
|
+
"base64",
|
|
1285
1308
|
"strings",
|
|
1286
1309
|
"sort",
|
|
1287
1310
|
"uniq",
|
|
@@ -1290,7 +1313,11 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1290
1313
|
"dd"
|
|
1291
1314
|
]);
|
|
1292
1315
|
var FS_OP_PRESCREEN_RE = new RegExp(
|
|
1293
|
-
|
|
1316
|
+
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
1317
|
+
// reader right after `"` / `'`, and without these two characters the
|
|
1318
|
+
// prescreen rejected every string-wrapped read before the parser ran.
|
|
1319
|
+
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
1320
|
+
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
1294
1321
|
);
|
|
1295
1322
|
var HOME_CACHE_ALLOWLIST = [
|
|
1296
1323
|
".cache",
|
|
@@ -1311,12 +1338,12 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1311
1338
|
{
|
|
1312
1339
|
rule: "shield:project-jail:block-read-ssh",
|
|
1313
1340
|
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1314
|
-
match: (p) => /(
|
|
1341
|
+
match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
|
|
1315
1342
|
},
|
|
1316
1343
|
{
|
|
1317
1344
|
rule: "shield:project-jail:block-read-aws",
|
|
1318
1345
|
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
1319
|
-
match: (p) => /(
|
|
1346
|
+
match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
|
|
1320
1347
|
},
|
|
1321
1348
|
{
|
|
1322
1349
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
@@ -1360,7 +1387,9 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1360
1387
|
// symmetry — silently exempts every `.env.test.*` file.
|
|
1361
1388
|
//
|
|
1362
1389
|
// shields.test.ts:983-995 is the canonical contract; keep both in step.
|
|
1363
|
-
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]
|
|
1390
|
+
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
|
|
1391
|
+
p
|
|
1392
|
+
)
|
|
1364
1393
|
},
|
|
1365
1394
|
{
|
|
1366
1395
|
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
@@ -1571,15 +1600,14 @@ function listOps() {
|
|
|
1571
1600
|
return _listOps;
|
|
1572
1601
|
}
|
|
1573
1602
|
var WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
|
|
1603
|
+
var FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
1574
1604
|
var INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
1575
1605
|
function unwrapCommandHead(words) {
|
|
1576
1606
|
let i = 0;
|
|
1577
1607
|
while (i < words.length) {
|
|
1578
1608
|
const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
|
|
1579
1609
|
if (head === "find") {
|
|
1580
|
-
const x = words.findIndex(
|
|
1581
|
-
(w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
|
|
1582
|
-
);
|
|
1610
|
+
const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1583
1611
|
if (x < 0) break;
|
|
1584
1612
|
i = x + 1;
|
|
1585
1613
|
continue;
|
|
@@ -1601,7 +1629,11 @@ function unwrapCommandHead(words) {
|
|
|
1601
1629
|
if (t.startsWith("-")) {
|
|
1602
1630
|
i++;
|
|
1603
1631
|
const nxt = words[i];
|
|
1604
|
-
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase())
|
|
1632
|
+
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
|
|
1633
|
+
// `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
|
|
1634
|
+
// swallowed and the jail needed a looser fallback whose cost was a
|
|
1635
|
+
// false positive on `sudo echo cat X`.
|
|
1636
|
+
!FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
|
|
1605
1637
|
i++;
|
|
1606
1638
|
continue;
|
|
1607
1639
|
}
|
|
@@ -1722,36 +1754,29 @@ function isProtectedHomePath(rawPath) {
|
|
|
1722
1754
|
}
|
|
1723
1755
|
function extractLiteralArgs(callExpr) {
|
|
1724
1756
|
const args = callExpr.Args || [];
|
|
1725
|
-
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
1726
|
-
const
|
|
1727
|
-
|
|
1728
|
-
let s = "";
|
|
1729
|
-
for (const p of parts) {
|
|
1730
|
-
const t = syntax.NodeType(p);
|
|
1731
|
-
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
1732
|
-
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
1733
|
-
else if (t === "DblQuoted") {
|
|
1734
|
-
const inner = p.Parts || [];
|
|
1735
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
1736
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
1737
|
-
} else {
|
|
1738
|
-
return null;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
return s;
|
|
1742
|
-
};
|
|
1743
|
-
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
1757
|
+
if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
|
|
1758
|
+
const words = args.map((a) => resolveWordLiteral(a));
|
|
1759
|
+
const name = (words[0] ?? "").toLowerCase();
|
|
1744
1760
|
const flags = [];
|
|
1745
1761
|
const paths = [];
|
|
1746
|
-
for (let i = 1; i <
|
|
1747
|
-
const v =
|
|
1762
|
+
for (let i = 1; i < words.length; i++) {
|
|
1763
|
+
const v = words[i];
|
|
1748
1764
|
if (v === null) continue;
|
|
1749
1765
|
if (v.startsWith("-")) flags.push(v);
|
|
1750
1766
|
else paths.push(v);
|
|
1751
1767
|
}
|
|
1752
|
-
return { name, flags, paths };
|
|
1768
|
+
return { name, flags, paths, words };
|
|
1753
1769
|
}
|
|
1754
|
-
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
1770
|
+
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
1771
|
+
"curl",
|
|
1772
|
+
"wget",
|
|
1773
|
+
"scp",
|
|
1774
|
+
"ssh",
|
|
1775
|
+
"nc",
|
|
1776
|
+
"ncat",
|
|
1777
|
+
"netcat",
|
|
1778
|
+
"rsync"
|
|
1779
|
+
]);
|
|
1755
1780
|
var VALUE_FLAGS = {
|
|
1756
1781
|
curl: /* @__PURE__ */ new Set([
|
|
1757
1782
|
"-d",
|
|
@@ -2036,6 +2061,9 @@ function deriveRedirOp(sample) {
|
|
|
2036
2061
|
}
|
|
2037
2062
|
}
|
|
2038
2063
|
var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
2064
|
+
var REDIR_FILE_IN_OPS = new Set(
|
|
2065
|
+
[deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
|
|
2066
|
+
);
|
|
2039
2067
|
var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
2040
2068
|
deriveRedirOp("cat <<X\nX"),
|
|
2041
2069
|
deriveRedirOp("cat <<-X\nX")
|
|
@@ -2100,16 +2128,21 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
2100
2128
|
}
|
|
2101
2129
|
return sawRm && ok;
|
|
2102
2130
|
}
|
|
2103
|
-
function analyzeFsOperationImpl(command) {
|
|
2131
|
+
function analyzeFsOperationImpl(command, depth = 0) {
|
|
2104
2132
|
const f = parseShared(command);
|
|
2105
2133
|
if (f === PARSE_FAIL) return null;
|
|
2106
2134
|
let result = null;
|
|
2107
2135
|
try {
|
|
2108
2136
|
syntax.Walk(f, (node) => {
|
|
2109
|
-
if (!node || result) return false;
|
|
2137
|
+
if (!node || result?.verdict === "block") return false;
|
|
2110
2138
|
const n = node;
|
|
2111
|
-
|
|
2112
|
-
|
|
2139
|
+
const nodeType = syntax.NodeType(n);
|
|
2140
|
+
if (nodeType === "Stmt") {
|
|
2141
|
+
result = stricter(result, jailedRedirectRead(n));
|
|
2142
|
+
return result?.verdict !== "block";
|
|
2143
|
+
}
|
|
2144
|
+
if (nodeType !== "CallExpr") return true;
|
|
2145
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2113
2146
|
if (!name) return true;
|
|
2114
2147
|
if (name === "rm") {
|
|
2115
2148
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -2138,19 +2171,22 @@ function analyzeFsOperationImpl(command) {
|
|
|
2138
2171
|
}
|
|
2139
2172
|
}
|
|
2140
2173
|
}
|
|
2141
|
-
if (
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
reason: sp.reason,
|
|
2149
|
-
path: p
|
|
2150
|
-
};
|
|
2151
|
-
return false;
|
|
2152
|
-
}
|
|
2174
|
+
if (depth < 1) {
|
|
2175
|
+
const payload = literalShellPayload(words, name);
|
|
2176
|
+
if (payload !== null) {
|
|
2177
|
+
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
2178
|
+
if (inner) {
|
|
2179
|
+
result = inner;
|
|
2180
|
+
return false;
|
|
2153
2181
|
}
|
|
2182
|
+
return true;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
|
|
2186
|
+
if (readPaths) {
|
|
2187
|
+
for (const p of readPaths) {
|
|
2188
|
+
result = stricter(result, matchSensitivePath2(p));
|
|
2189
|
+
if (result?.verdict === "block") return false;
|
|
2154
2190
|
}
|
|
2155
2191
|
}
|
|
2156
2192
|
return true;
|
|
@@ -2160,6 +2196,57 @@ function analyzeFsOperationImpl(command) {
|
|
|
2160
2196
|
return null;
|
|
2161
2197
|
}
|
|
2162
2198
|
}
|
|
2199
|
+
function stricter(a, b) {
|
|
2200
|
+
if (!a) return b;
|
|
2201
|
+
if (!b) return a;
|
|
2202
|
+
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
2203
|
+
}
|
|
2204
|
+
function matchSensitivePath2(p) {
|
|
2205
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
2206
|
+
if (sp.match(p))
|
|
2207
|
+
return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
|
|
2208
|
+
}
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
|
|
2212
|
+
var positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
|
|
2213
|
+
function wrappedReadPaths(words, name) {
|
|
2214
|
+
if (name === "find") {
|
|
2215
|
+
const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
|
|
2216
|
+
if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
|
|
2217
|
+
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
2218
|
+
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
2219
|
+
}
|
|
2220
|
+
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
2221
|
+
const h = unwrapCommandHead(words);
|
|
2222
|
+
return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
|
|
2223
|
+
}
|
|
2224
|
+
function literalShellPayload(words, name) {
|
|
2225
|
+
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
2226
|
+
const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
2227
|
+
if (head === "eval") {
|
|
2228
|
+
const rest = words.slice(h + 1);
|
|
2229
|
+
if (rest.length === 0 || rest.some((w) => w === null)) return null;
|
|
2230
|
+
return rest.join(" ");
|
|
2231
|
+
}
|
|
2232
|
+
if (SHELL_INTERPRETERS.has(head)) {
|
|
2233
|
+
const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
|
|
2234
|
+
if (c < 0) return null;
|
|
2235
|
+
return words[c + 1] ?? null;
|
|
2236
|
+
}
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
function jailedRedirectRead(stmt) {
|
|
2240
|
+
const redirs = stmt.Redirs || [];
|
|
2241
|
+
for (const r of redirs) {
|
|
2242
|
+
if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
|
|
2243
|
+
const p = resolveWordLiteral(r.Word);
|
|
2244
|
+
if (p === null || p === "") continue;
|
|
2245
|
+
const hit = matchSensitivePath2(p);
|
|
2246
|
+
if (hit) return hit;
|
|
2247
|
+
}
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2163
2250
|
function analyzeShellCommand(command) {
|
|
2164
2251
|
const actions = [];
|
|
2165
2252
|
const paths = [];
|
|
@@ -2296,21 +2383,7 @@ function evaluateEgress(dests, policy) {
|
|
|
2296
2383
|
}
|
|
2297
2384
|
|
|
2298
2385
|
// src/policy/pipe-chain.ts
|
|
2299
|
-
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
2300
|
-
"cat",
|
|
2301
|
-
"head",
|
|
2302
|
-
"tail",
|
|
2303
|
-
"grep",
|
|
2304
|
-
"awk",
|
|
2305
|
-
"sed",
|
|
2306
|
-
"cut",
|
|
2307
|
-
"sort",
|
|
2308
|
-
"tee",
|
|
2309
|
-
"less",
|
|
2310
|
-
"more",
|
|
2311
|
-
"strings",
|
|
2312
|
-
"xxd"
|
|
2313
|
-
]);
|
|
2386
|
+
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
2314
2387
|
var SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
2315
2388
|
"curl",
|
|
2316
2389
|
"wget",
|
|
@@ -2341,16 +2414,25 @@ var OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
|
2341
2414
|
"node"
|
|
2342
2415
|
]);
|
|
2343
2416
|
var SENSITIVE_PATTERNS = [
|
|
2344
|
-
/
|
|
2345
|
-
|
|
2417
|
+
// Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
|
|
2418
|
+
/(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
2419
|
+
// .env chain; fixtures exempt unless .local
|
|
2346
2420
|
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
2347
2421
|
// SSH private keys
|
|
2348
2422
|
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
2349
2423
|
// certificate files
|
|
2350
|
-
/
|
|
2351
|
-
//
|
|
2352
|
-
/
|
|
2353
|
-
//
|
|
2424
|
+
// The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
|
|
2425
|
+
// the directory counts wherever it appears, while the directory ITSELF counts
|
|
2426
|
+
// only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
|
|
2427
|
+
// `config/.ssh` is more likely a search pattern than a read. These are
|
|
2428
|
+
// extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
|
|
2429
|
+
// contract as the shell tier, so the same boundary is the right one.
|
|
2430
|
+
// Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
|
|
2431
|
+
// identical pipeline naming a file inside that directory.
|
|
2432
|
+
/(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
|
|
2433
|
+
// ~/.ssh/ and ~/.ssh
|
|
2434
|
+
/(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
|
|
2435
|
+
// AWS creds + dir
|
|
2354
2436
|
/(?:^|\/)\.netrc$/i,
|
|
2355
2437
|
// netrc (stores HTTP credentials)
|
|
2356
2438
|
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
@@ -2384,8 +2466,8 @@ function splitOnPipe(cmd) {
|
|
|
2384
2466
|
if (current.trim()) segments2.push(current.trim());
|
|
2385
2467
|
return segments2.filter(Boolean);
|
|
2386
2468
|
}
|
|
2387
|
-
function positionalTokens(
|
|
2388
|
-
return
|
|
2469
|
+
function positionalTokens(tokens) {
|
|
2470
|
+
return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
2389
2471
|
}
|
|
2390
2472
|
function analyzePipeChain(command) {
|
|
2391
2473
|
const segments2 = splitOnPipe(command);
|
|
@@ -2408,8 +2490,10 @@ function analyzePipeChain(command) {
|
|
|
2408
2490
|
for (const segment of segments2) {
|
|
2409
2491
|
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
2410
2492
|
if (tokens.length === 0) continue;
|
|
2411
|
-
const
|
|
2412
|
-
const
|
|
2493
|
+
const h = unwrapCommandHead(tokens);
|
|
2494
|
+
const head = h < tokens.length ? h : 0;
|
|
2495
|
+
const binary = tokens[head].toLowerCase();
|
|
2496
|
+
const args = positionalTokens(tokens.slice(head));
|
|
2413
2497
|
if (SOURCE_COMMANDS.has(binary)) {
|
|
2414
2498
|
sourceFiles.push(...args);
|
|
2415
2499
|
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
@@ -3852,7 +3936,7 @@ var project_jail_default = {
|
|
|
3852
3936
|
{
|
|
3853
3937
|
field: "command",
|
|
3854
3938
|
op: "matches",
|
|
3855
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3939
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
3856
3940
|
flags: "i"
|
|
3857
3941
|
}
|
|
3858
3942
|
],
|
|
@@ -3866,7 +3950,7 @@ var project_jail_default = {
|
|
|
3866
3950
|
{
|
|
3867
3951
|
field: "command",
|
|
3868
3952
|
op: "matches",
|
|
3869
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3953
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
3870
3954
|
flags: "i"
|
|
3871
3955
|
}
|
|
3872
3956
|
],
|
|
@@ -3880,7 +3964,7 @@ var project_jail_default = {
|
|
|
3880
3964
|
{
|
|
3881
3965
|
field: "command",
|
|
3882
3966
|
op: "matches",
|
|
3883
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
3967
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
3884
3968
|
flags: "i"
|
|
3885
3969
|
}
|
|
3886
3970
|
],
|
|
@@ -3894,7 +3978,7 @@ var project_jail_default = {
|
|
|
3894
3978
|
{
|
|
3895
3979
|
field: "command",
|
|
3896
3980
|
op: "matches",
|
|
3897
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3981
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
3898
3982
|
flags: "i"
|
|
3899
3983
|
}
|
|
3900
3984
|
],
|
|
@@ -3908,7 +3992,7 @@ var project_jail_default = {
|
|
|
3908
3992
|
{
|
|
3909
3993
|
field: "file_path",
|
|
3910
3994
|
op: "matches",
|
|
3911
|
-
value: "(
|
|
3995
|
+
value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
|
|
3912
3996
|
flags: "i"
|
|
3913
3997
|
}
|
|
3914
3998
|
],
|
|
@@ -3922,7 +4006,7 @@ var project_jail_default = {
|
|
|
3922
4006
|
{
|
|
3923
4007
|
field: "file_path",
|
|
3924
4008
|
op: "matches",
|
|
3925
|
-
value: "(
|
|
4009
|
+
value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
|
|
3926
4010
|
flags: "i"
|
|
3927
4011
|
}
|
|
3928
4012
|
],
|
|
@@ -3936,7 +4020,7 @@ var project_jail_default = {
|
|
|
3936
4020
|
{
|
|
3937
4021
|
field: "file_path",
|
|
3938
4022
|
op: "matches",
|
|
3939
|
-
value: "(^|[\\/\\\\])\\.env(
|
|
4023
|
+
value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
|
|
3940
4024
|
flags: "i"
|
|
3941
4025
|
}
|
|
3942
4026
|
],
|
|
@@ -4379,7 +4463,7 @@ function summarizeBlast(result, opts = {}) {
|
|
|
4379
4463
|
// src/scan/destructive-regex.ts
|
|
4380
4464
|
var DESTRUCTIVE_OP_RE = /\brm\s+-[rRf]+\b|\bDROP\s+(TABLE|DATABASE|COLLECTION|SCHEMA)\b|\bTRUNCATE\s+TABLE\b|\bgit\s+push\s+(--force|-f)\b|\bFLUSHALL\b|\bFLUSHDB\b|\bkubectl\s+delete\b|\bhelm\s+uninstall\b/i;
|
|
4381
4465
|
var PRIVILEGE_ESCALATION_RE = /\bchmod\s+(0?777|\+x)\b|\bchown\s+root\b/i;
|
|
4382
|
-
var SENSITIVE_PATH_RE =
|
|
4466
|
+
var SENSITIVE_PATH_RE = /[\\/]\.aws(?:[\\/]|$)|^\.aws[\\/]|[\\/]\.ssh(?:[\\/]|$)|^\.ssh[\\/]|(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)|\.config\/gcloud\/credentials\.db\b|\.docker\/config\.json\b|\.netrc\b|\.npmrc\b|\.node9\/credentials\.json\b/i;
|
|
4383
4467
|
var FILE_TOOLS = /* @__PURE__ */ new Set([
|
|
4384
4468
|
"read",
|
|
4385
4469
|
"read_file",
|
|
@@ -4639,8 +4723,8 @@ function matchCanaryArgs(args, values) {
|
|
|
4639
4723
|
|
|
4640
4724
|
// src/scan/canonical.ts
|
|
4641
4725
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
4642
|
-
var CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
4643
|
-
var CANONICAL_EXTRACTOR_HASH = "
|
|
4726
|
+
var CANONICAL_EXTRACTOR_VERSION = "canonical-v13";
|
|
4727
|
+
var CANONICAL_EXTRACTOR_HASH = "4622a2f41696af04";
|
|
4644
4728
|
var DEDUPE_PREVIEW_LEN = 120;
|
|
4645
4729
|
function extractCanonicalFindings(call, ctx) {
|
|
4646
4730
|
const out = [];
|
|
@@ -5015,6 +5099,7 @@ export {
|
|
|
5015
5099
|
CANARY_MIN_LENGTH,
|
|
5016
5100
|
CANONICAL_EXTRACTOR_HASH,
|
|
5017
5101
|
CANONICAL_EXTRACTOR_VERSION,
|
|
5102
|
+
COMMAND_WRAPPERS,
|
|
5018
5103
|
COST_PER_LOOP_ITER_USD,
|
|
5019
5104
|
DEFAULT_EGRESS_ALLOWLIST,
|
|
5020
5105
|
DESTINATION_ARGS,
|
|
@@ -5028,6 +5113,7 @@ export {
|
|
|
5028
5113
|
LONG_OUTPUT_THRESHOLD_BYTES,
|
|
5029
5114
|
LOOP_MAX_RECORDS,
|
|
5030
5115
|
LOOP_THRESHOLD_FOR_WASTE,
|
|
5116
|
+
NET_BINARIES,
|
|
5031
5117
|
PRIVILEGE_ESCALATION_RE,
|
|
5032
5118
|
REALTIME_PII_PATTERNS,
|
|
5033
5119
|
SCAN_SIGNAL_WEIGHTS,
|
|
@@ -5098,6 +5184,7 @@ export {
|
|
|
5098
5184
|
toScanFinding,
|
|
5099
5185
|
toolMatchesRule,
|
|
5100
5186
|
truncateBlastPath,
|
|
5187
|
+
unwrapCommandHead,
|
|
5101
5188
|
validateOverrides,
|
|
5102
5189
|
validateRegex,
|
|
5103
5190
|
validateShieldDefinition
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/policy-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
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",
|