/** * AI Guard Runtime SDK - Dynamic scanning in observe-only mode * * This SDK provides runtime monitoring of LLM applications without blocking traffic. * It captures request/response data, evaluates dynamic rules, and emits findings in * canonical schema format. */ /** * SDK configuration options. */ import type { AuditSink } from "./audit-sink"; export type { AuditSink }; export type TenantRole = "admin" | "reader"; export type RetentionPolicyConfig = { maxEntries?: number; maxAgeMs?: number; }; export type TenantAccessControlConfig = { enforceTenantIsolation?: boolean; allowCrossTenantReadForAdmins?: boolean; tenantRoles?: Record; }; export type PrivacyControlsConfig = { redactionToken?: string; piiScrubbingEnabled?: boolean; secretScrubbingEnabled?: boolean; retention?: RetentionPolicyConfig; accessControl?: TenantAccessControlConfig; maxLoggedStringLength?: number; }; export type AccessAuditEntry = { id: string; timestamp: number; requestingTenantId: string; targetTenantId: string; action: "read-audit-logs" | "delete-audit-logs"; allowed: boolean; reason: string; }; export type ClassifierConfig = { enabled: boolean; modelPath?: string; /** Path to a HuggingFace vocab.json file for the WordPiece tokenizer. */ vocabPath?: string; threshold?: number; requestThreshold?: number; responseThreshold?: number; categoryThresholds?: Partial>; timeoutMs?: number; }; export type RuntimeMeteringConfig = { enabled?: boolean; actionName?: string; }; export type ToolAuthorizationPolicyConfig = { denyFileDeletion?: boolean; denyUnknownWebhookDomains?: boolean; allowedWebhookDomains?: readonly string[]; denyInternalPromptStateAccess?: boolean; blockedToolNames?: readonly string[]; }; export type SessionRiskTrackingConfig = { enabled?: boolean; maxSessionRiskScore?: number; }; export type RemoteFeedbackConfig = { enabled?: boolean; refreshMs?: number; channel?: "stable" | "preview" | "emergency" | "fast"; canaryPopulationId?: string; verifyKey?: string; }; export type RulePackChannel = "stable" | "preview" | "emergency"; export type RuleThresholdOverrides = Record; export type RulePackPayload = { packId: string; version: string; channel: RulePackChannel; issuedAt: string; minSdkVersion?: string; disabledRuleIds: string[]; thresholds: RuleThresholdOverrides; rollout?: { canaryPercent?: number; canaryWorkspaceIds?: string[]; }; attackIntel?: { summary?: string; indicators?: string[]; campaignIds?: string[]; }; releaseNotes?: { protectionsAdded?: string[]; falsePositiveReductions?: string[]; }; }; export type SignedRulePack = { payload: RulePackPayload; signature: string; algorithm: "hmac-sha256"; }; export type SDKConfig = { /** * Globally enable or disable the SDK. * Default: true */ enabled?: boolean; /** * Maximum time to spend evaluating rules per request (ms). * Evaluation stops if exceeded, findings up to that point are emitted. * Default: 50ms (targets <1% latency overhead) */ evaluationTimeoutMs?: number; /** * Maximum size of request/response payloads to capture (bytes). * Payloads larger than this are sampled or truncated. * Default: 100KB */ maxPayloadBytes?: number; /** * Callback for emitting findings during observation. * Findings are emitted to this callback in real-time. */ onFinding?: (finding: DynamicFinding) => void | Promise; /** * Callback for SDK errors. * Errors are non-fatal; SDK fails open (does not block traffic). */ onError?: (error: Error) => void; /** * Sample rate: percentage of requests to evaluate (0-100). * Default: 100 (evaluate all requests) */ sampleRatePercent?: number; /** * Whether to capture full request/response bodies. * If false, only metadata and headers are captured. * Default: true */ capturePayloads?: boolean; /** * Runtime environment name used by policy rules (example: development, staging, production). * Default: process.env.NODE_ENV ?? "development" */ environment?: string; /** * Policy configuration for mapping findings to runtime actions. */ policy?: RuntimePolicyConfig; /** * Enforcement rollout mode. * - dry-run: always observe, never enforce * - shadow-block: observe but mark wouldHaveBlocked=true and emit shadow-block alerts * - canary: enforce only for canary traffic * - enforce: enforce actions immediately * Default: dry-run */ enforcementMode?: EnforcementMode; /** * Canary rollout configuration. */ canary?: CanaryConfig; /** * Callback for per-finding policy/audit decisions. */ onAuditLog?: (entry: AuditLogEntry) => void | Promise; /** * Runtime event pipeline settings for resilient async emission. */ runtimeEventPipeline?: RuntimeEventPipelineConfig; /** * Route pinning for selective enforcement while global mode stays permissive. */ routePinning?: { enforcedRoutes?: readonly string[]; }; /** * Circuit-breaker safety for enforcement. * When enabled in fail-open mode and error rate exceeds threshold, runtime reverts to observe behavior. */ circuitBreaker?: { mode?: "disabled" | "fail-open"; errorRateThresholdPercent?: number; minRequests?: number; }; /** * Separate alert channel for shadow-block events. */ onShadowBlockAlert?: (alert: { finding: DynamicFinding; decision: ActionDecision; timestamp: number; }) => void | Promise; /** * Critical finding alert fan-out for active enforcement decisions. */ criticalAlerting?: { enabled?: boolean; pagerDuty?: { routingKey?: string; eventAction?: "trigger"; }; opsGenie?: { apiKey?: string; apiUrl?: string; priority?: "P1" | "P2" | "P3" | "P4" | "P5"; }; }; /** * Privacy, data-minimization, retention, and tenant access controls. */ privacyControls?: PrivacyControlsConfig; /** * Persistent audit sink for durable out-of-process storage of audit log * entries. When provided, every sanitized AuditLogEntry is forwarded to * `auditSink.write()` in addition to the in-process store. * * Default: NoopAuditSink (entries are only held in process memory) * * Built-in options: * - FileAuditSink — appends NDJSON to a local file * - CompositeAuditSink — fans out to multiple sinks */ auditSink?: AuditSink; /** * Canary token for system-prompt extraction detection. * Embed this exact string inside your LLM system prompt. If the model is * ever coerced into revealing its system prompt, the token will appear in * a response and a critical finding is emitted immediately. * * Minimum length: 8 characters. Use an unguessable value like a UUID or * a fake API key: `sk-canary-`. */ canaryToken?: string; /** * Optional per-session token budget guard. * If enabled, a finding is emitted when token usage inside the rolling * session window exceeds `maxPerSessionTokens`. */ tokenBudget?: { maxPerSessionTokens: number; windowMs: number; }; /** * Optional ONNX-based prompt-injection classifier. * When enabled, classifier findings are emitted in addition to regex rules. */ classifierConfig?: ClassifierConfig; /** * Streaming scan window size in estimated tokens. * SDK middleware scans stream buffers whenever this threshold is reached. * Default: 100 */ streamScanWindowTokens?: number; /** * Optional runtime metering for each evaluate() call. * Uses Platform API usage endpoint when configured. */ runtimeMetering?: RuntimeMeteringConfig; /** * Optional remote feedback prior sync for Bayesian calibration. */ remoteFeedback?: RemoteFeedbackConfig; /** * Agentic tool-call authorization controls. */ toolAuthorization?: ToolAuthorizationPolicyConfig; /** * Stateful multi-turn/session risk tracking controls. */ sessionRiskTracking?: SessionRiskTrackingConfig; }; /** * Runtime action that can be requested by policy. */ export type RuntimeAction = "observe" | "alert" | "redact" | "block" | "rate-limit"; /** * Normalized enforcement ladder used by the action engine. */ export type NormalizedEnforcementMode = "observe" | "shadow-block" | "hard-block"; /** * Rollout mode for policy enforcement. */ export type EnforcementMode = NormalizedEnforcementMode | "dry-run" | "canary" | "enforce"; /** * Config for canary enforcement. */ export type CanaryConfig = { /** * Percentage of requests eligible for enforcement (0-100). * Default: 0 */ percentage?: number; /** * Route patterns always included in canary (supports wildcard *). */ routes?: readonly string[]; /** * Seed used for deterministic percentage bucketing. * Default: "ai-guard" */ seed?: string; }; /** * A single policy rule used to select runtime actions. */ export type RuntimePolicyRule = { id: string; action: RuntimeAction; priority?: number; minimumSeverity?: DynamicFinding["severity"]; minimumConfidence?: number; routes?: readonly string[]; tenantIds?: readonly string[]; ruleIds?: readonly string[]; environments?: readonly string[]; expiresAt?: number | string | Date; note?: string; }; /** * Runtime policy config. */ export type RuntimePolicyConfig = { defaultAction?: RuntimeAction; rules?: readonly RuntimePolicyRule[]; }; /** * Policy match output for a finding. */ export type PolicyResolution = { action: RuntimeAction; reason: string; policyRuleId?: string; trace?: PolicyDecisionTrace; }; export type PolicyDecisionTrace = { requestUrl: string; tenantId: string; environment: string; requestedMode: EnforcementMode; effectiveMode: NormalizedEnforcementMode; policyRuleId?: string; policyRuleAction?: RuntimeAction; policyRulePriority?: number; policyRuleNote?: string; policyRuleExpiresAt?: string; reason: string; }; /** * Final action decision after applying rollout mode. */ export type ActionDecision = { intendedAction: RuntimeAction; appliedAction: RuntimeAction; enforced: boolean; wouldHaveBlocked: boolean; failOpenTriggered?: boolean; enforcementMode: EnforcementMode; effectiveMode: NormalizedEnforcementMode; canaryMatched: boolean; reason: string; policyRuleId?: string; trace?: PolicyDecisionTrace; }; /** * Audit log event for each policy/action decision. */ export type AuditLogEntry = { id: string; timestamp: number; requestId: string; requestUrl: string; tenantId: string; environment: string; findingId: string; ruleId: string; severity: DynamicFinding["severity"]; confidence: number; intendedAction: RuntimeAction; appliedAction: RuntimeAction; enforcementMode: EnforcementMode; effectiveMode: NormalizedEnforcementMode; enforced: boolean; wouldHaveBlocked: boolean; failOpenTriggered?: boolean; canaryMatched: boolean; policyRuleId?: string; reason: string; trace?: PolicyDecisionTrace; }; export type RuntimeEventType = "finding" | "audit"; export type RuntimeEvent = { id: string; type: RuntimeEventType; partitionKey: string; timestamp: number; payload: DynamicFinding | AuditLogEntry; attempt: number; }; export type RuntimeEventPipelineConfig = { workerCount?: number; maxQueueSize?: number; maxRetries?: number; retryBaseDelayMs?: number; onDeadLetter?: (event: RuntimeEvent, error: Error) => void | Promise; }; export type RuntimeEventPipelineMetrics = { workerCount: number; maxQueueSize: number; peakQueueDepth: number; enqueued: number; processed: number; retries: number; deadLetters: number; backpressureRejects: number; }; /** * Runtime request metadata captured by SDK middleware. */ export type CapturedRequest = { id: string; tenantId?: string; timestamp: number; method: string; url: string; headers: Record; body?: unknown; bodySize?: number; }; /** * Runtime response metadata captured by SDK middleware. */ export type CapturedResponse = { statusCode: number; headers: Record; body?: unknown; bodySize?: number; durationMs: number; }; /** * Model/LLM metadata for context during evaluation. */ export type ModelMetadata = { name: string; version?: string; provider?: string; temperature?: number; maxTokens?: number; costPerRequest?: number; usage?: { promptTokens: number; completionTokens: number; totalTokens: number; }; }; /** * Complete runtime context for a single request/response cycle. */ export type RuntimeContext = { request: CapturedRequest; response: CapturedResponse; model: ModelMetadata; sessionId?: string; executionStartMs: number; evaluationDeadlineMs: number; }; /** * A finding emitted by dynamic rule evaluation. * Uses canonical FindingSchema structure for consistency. */ export type DynamicFinding = { id: string; ruleId: string; engineType: "dynamic"; requestId: string; requestUrl: string; tenantId: string; timestamp: number; ruleName: string; category: "prompt-injection" | "sensitive-data" | "api-key-leak" | "unsafe-output" | "ai-runtime-abuse" | "config-exposure" | "training-data-poisoning" | "model-dos" | "supply-chain" | "model-theft" | "excessive-agency" | "overreliance" | "workflow-security"; severity: "low" | "medium" | "high" | "critical"; message: string; evidence?: string; confidence: number; riskScore?: number; signalSummary?: string; fixSuggestion: string; pluginId: string; modelName: string; version: { major: number; minor: number; patch: number; }; /** * Runtime decision metadata generated by policy/action engine. */ actionDecision?: ActionDecision; /** * Optional agentic runtime metadata for step/session-aware findings. */ agentStepIndex?: number; toolCallName?: string; sessionRiskScore?: number; runbookHint?: string; }; /** * SDK runtime statistics. */ export type SDKStats = { requestsEvaluated: number; requestsSkipped: number; totalFindingsEmitted: number; totalEvaluationTimeMs: number; averageEvaluationTimeMs: number; maxEvaluationTimeMs: number; errorCount: number; retentionDeletes: number; accessDeniedCount: number; lastError?: string; }; export type FindingFeedbackVerdict = "true-positive" | "false-positive" | "benign-expected-behavior" | "missed-attack"; export type FindingFeedbackResult = { findingId: string; ruleId: string; verdict: FindingFeedbackVerdict; posteriorTruePositiveProbability: number; alpha: number; beta: number; };