/** * ATR Engine - Evaluates agent events against ATR rules * * Core detection engine that: * 1. Loads ATR YAML rules from disk * 2. Evaluates agent events (LLM I/O, tool calls, behaviors) against rules * 3. Returns matched rules with confidence scores * 4. Supports two condition formats: * - Array format: conditions is an array of {field, operator, value} objects * - Named format: conditions is an object map of named condition blocks * * @module agent-threat-rules/engine */ import type { ATRRule, ATRMatch, AgentEvent, ATRVerdict, ActionResult, ScanResult, ATRLanguage, ATRSemanticJudge } from './types.js'; import type { SessionTracker } from './session-tracker.js'; import type { ActionExecutor } from './action-executor.js'; import type { SkillFingerprintStore } from './skill-fingerprint.js'; import type { SemanticLayerConfig } from './layer-integration.js'; import type { InvariantChecker } from './tier0-invariant.js'; import type { BlacklistProvider } from './tier1-blacklist.js'; import type { EmbeddingModule } from './modules/embedding.js'; export declare function decodeBase64Blocks(content: string): string[]; /** * Detection reporter — opt-in callback for feeding scan results back to * ATR Threat Cloud. Every integration endpoint that enables this becomes * a sensor in the global detection network. Anonymized detection events * are aggregated across all endpoints to crystallize new rules. * * Privacy: only ruleId, severity, and scanTarget are reported by default. * No raw content, no PII, no file paths. Platforms can override onDetection * to control exactly what is sent. */ export interface ATRReporter { /** Called for every match. Implement to POST anonymized data to TC. */ readonly onDetection: (report: ATRDetectionReport) => void | Promise; /** Called when a scan completes with zero matches (opt-in). */ readonly onClean?: (report: ATRCleanReport) => void | Promise; } export interface ATRDetectionReport { readonly ruleId: string; readonly severity: string; readonly scanTarget: string; readonly category: string; readonly confidence: number; readonly timestamp: string; /** Content hash (SHA-256) — identifies the scanned artifact without revealing content */ readonly contentHash: string; } export interface ATRCleanReport { readonly rulesEvaluated: number; readonly scanTarget: string; readonly timestamp: string; readonly contentHash: string; } export interface ATREngineConfig { /** Directory containing ATR rule YAML files */ rulesDir?: string; /** Pre-loaded rules (for testing or embedding) */ rules?: ATRRule[]; /** Optional session tracker for behavioral detection across events */ sessionTracker?: SessionTracker; /** Optional Tier 0: Invariant enforcement (hard boundaries, pre-check) */ invariantChecker?: InvariantChecker; /** Optional Tier 1: Skill blacklist provider (known-bad lookup) */ blacklistProvider?: BlacklistProvider; /** Optional Layer 2: Skill behavioral fingerprinting (no LLM required) */ fingerprintStore?: SkillFingerprintStore; /** Optional Tier 2.5: Embedding similarity module (requires @xenova/transformers) */ embeddingModule?: EmbeddingModule; /** Optional Layer 3: Semantic LLM-as-judge analysis (requires API key) */ semanticModule?: SemanticLayerConfig; /** Optional rule-level semantic judge for detection.method=semantic rules */ semanticJudge?: ATRSemanticJudge; /** Optional: detection reporter for feeding results to ATR Threat Cloud */ reporter?: ATRReporter; /** * Detection lane — controls which rule maturities are allowed to fire. * 'enforce' : only maturity=stable rules (auto-block lane, lowest FP). * 'alert' : maturity stable + test (analyst/correlation lane). * 'hunt' : all maturities (advisory/eval; default, backward-compatible). * Deprecated/draft rules are always skipped regardless of lane. */ lane?: 'enforce' | 'alert' | 'hunt'; /** * Embedding-confirm threshold for rules flagged `confirm: embedding`. * In enforce/alert lanes, such a rule's match is dropped unless the input's * cosine similarity to the known-attack reference >= this value. Requires an * embeddingModule; if absent, confirm-rules are dropped from enforce/alert * (they are too broad to block without confirmation). Default 0.6. */ confirmThreshold?: number; } export declare class ATREngine { private readonly config; private rules; private readonly compiledPatterns; private readonly semanticModuleInstance; /** * Find bundled rules directory shipped with the npm package. * Checks: ../rules (from dist/), ./rules (repo root) */ private findBundledRulesDir; constructor(config?: ATREngineConfig); /** * Load rules from configured directory and/or pre-loaded rules. */ loadRules(): Promise; /** * Lane gate: does this rule's maturity qualify for the configured detection * lane? 'enforce' = stable only; 'alert' = stable+test; 'hunt'/unset = all. * Keeps experimental content out of the auto-block path without deleting it. */ private passesLane; /** * Load a single rule file and add it to the engine. */ addRuleFile(filePath: string): void; /** * Add a pre-parsed rule to the engine. */ addRule(rule: ATRRule): void; /** * Evaluate an agent event against all loaded ATR rules (synchronous, regex only). * In enforce/alert lanes, rules flagged `confirm: embedding` are EXCLUDED here — * embedding confirmation is asynchronous and cannot run in this path, so a broad * confirm-rule must not fire unconfirmed in a blocking lane. Use * evaluateWithVerdict() for confirmed enforce/alert detection. */ evaluate(event: AgentEvent): ATRMatch[]; /** * Raw synchronous evaluation: returns all lane-passing matches including * UNCONFIRMED `confirm: embedding` rules. Used ONLY internally by the verdict * path, which then applies the async embedding-confirm gate. * * INTERNAL — do not call from outside evaluateWithVerdict. Calling this directly * bypasses the embedding-confirm gate, letting broad confirm-rules fire * unconfirmed in enforce/alert. The public sync entry point is evaluate(). */ private evaluateRaw; /** * Async evaluation path that supports rule-level method=semantic dispatch. * * The synchronous evaluate() method remains pattern-only for compatibility. * Consumers that configure semanticJudge should call evaluateAsync() or * evaluateWithVerdict(), which delegates here when a semantic judge exists. */ evaluateAsync(event: AgentEvent): Promise; /** * Evaluate a single rule against an event. * Supports both array-format and named-map-format conditions. */ private evaluateRule; /** Evaluate a rule using pattern-mode conditions, regardless of detection.method. */ private evaluatePatternRule; /** * Async variant that supports method=semantic with an injected judge. * For trace/pattern/signature/behavioral methods, defers to the sync path. */ private evaluateRuleAsync; /** * Minimal signature-method evaluator (atr-method-v1.1.md §5). * Walks detection.signature.indicators against event.fields with the * specified match_logic. Hash-typed indicators expect event.fields to * contain pre-computed hex hashes at the indicated target_field. */ private evaluateSignatureMethod; /** * Evaluate array-format conditions: [{field, operator, value}, ...] * with condition: "any" | "all" */ private evaluateArrayConditions; /** * Evaluate a single array-format condition {field, operator, value}. */ private evaluateArrayCondition; /** * Evaluate named-map-format conditions: {name: {field, patterns, match_type}, ...} * with condition: "name1 AND name2" | "name1 OR name2" | "name1" */ private evaluateNamedConditions; /** * Evaluate a single named condition against an event. */ private evaluateNamedCondition; /** * Evaluate a pattern matching condition (named format with patterns array). */ private evaluatePatternCondition; /** * Determine if a rule should suppress matches inside markdown code blocks. * Rules that commonly false-positive on documentation (shell commands, file paths, * code examples) are suppressed. Prompt injection rules are NEVER suppressed * because attackers deliberately hide payloads in code blocks. */ private shouldSuppressInCodeBlocks; /** * Evaluate a behavioral threshold condition. * When a session tracker is available and the event has a sessionId, * supports session-derived metrics: call_frequency, pattern_frequency, event_count. */ private evaluateBehavioralCondition; /** * Resolve a metric value from event metrics or session tracker. * Session-derived metrics use the format: "call_frequency:toolName" or "pattern_frequency:pattern". */ private resolveMetricValue; /** * Parse a window string (e.g. "5m", "1h", "30s") to milliseconds. * Defaults to 5 minutes if not specified or unparseable. */ private parseWindowMs; /** * Evaluate a sequence condition against the current event. * * Two modes: * 1. Session-aware (when SessionTracker + sessionId available): * Checks patterns across historical events in the session. * Respects `ordered` flag and `within` time window. * 2. Single-event fallback: checks if patterns co-occur in one event. */ private evaluateSequenceCondition; /** * Cross-event sequence detection using SessionTracker. * Checks if step patterns have been seen across events in order. */ private evaluateSequenceAcrossSession; /** * Single-event fallback: check if step patterns co-occur in one event. */ private evaluateSequenceSingleEvent; /** * Resolve a field value from an agent event. */ private resolveField; /** * Evaluate a boolean expression string against condition results. * Supports AND, OR, NOT operators. */ private evaluateExpression; /** * Split expression by operator, respecting parentheses. */ private splitByOperator; /** * Pre-compile regex patterns for a rule (performance optimization). * Supports both array-format and named-map-format conditions. */ private compilePatterns; /** * Evaluate an event and compute a verdict with optional action execution. * * Combines evaluate() + computeVerdict() + optional ActionExecutor * into a single call for convenience. */ evaluateWithVerdict(event: AgentEvent, executor?: ActionExecutor): Promise<{ verdict: ATRVerdict; actionResults: readonly ActionResult[]; layersUsed: readonly string[]; }>; /** Get loaded rule count */ getRuleCount(): number; /** Get all loaded rules */ getRules(): readonly ATRRule[]; /** Get a rule by ID */ getRuleById(id: string): ATRRule | undefined; /** Get rules by category */ getRulesByCategory(category: string): ATRRule[]; /** * Scan SKILL.md content for threats. * All rules fire with scanContext='skill': * - skill/both rules: native context, full confidence * - MCP-only rules: cross-context, confidence * 0.6 * Also decodes base64 blocks and scans decoded content. * Code-block suppression and FP denylist applied in evaluate(). */ scanSkill(content: string): ATRMatch[]; /** * Async SKILL.md scan that supports method=semantic rules through semanticJudge. */ scanSkillAsync(content: string): Promise; /** Scan a SKILL.md file and return a unified ScanResult with content_hash. */ scanSkillFull(content: string, filePath?: string): ScanResult; /** Async SKILL.md scan result with semantic rule support. */ scanSkillFullAsync(content: string, filePath?: string): Promise; /** Evaluate an MCP agent event and return a unified ScanResult with content_hash. */ evaluateFull(event: AgentEvent, filePath?: string): ScanResult; /** Async MCP event scan result with semantic rule support. */ evaluateFullAsync(event: AgentEvent, filePath?: string): Promise; } /** * Normalize Unicode text to NFC form and strip zero-width characters. * This prevents evasion via combining characters, zero-width joiners, etc. * * NFC was chosen over NFKC to preserve writer intent \u2014 full-width letters * (\uFF21\uFF22\uFF23 vs ABC) remain distinct so rules that explicitly target full-width * evasion can still match. For aggressive normalization use * `normalizeUnicodeAggressive()`. */ export declare function normalizeUnicode(text: string): string; export declare function foldConfusables(text: string): string; /** * Heuristic dominant-script detection for v3.0 multilingual dispatch. * * Counts Unicode-block code points and returns the BCP-47 tag of the * dominant script. Used to skip language-tagged conditions whose * declared language does not match the input \u2014 pure optimisation, never * affects correctness of language-untagged rules. * * Disambiguation: * - Han script: split via simplified-only vs traditional-only indicator * char sets. Tie / both zero \u2192 defaults to 'zh-Hant'. * - Latin script: 'es' if Spanish-specific punctuation/diacritics * (\u00F1, \u00BF, \u00A1) detected, else 'en'. * - Empty / pure ASCII without Spanish markers \u2192 'en'. * * Exported for unit testing; not part of the public API surface. */ export declare function detectInputLanguage(text: string): ATRLanguage; /** * Decide whether a condition with `language: condLang` should be evaluated * against an input whose detected language is `inputLang`. * * Rules: * - condLang undefined (no language field) \u2192 always evaluate (v2.x compat) * - condLang === inputLang \u2192 evaluate * - Han-script ambiguity: if input is Han and condLang is the other * Chinese variant, still evaluate (the cheap detector cannot reliably * split zh-Hant vs zh-Hans, so we err on inclusion) * - Otherwise \u2192 skip (return false) * * Exported for unit testing; not part of the public API surface. */ export declare function conditionLanguageMatches(condLang: ATRLanguage | undefined, inputLang: ATRLanguage): boolean; //# sourceMappingURL=engine.d.ts.map