@node9/policy-engine 2.8.4 → 2.9.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 CHANGED
@@ -136,6 +136,15 @@ interface DlpPattern {
136
136
  * Only set on broad patterns where the regex alone can't distinguish real secrets.
137
137
  */
138
138
  minEntropy?: number;
139
+ /**
140
+ * Structural validator run on the matched token (checksum, version byte).
141
+ * When present it DECIDES: a passing validator accepts the match outright,
142
+ * skipping the stopword and entropy heuristics (a checksum-valid token that
143
+ * happens to contain a stopword substring is a real secret, roughly 1 in
144
+ * 3,000 real WIF keys); a failing one rejects it. Must never throw; a throw
145
+ * is treated as "not suppressed" so a validator bug cannot hide a match.
146
+ */
147
+ validate?: (raw: string) => boolean;
139
148
  }
140
149
  declare const DLP_PATTERNS: DlpPattern[];
141
150
  /**
@@ -168,11 +177,6 @@ declare const DLP_SCAN_LIMITS: {
168
177
  /** Max nesting depth walked; anything deeper is NOT scanned. */
169
178
  readonly maxDepth: 5;
170
179
  };
171
- /**
172
- * Recursively scans an args value for known secret patterns.
173
- * Handles nested objects, arrays, and JSON-encoded strings.
174
- * Returns the first match found, or null if clean.
175
- */
176
180
  declare function scanArgs(args: unknown, depth?: number, fieldPath?: string): DlpMatch | null;
177
181
  /** Scan a plain text string (e.g. Claude response prose) for DLP patterns. */
178
182
  declare function scanText(text: string): DlpMatch | null;
@@ -264,6 +268,25 @@ declare function parseDestHost(token: string): string | null;
264
268
  * and parses the host. Deduplicated by host. Pure — no I/O, no DNS.
265
269
  */
266
270
  declare function extractShellDestinations(command: string): ShellDestination[];
271
+ /** A raw destination-position token from a network binary, BEFORE parseDestHost.
272
+ * The SSRF floor needs these because parseDestHost requires a dot and therefore
273
+ * drops `2852039166`, which denotes a cloud metadata address (measured: that
274
+ * command is allowed today at the strictest egress setting). */
275
+ interface ShellDestToken {
276
+ token: string;
277
+ binary: string;
278
+ }
279
+ /**
280
+ * Destination-position tokens for every network binary in a command, unparsed.
281
+ *
282
+ * Same walk and same flag-skipping as extractShellDestinations, so the two agree
283
+ * on which arguments are destinations. It exists as a sibling rather than a
284
+ * widening of parseDestHost because the dot requirement there is a load-bearing
285
+ * false-positive guard: turning every numeric token into a HOST would change
286
+ * egress verdicts for every user. Asking whether a token DENOTES A PROTECTED
287
+ * ADDRESS has no false-positive surface, because that comparison is exact.
288
+ */
289
+ declare function extractShellDestTokens(command: string): ShellDestToken[];
267
290
  declare function analyzeFsOperation(command: string): FsOpVerdict | null;
268
291
  interface ShellCommandAnalysis {
269
292
  /** First word of every CallExpr — the command names invoked. */
@@ -291,6 +314,14 @@ interface EgressPolicy {
291
314
  deny: string[];
292
315
  /** Auto-allow localhost / RFC1918 / *.local. Default true. */
293
316
  allowPrivate: boolean;
317
+ /** SSRF floor: exempts OVERRIDABLE tiers only (carrier-grade NAT, and
318
+ * loopback/RFC1918 when ssrfStrict is on). A tier-1 entry is ignored here
319
+ * and rejected at config load with a reason. */
320
+ ssrfAllow?: readonly string[];
321
+ /** SSRF floor tier 3: also block loopback and RFC1918. Off by default,
322
+ * because a developer talks to those constantly (72 of 308 destinations on
323
+ * measured real history). */
324
+ ssrfStrict?: boolean;
294
325
  }
295
326
  interface EgressVerdict {
296
327
  verdict: 'block' | 'review';
@@ -990,7 +1021,7 @@ declare const SENSITIVE_PATH_RE: RegExp;
990
1021
  */
991
1022
  declare const FILE_TOOLS: Set<string>;
992
1023
 
993
- type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
1024
+ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card' | 'IBAN';
994
1025
  /**
995
1026
  * Detect PII patterns in a string. Returns a deduplicated list — one entry
996
1027
  * per distinct pattern type, never multiple "Email" findings from one input.
@@ -998,13 +1029,40 @@ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
998
1029
  declare function detectPii(text: string): PiiPattern[];
999
1030
  declare const REALTIME_PII_PATTERNS: readonly PiiPattern[];
1000
1031
  /**
1001
- * Realtime adapter for detectPii: walks a tool-args value (stringifying
1002
- * objects/arrays) and returns only the high-signal PII patterns found. Used by
1003
- * the authorize path to gate SSN / Credit Card in tool arguments. Pure.
1032
+ * Realtime adapter for detectPii: walks a tool-args value leaf by leaf and
1033
+ * returns only the high-signal PII patterns found. Used by the authorize path
1034
+ * to gate SSN / Credit Card in tool arguments. Pure.
1035
+ *
1036
+ * Walks leaves rather than scanning JSON.stringify(args). Stringifying turns
1037
+ * a real newline into the two characters backslash + `n`, and `n` is a word
1038
+ * character, so a card or SSN that BEGINS A LINE inside a multi-line value
1039
+ * (a CSV being written, for instance) had no \b in front of it and was
1040
+ * invisible to the realtime gate. Raw leaves keep the real newline.
1004
1041
  */
1005
1042
  declare function detectArgsPii(args: unknown): PiiPattern[];
1006
1043
 
1007
- 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';
1044
+ interface CanaryValue {
1045
+ id: string;
1046
+ value: string;
1047
+ retired?: boolean;
1048
+ }
1049
+ type CanaryView = 'raw' | 'url-decoded' | 'base64-decoded' | 'hex-decoded' | 'separators-stripped';
1050
+ interface CanaryHit {
1051
+ id: string;
1052
+ view: CanaryView;
1053
+ fieldPath?: string;
1054
+ retired: boolean;
1055
+ }
1056
+ /** Values shorter than this are skipped by the matcher AND rejected by the registry (H12):
1057
+ * below 16 the separators-stripped view starts to collide with ordinary text. */
1058
+ declare const CANARY_MIN_LENGTH = 16;
1059
+ /** Exact containment of any registered value in any bounded view of the text. */
1060
+ declare function matchCanary(text: string, values: readonly CanaryValue[]): CanaryHit | null;
1061
+ /** Walks string leaves (depth <= 6, per-field budget), parsing JSON-in-string leaves
1062
+ * so escaped values are seen (B23), like scanArgs. */
1063
+ declare function matchCanaryArgs(args: unknown, values: readonly CanaryValue[]): CanaryHit | null;
1064
+
1065
+ type CanonicalFindingType = 'smart-rule' | 'ast-fs-op' | 'dlp' | 'pii' | 'canary' | 'sensitive-file-read' | 'privilege-escalation' | 'destructive-op' | 'pipe-to-shell' | 'eval-of-remote' | 'loop' | 'long-output-redacted';
1008
1066
  type CanonicalAgent = 'claude' | 'gemini' | 'codex' | 'shell';
1009
1067
  type CanonicalSourceType = 'default' | 'shield' | 'user' | 'engine';
1010
1068
  interface CanonicalFinding {
@@ -1083,6 +1141,13 @@ interface ExtractContext {
1083
1141
  toolInspection: Record<string, string>;
1084
1142
  /** DLP enabled flag from PolicyConfig. */
1085
1143
  dlpEnabled: boolean;
1144
+ /** Decoy credentials registered on this machine (value plus kind and plant
1145
+ * path for the finding text). Optional: absent or empty means no canary
1146
+ * pass, which keeps extractor output machine-independent in CI (H3). */
1147
+ canaryValues?: ReadonlyArray<CanaryValue & {
1148
+ kind?: string;
1149
+ path?: string;
1150
+ }>;
1086
1151
  }
1087
1152
  interface SessionExtractContext {
1088
1153
  sessionId: string;
@@ -1131,7 +1196,7 @@ declare const LONG_OUTPUT_THRESHOLD_BYTES: number;
1131
1196
  * and fails CI when the hash drifts without a version bump — forgetting
1132
1197
  * is loud, not silent.
1133
1198
  */
1134
- declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v9";
1199
+ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
1135
1200
  /**
1136
1201
  * SHA-256 prefix of the detector-source files
1137
1202
  * (canonical.ts + pii.ts + destructive-regex.ts).
@@ -1142,7 +1207,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v9";
1142
1207
  * files changed, this hash must change too, and you must consciously
1143
1208
  * decide whether to bump CANONICAL_EXTRACTOR_VERSION."
1144
1209
  */
1145
- declare const CANONICAL_EXTRACTOR_HASH = "0d6c1ddb9a5af5b7";
1210
+ declare const CANONICAL_EXTRACTOR_HASH = "5c786cc174281e51";
1146
1211
  declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
1147
1212
  declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
1148
1213
  /**
@@ -1166,7 +1231,64 @@ declare function dedupeCanonicalFindings(findings: ReadonlyArray<CanonicalFindin
1166
1231
  declare function toScanFinding(c: CanonicalFinding): ScanFinding | null;
1167
1232
  declare function previewArgs(input: Record<string, unknown>, max: number): string;
1168
1233
 
1234
+ type SsrfTier = 'metadata' | 'link-local' | 'multicast' | 'unspecified' | 'cgnat' | 'private';
1235
+ interface SsrfMatch {
1236
+ tier: SsrfTier;
1237
+ /** false for tier 1: no allowlist entry exempts it. */
1238
+ overridable: boolean;
1239
+ /** 'address' when an IP literal matched, 'hostname' for the metadata name list. */
1240
+ kind: 'address' | 'hostname';
1241
+ /** The canonical address; absent for a hostname match. Never an empty string:
1242
+ * an empty value is dropped in flight by cloud.ts and the audit shipper. */
1243
+ normalized?: string;
1244
+ }
1245
+ /** Longest legal DNS name. The caller does NOT reliably bound this: a URL with a
1246
+ * 261-character host reaches us intact (measured), so we bound our own input. */
1247
+ declare const SSRF_MAX_HOST = 253;
1248
+ /**
1249
+ * Fold any spelling of an IP literal to one canonical address, or null when the
1250
+ * input is not an IP literal (a hostname, or malformed). Never throws.
1251
+ *
1252
+ * IPv4-mapped IPv6 folds to the IPv4 address it denotes (RFC 4291 2.5.5.2), so
1253
+ * `[::ffff:a9fe:a9fe]` and `169.254.169.254` compare equal. Brackets and a zone
1254
+ * id are stripped here: the caller does NOT strip them (measured).
1255
+ */
1256
+ declare function normalizeIpLiteral(host: string): string | null;
1257
+ /**
1258
+ * Classify a destination host. Returns null when it is not a protected address,
1259
+ * which means the ordinary egress policy decides. Never throws.
1260
+ *
1261
+ * Ordering is significant: the exact metadata addresses are checked before the
1262
+ * link-local range that contains them, so the reason names metadata.
1263
+ */
1264
+ declare function classifySsrf(host: string): SsrfMatch | null;
1265
+ interface SsrfVerdict extends SsrfMatch {
1266
+ /** The token as written in the command. */
1267
+ host: string;
1268
+ binary: string;
1269
+ reason: string;
1270
+ }
1271
+ interface SsrfFloorOptions {
1272
+ /** Exempts OVERRIDABLE tiers only. A tier-1 entry here is ignored (and is
1273
+ * rejected at config load with a reason, never silently). */
1274
+ ssrfAllow?: readonly string[];
1275
+ /** Opt-in tier 3: loopback and RFC1918. Off by default: a developer talks to
1276
+ * those constantly (72 of 308 destinations on measured real history). */
1277
+ ssrfStrict?: boolean;
1278
+ }
1279
+ /**
1280
+ * The floor. Returns the first protected destination, or null.
1281
+ *
1282
+ * Runs unconditionally: not gated on egress.enabled, mode, allow or allowPrivate.
1283
+ * The one thing that does switch it off is `node9 pause`, which returns before
1284
+ * every gate; that is stated as a limit rather than special-cased here.
1285
+ */
1286
+ declare function ssrfFloor(tokens: ReadonlyArray<{
1287
+ token: string;
1288
+ binary: string;
1289
+ }>, opts?: SsrfFloorOptions): SsrfVerdict | null;
1290
+
1169
1291
  /** Engine version stamped on audit entries for future drift detection. */
1170
1292
  declare const ENGINE_VERSION = "1.4.0";
1171
1293
 
1172
- 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, 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, 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, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
1294
+ export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfFloor, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
package/dist/index.d.ts CHANGED
@@ -136,6 +136,15 @@ interface DlpPattern {
136
136
  * Only set on broad patterns where the regex alone can't distinguish real secrets.
137
137
  */
138
138
  minEntropy?: number;
139
+ /**
140
+ * Structural validator run on the matched token (checksum, version byte).
141
+ * When present it DECIDES: a passing validator accepts the match outright,
142
+ * skipping the stopword and entropy heuristics (a checksum-valid token that
143
+ * happens to contain a stopword substring is a real secret, roughly 1 in
144
+ * 3,000 real WIF keys); a failing one rejects it. Must never throw; a throw
145
+ * is treated as "not suppressed" so a validator bug cannot hide a match.
146
+ */
147
+ validate?: (raw: string) => boolean;
139
148
  }
140
149
  declare const DLP_PATTERNS: DlpPattern[];
141
150
  /**
@@ -168,11 +177,6 @@ declare const DLP_SCAN_LIMITS: {
168
177
  /** Max nesting depth walked; anything deeper is NOT scanned. */
169
178
  readonly maxDepth: 5;
170
179
  };
171
- /**
172
- * Recursively scans an args value for known secret patterns.
173
- * Handles nested objects, arrays, and JSON-encoded strings.
174
- * Returns the first match found, or null if clean.
175
- */
176
180
  declare function scanArgs(args: unknown, depth?: number, fieldPath?: string): DlpMatch | null;
177
181
  /** Scan a plain text string (e.g. Claude response prose) for DLP patterns. */
178
182
  declare function scanText(text: string): DlpMatch | null;
@@ -264,6 +268,25 @@ declare function parseDestHost(token: string): string | null;
264
268
  * and parses the host. Deduplicated by host. Pure — no I/O, no DNS.
265
269
  */
266
270
  declare function extractShellDestinations(command: string): ShellDestination[];
271
+ /** A raw destination-position token from a network binary, BEFORE parseDestHost.
272
+ * The SSRF floor needs these because parseDestHost requires a dot and therefore
273
+ * drops `2852039166`, which denotes a cloud metadata address (measured: that
274
+ * command is allowed today at the strictest egress setting). */
275
+ interface ShellDestToken {
276
+ token: string;
277
+ binary: string;
278
+ }
279
+ /**
280
+ * Destination-position tokens for every network binary in a command, unparsed.
281
+ *
282
+ * Same walk and same flag-skipping as extractShellDestinations, so the two agree
283
+ * on which arguments are destinations. It exists as a sibling rather than a
284
+ * widening of parseDestHost because the dot requirement there is a load-bearing
285
+ * false-positive guard: turning every numeric token into a HOST would change
286
+ * egress verdicts for every user. Asking whether a token DENOTES A PROTECTED
287
+ * ADDRESS has no false-positive surface, because that comparison is exact.
288
+ */
289
+ declare function extractShellDestTokens(command: string): ShellDestToken[];
267
290
  declare function analyzeFsOperation(command: string): FsOpVerdict | null;
268
291
  interface ShellCommandAnalysis {
269
292
  /** First word of every CallExpr — the command names invoked. */
@@ -291,6 +314,14 @@ interface EgressPolicy {
291
314
  deny: string[];
292
315
  /** Auto-allow localhost / RFC1918 / *.local. Default true. */
293
316
  allowPrivate: boolean;
317
+ /** SSRF floor: exempts OVERRIDABLE tiers only (carrier-grade NAT, and
318
+ * loopback/RFC1918 when ssrfStrict is on). A tier-1 entry is ignored here
319
+ * and rejected at config load with a reason. */
320
+ ssrfAllow?: readonly string[];
321
+ /** SSRF floor tier 3: also block loopback and RFC1918. Off by default,
322
+ * because a developer talks to those constantly (72 of 308 destinations on
323
+ * measured real history). */
324
+ ssrfStrict?: boolean;
294
325
  }
295
326
  interface EgressVerdict {
296
327
  verdict: 'block' | 'review';
@@ -990,7 +1021,7 @@ declare const SENSITIVE_PATH_RE: RegExp;
990
1021
  */
991
1022
  declare const FILE_TOOLS: Set<string>;
992
1023
 
993
- type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
1024
+ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card' | 'IBAN';
994
1025
  /**
995
1026
  * Detect PII patterns in a string. Returns a deduplicated list — one entry
996
1027
  * per distinct pattern type, never multiple "Email" findings from one input.
@@ -998,13 +1029,40 @@ type PiiPattern = 'Email' | 'SSN' | 'Phone' | 'Credit Card';
998
1029
  declare function detectPii(text: string): PiiPattern[];
999
1030
  declare const REALTIME_PII_PATTERNS: readonly PiiPattern[];
1000
1031
  /**
1001
- * Realtime adapter for detectPii: walks a tool-args value (stringifying
1002
- * objects/arrays) and returns only the high-signal PII patterns found. Used by
1003
- * the authorize path to gate SSN / Credit Card in tool arguments. Pure.
1032
+ * Realtime adapter for detectPii: walks a tool-args value leaf by leaf and
1033
+ * returns only the high-signal PII patterns found. Used by the authorize path
1034
+ * to gate SSN / Credit Card in tool arguments. Pure.
1035
+ *
1036
+ * Walks leaves rather than scanning JSON.stringify(args). Stringifying turns
1037
+ * a real newline into the two characters backslash + `n`, and `n` is a word
1038
+ * character, so a card or SSN that BEGINS A LINE inside a multi-line value
1039
+ * (a CSV being written, for instance) had no \b in front of it and was
1040
+ * invisible to the realtime gate. Raw leaves keep the real newline.
1004
1041
  */
1005
1042
  declare function detectArgsPii(args: unknown): PiiPattern[];
1006
1043
 
1007
- 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';
1044
+ interface CanaryValue {
1045
+ id: string;
1046
+ value: string;
1047
+ retired?: boolean;
1048
+ }
1049
+ type CanaryView = 'raw' | 'url-decoded' | 'base64-decoded' | 'hex-decoded' | 'separators-stripped';
1050
+ interface CanaryHit {
1051
+ id: string;
1052
+ view: CanaryView;
1053
+ fieldPath?: string;
1054
+ retired: boolean;
1055
+ }
1056
+ /** Values shorter than this are skipped by the matcher AND rejected by the registry (H12):
1057
+ * below 16 the separators-stripped view starts to collide with ordinary text. */
1058
+ declare const CANARY_MIN_LENGTH = 16;
1059
+ /** Exact containment of any registered value in any bounded view of the text. */
1060
+ declare function matchCanary(text: string, values: readonly CanaryValue[]): CanaryHit | null;
1061
+ /** Walks string leaves (depth <= 6, per-field budget), parsing JSON-in-string leaves
1062
+ * so escaped values are seen (B23), like scanArgs. */
1063
+ declare function matchCanaryArgs(args: unknown, values: readonly CanaryValue[]): CanaryHit | null;
1064
+
1065
+ type CanonicalFindingType = 'smart-rule' | 'ast-fs-op' | 'dlp' | 'pii' | 'canary' | 'sensitive-file-read' | 'privilege-escalation' | 'destructive-op' | 'pipe-to-shell' | 'eval-of-remote' | 'loop' | 'long-output-redacted';
1008
1066
  type CanonicalAgent = 'claude' | 'gemini' | 'codex' | 'shell';
1009
1067
  type CanonicalSourceType = 'default' | 'shield' | 'user' | 'engine';
1010
1068
  interface CanonicalFinding {
@@ -1083,6 +1141,13 @@ interface ExtractContext {
1083
1141
  toolInspection: Record<string, string>;
1084
1142
  /** DLP enabled flag from PolicyConfig. */
1085
1143
  dlpEnabled: boolean;
1144
+ /** Decoy credentials registered on this machine (value plus kind and plant
1145
+ * path for the finding text). Optional: absent or empty means no canary
1146
+ * pass, which keeps extractor output machine-independent in CI (H3). */
1147
+ canaryValues?: ReadonlyArray<CanaryValue & {
1148
+ kind?: string;
1149
+ path?: string;
1150
+ }>;
1086
1151
  }
1087
1152
  interface SessionExtractContext {
1088
1153
  sessionId: string;
@@ -1131,7 +1196,7 @@ declare const LONG_OUTPUT_THRESHOLD_BYTES: number;
1131
1196
  * and fails CI when the hash drifts without a version bump — forgetting
1132
1197
  * is loud, not silent.
1133
1198
  */
1134
- declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v9";
1199
+ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v10";
1135
1200
  /**
1136
1201
  * SHA-256 prefix of the detector-source files
1137
1202
  * (canonical.ts + pii.ts + destructive-regex.ts).
@@ -1142,7 +1207,7 @@ declare const CANONICAL_EXTRACTOR_VERSION = "canonical-v9";
1142
1207
  * files changed, this hash must change too, and you must consciously
1143
1208
  * decide whether to bump CANONICAL_EXTRACTOR_VERSION."
1144
1209
  */
1145
- declare const CANONICAL_EXTRACTOR_HASH = "0d6c1ddb9a5af5b7";
1210
+ declare const CANONICAL_EXTRACTOR_HASH = "5c786cc174281e51";
1146
1211
  declare function extractCanonicalFindings(call: ToolCallEntry, ctx: ExtractContext): CanonicalFinding[];
1147
1212
  declare function extractSessionLevelFindings(calls: ReadonlyArray<SessionToolCall>, ctx: SessionExtractContext): CanonicalFinding[];
1148
1213
  /**
@@ -1166,7 +1231,64 @@ declare function dedupeCanonicalFindings(findings: ReadonlyArray<CanonicalFindin
1166
1231
  declare function toScanFinding(c: CanonicalFinding): ScanFinding | null;
1167
1232
  declare function previewArgs(input: Record<string, unknown>, max: number): string;
1168
1233
 
1234
+ type SsrfTier = 'metadata' | 'link-local' | 'multicast' | 'unspecified' | 'cgnat' | 'private';
1235
+ interface SsrfMatch {
1236
+ tier: SsrfTier;
1237
+ /** false for tier 1: no allowlist entry exempts it. */
1238
+ overridable: boolean;
1239
+ /** 'address' when an IP literal matched, 'hostname' for the metadata name list. */
1240
+ kind: 'address' | 'hostname';
1241
+ /** The canonical address; absent for a hostname match. Never an empty string:
1242
+ * an empty value is dropped in flight by cloud.ts and the audit shipper. */
1243
+ normalized?: string;
1244
+ }
1245
+ /** Longest legal DNS name. The caller does NOT reliably bound this: a URL with a
1246
+ * 261-character host reaches us intact (measured), so we bound our own input. */
1247
+ declare const SSRF_MAX_HOST = 253;
1248
+ /**
1249
+ * Fold any spelling of an IP literal to one canonical address, or null when the
1250
+ * input is not an IP literal (a hostname, or malformed). Never throws.
1251
+ *
1252
+ * IPv4-mapped IPv6 folds to the IPv4 address it denotes (RFC 4291 2.5.5.2), so
1253
+ * `[::ffff:a9fe:a9fe]` and `169.254.169.254` compare equal. Brackets and a zone
1254
+ * id are stripped here: the caller does NOT strip them (measured).
1255
+ */
1256
+ declare function normalizeIpLiteral(host: string): string | null;
1257
+ /**
1258
+ * Classify a destination host. Returns null when it is not a protected address,
1259
+ * which means the ordinary egress policy decides. Never throws.
1260
+ *
1261
+ * Ordering is significant: the exact metadata addresses are checked before the
1262
+ * link-local range that contains them, so the reason names metadata.
1263
+ */
1264
+ declare function classifySsrf(host: string): SsrfMatch | null;
1265
+ interface SsrfVerdict extends SsrfMatch {
1266
+ /** The token as written in the command. */
1267
+ host: string;
1268
+ binary: string;
1269
+ reason: string;
1270
+ }
1271
+ interface SsrfFloorOptions {
1272
+ /** Exempts OVERRIDABLE tiers only. A tier-1 entry here is ignored (and is
1273
+ * rejected at config load with a reason, never silently). */
1274
+ ssrfAllow?: readonly string[];
1275
+ /** Opt-in tier 3: loopback and RFC1918. Off by default: a developer talks to
1276
+ * those constantly (72 of 308 destinations on measured real history). */
1277
+ ssrfStrict?: boolean;
1278
+ }
1279
+ /**
1280
+ * The floor. Returns the first protected destination, or null.
1281
+ *
1282
+ * Runs unconditionally: not gated on egress.enabled, mode, allow or allowPrivate.
1283
+ * The one thing that does switch it off is `node9 pause`, which returns before
1284
+ * every gate; that is stated as a limit rather than special-cased here.
1285
+ */
1286
+ declare function ssrfFloor(tokens: ReadonlyArray<{
1287
+ token: string;
1288
+ binary: string;
1289
+ }>, opts?: SsrfFloorOptions): SsrfVerdict | null;
1290
+
1169
1291
  /** Engine version stamped on audit entries for future drift detection. */
1170
1292
  declare const ENGINE_VERSION = "1.4.0";
1171
1293
 
1172
- 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, 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, 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, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };
1294
+ export { AST_FS_REGEX_RULES, type AuditEntryForClassify, BASH_TOOL_NAMES, BUILTIN_SHIELDS, type BlastEnvFinding, type BlastFinding, type BlastResult, type BlastSummary, CANARY_MIN_LENGTH, CANONICAL_EXTRACTOR_HASH, CANONICAL_EXTRACTOR_VERSION, COST_PER_LOOP_ITER_USD, type CanaryHit, type CanaryValue, type CanaryView, type CanonicalAgent, type CanonicalFinding, type CanonicalFindingType, type CanonicalSourceType, DEFAULT_EGRESS_ALLOWLIST, DESTRUCTIVE_OP_RE, DLP_PATTERNS, DLP_SCAN_LIMITS, type DlpMatch, ENGINE_VERSION, type EgressPolicy, type EgressVerdict, type ExtractContext, FILE_TOOLS, FLAGS_WITH_VALUES, FS_READ_TOOLS, type FsOpVerdict, type InjectionConfidence, type InjectionContext, type InjectionMatch, LONG_OUTPUT_THRESHOLD_BYTES, LOOP_MAX_RECORDS, LOOP_THRESHOLD_FOR_WASTE, type LoopWindowEvaluation, PRIVILEGE_ESCALATION_RE, type PiiPattern, type PipeChainAnalysis, type PolicyConfig, type PolicyContext, type PolicyHostHooks, type PolicyVerdict, type ProvenanceLookup, type ProvenanceTrust, REALTIME_PII_PATTERNS, type RiskMetadata, SCAN_SIGNAL_WEIGHTS, SENSITIVE_PATH_RE, SENSITIVE_PATH_REGEXES, SSRF_MAX_HOST, type ScanFinding, type ScanSignals, type ScanSummary, type ScoreTier, type SessionExtractContext, type SessionToolCall, type Severity, type ShellCommandAnalysis, type ShellDestToken, type ShellDestination, type ShieldDefinition, type ShieldOverrides, type ShieldVerdict, type SmartCondition, type SmartRule, type SsrfFloorOptions, type SsrfMatch, type SsrfTier, type SsrfVerdict, type ToolCallEntry, type ToolCallRecord, analyzeFsOperation, analyzePipeChain, analyzeShellCommand, checkDangerousSql, classifyAuditEntry, classifyRuleSeverity, classifyScanSignal, classifySsrf, computeAgentDeviceScore, computeArgsHash, computeBlendedSecurityScore, computeScanScore, computeSecurityScore, dedupeCanonicalFindings, detectArgsPii, detectDangerousEval, detectDangerousShellExec, detectInlineExec, detectPii, evaluateEgress, evaluateLoopWindow, evaluatePolicy, evaluateSmartConditions, extractAllSshHosts, extractCanonicalFindings, extractNetworkTargets, extractPositionalArgs, extractSessionLevelFindings, extractShellDestTokens, extractShellDestinations, getCompiledRegex, getNestedValue, hostMatches, isBashTool, isIgnoredTool, isPrivateHost, isProtectedHomePath, isShellShapedTool, isShieldVerdict, matchCanary, matchCanaryArgs, matchSensitivePath, matchesPattern, narrativeRuleLabel, normalizeCommandForPolicy, normalizeIpLiteral, parseAllSshHostsFromCommand, parseDestHost, previewArgs, redactText, resolvePinned, scanArgs, scanInjection, scanText, sensitivePathMatch, ssrfFloor, summarizeBlast, summarizeScan, toScanFinding, toolMatchesRule, truncateBlastPath, validateOverrides, validateRegex, validateShieldDefinition };