shrike-guard 1.3.3 → 4.0.2
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/CHANGELOG.md +70 -0
- package/README.md +16 -16
- package/dist/anthropic/index.d.mts +1 -1
- package/dist/anthropic/index.d.ts +1 -1
- package/dist/anthropic/index.js +113 -32
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs +113 -32
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.d.mts +4 -98
- package/dist/api/index.d.ts +4 -98
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +1 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/auth-DotJtJHE.d.mts +97 -0
- package/dist/auth-DotJtJHE.d.ts +97 -0
- package/dist/gemini/index.d.mts +36 -20
- package/dist/gemini/index.d.ts +36 -20
- package/dist/gemini/index.js +130 -46
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs +130 -46
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.d.mts +535 -8
- package/dist/index.d.ts +535 -8
- package/dist/index.js +946 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +930 -33
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.d.mts +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +119 -38
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/index.mjs +119 -38
- package/dist/openai/index.mjs.map +1 -1
- package/dist/scanner-cxA3LnQR.d.mts +313 -0
- package/dist/scanner-cxA3LnQR.d.ts +313 -0
- package/package.json +11 -9
- package/dist/scanner-CkCXFY3H.d.mts +0 -166
- package/dist/scanner-CkCXFY3H.d.ts +0 -166
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration constants and types for the Shrike Guard SDK.\n */\n\nimport { randomUUID } from 'node:crypto';\n\n/**\n * Defines behavior when scan operations fail.\n */\nexport enum FailMode {\n /**\n * Allow the request to proceed (fail-open). Use this mode when\n * availability is prioritized over strict security.\n */\n OPEN = 'open',\n\n /**\n * Block the request and raise an exception (fail-closed). This is the\n * default behavior — matching the MCP server's security posture. Use\n * fail-open only when you've explicitly decided availability matters more.\n */\n CLOSED = 'closed',\n}\n\n/** Default timeout for scan requests in milliseconds */\nexport const DEFAULT_SCAN_TIMEOUT = 10000;\n\n/** Default fail mode - block requests when scan fails (fail-closed) */\nexport const DEFAULT_FAIL_MODE = FailMode.CLOSED;\n\n/** Default Shrike API endpoint (uses load balancer for scalability) */\nexport const DEFAULT_ENDPOINT = 'https://api.shrikesecurity.com/agent';\n\n/** Default rate limit (requests per minute) */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE = 100;\n\n// Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n// No local patterns needed - backend handles all detection logic\n\n/** SDK identification */\nexport const SDK_NAME = 'typescript';\nexport const SDK_USER_AGENT = 'shrike-guard-typescript';\n\n/**\n * Session identity — persists for the lifetime of this SDK process.\n * Maps to L9's SessionCache key for multi-turn attack detection.\n */\nconst SESSION_ID = randomUUID();\nconst AGENT_ID = process.env.SHRIKE_AGENT_ID || `sdk-ts-${randomUUID().slice(0, 8)}`;\n\n/** Returns the stable session ID for this SDK process. */\nexport function getSessionId(): string {\n return SESSION_ID;\n}\n\n/** Returns the agent ID (from SHRIKE_AGENT_ID env or auto-generated). */\nexport function getAgentId(): string {\n return AGENT_ID;\n}\n\n/**\n * Approval information returned when a policy requires human sign-off.\n */\nexport interface ApprovalInfo {\n requires_approval: boolean;\n approval_id: string;\n approval_level: string;\n action_summary: string;\n policy_name: string;\n expires_in_seconds: number;\n threat_type?: string;\n severity?: string;\n owasp_category?: string;\n risk_factors?: string[];\n original_action?: string;\n}\n\n/**\n * Retry configuration for transient failure resilience.\n * Matches MCP server's RETRY_CONFIG for consistent behavior.\n */\nexport const RETRY_CONFIG = {\n maxRetries: 2,\n initialDelayMs: 200,\n maxDelayMs: 2000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'fetch failed'],\n};\n","/**\n * Custom exceptions for the Shrike Guard SDK.\n */\n\n/**\n * Base exception for all Shrike SDK errors.\n */\nexport class ShrikeError extends Error {\n public readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'ShrikeError';\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Raised when a scan operation fails and fail_mode is 'closed'.\n *\n * This exception is raised when:\n * - The Shrike API times out\n * - A network error occurs\n * - The API returns an unexpected error\n *\n * When fail_mode is 'open' (default), these errors are silently\n * handled and the request is allowed to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n tonality_drift_profanity: 'jailbreak',\n tonality_drift_casual: 'jailbreak',\n tonality_drift_hostile: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxicity\n toxicity: 'toxicity',\n harmful_content: 'toxicity',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxicity: 'medium',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n return THREAT_TYPE_MAP[normalized] || 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection details, normalizes threat types,\n * and buckets confidence scores.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n\n // Pass through approval_info if present (not an IP concern)\n const approvalInfo = raw.approval_info;\n\n if (safe) {\n const result: ScanResult = {\n safe: true,\n reason: raw.reason || '',\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n }\n\n // Unsafe: normalize and sanitize\n const threatType = normalizeThreatType(raw.threat_type);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n const result: ScanResult = {\n safe: false,\n threat_type: threatType,\n severity: severity,\n confidence: confidence,\n reason: raw.reason || guidance,\n guidance: guidance,\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","/**\n * SDK version - keep in sync with package.json\n */\nexport const VERSION = '1.3.1';\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: unknown[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n [key: string]: unknown;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\n/**\n * When running without an API key, appends a signup hint to scan results\n * so agents/users know they can upgrade from regex-only to full scanning.\n */\nexport function maybeAddSignupHint(result: ScanResult, apiKey: string): ScanResult {\n if (apiKey) return result;\n // Don't override if backend already provided upgrade_hint\n if ((result as any).upgrade_hint) return result;\n return {\n ...result,\n _note: 'Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup',\n };\n}\n\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n\n if (!this.apiKey) {\n console.warn('[shrike-guard] No API key provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/scan`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, maybeAddSignupHint, ScanResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n if (!this._shrikeApiKey) {\n console.warn('[shrike-guard] No shrikeApiKey provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L4 (regex, unicode, encoding, token normalization)\n * - Pro tier: L1-L8 (full scan including LLM)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/scan`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan API error: ${response.status}` };\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this._shrikeApiKey);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return { safe: true, reason: 'Scan timeout, failing open' };\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan error: ${errorMessage}` };\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (!scanResult.safe) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID"],"mappings":";;;;;;;;;;AAyBO,IAAM,oBAAA,GAAuB,GAAA;AAG7B,IAAM,iBAAA,GAAoB,QAAA;AAG1B,IAAM,gBAAA,GAAmB,sCAAA;AASzB,IAAM,QAAA,GAAW,YAAA;AAOxB,IAAM,aAAaA,iBAAA,EAAW;AAC9B,IAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,CAAA,OAAA,EAAUA,mBAAW,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAG3E,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO,UAAA;AACT;AAGO,SAAS,UAAA,GAAqB;AACnC,EAAA,OAAO,QAAA;AACT;;;ACnDO,IAAM,WAAA,GAAN,cAA0B,KAAA,CAAM;AAAA,EAGrC,WAAA,CAAY,OAAA,EAAiB,OAAA,GAAmC,EAAC,EAAG;AAClE,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAEf,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF,CAAA;AAcO,IAAM,eAAA,GAAN,cAA8B,WAAA,CAAY;AAAA,EAC/C,WAAA,CAAY,OAAA,EAAiB,OAAA,GAAmC,EAAC,EAAG;AAClE,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF,CAAA;AASO,IAAM,kBAAA,GAAN,cAAiC,WAAA,CAAY;AAAA,EAUlD,YACE,OAAA,EACA,UAAA,EACA,UAAA,EACA,UAAA,GAAwB,EAAC,EACzB;AACA,IAAA,KAAA,CAAM,OAAA,EAAS;AAAA,MACb,WAAA,EAAa,UAAA;AAAA,MACb,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACpB;AACF,CAAA;;;AC3DA,IAAM,eAAA,GAA0C;AAAA;AAAA,EAE9C,gBAAA,EAAkB,kBAAA;AAAA,EAClB,SAAA,EAAW,kBAAA;AAAA,EACX,MAAA,EAAQ,kBAAA;AAAA,EACR,oBAAA,EAAsB,kBAAA;AAAA,EACtB,cAAA,EAAgB,kBAAA;AAAA,EAChB,oBAAA,EAAsB,kBAAA;AAAA,EACtB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,cAAA,EAAgB,kBAAA;AAAA;AAAA,EAEhB,SAAA,EAAW,WAAA;AAAA,EACX,iBAAA,EAAmB,WAAA;AAAA,EACnB,aAAA,EAAe,WAAA;AAAA,EACf,QAAA,EAAU,WAAA;AAAA,EACV,YAAA,EAAc,WAAA;AAAA,EACd,kBAAA,EAAoB,WAAA;AAAA,EACpB,QAAA,EAAU,WAAA;AAAA,EACV,UAAA,EAAY,WAAA;AAAA;AAAA;AAAA,EAGZ,wBAAA,EAA0B,eAAA;AAAA,EAC1B,sBAAA,EAAwB,eAAA;AAAA,EACxB,qBAAA,EAAuB,WAAA;AAAA;AAAA,EAEvB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,wBAAA,EAA0B,oBAAA;AAAA;AAAA,EAE1B,iBAAA,EAAmB,mBAAA;AAAA,EACnB,YAAA,EAAc,mBAAA;AAAA,EACd,UAAA,EAAY,mBAAA;AAAA,EACZ,OAAA,EAAS,mBAAA;AAAA,EACT,SAAA,EAAW,mBAAA;AAAA,EACX,sBAAA,EAAwB,mBAAA;AAAA,EACxB,qBAAA,EAAuB,mBAAA;AAAA;AAAA,EAEvB,aAAA,EAAe,eAAA;AAAA,EACf,IAAA,EAAM,eAAA;AAAA,EACN,SAAA,EAAW,eAAA;AAAA,EACX,YAAA,EAAc,eAAA;AAAA,EACd,aAAA,EAAe,eAAA;AAAA,EACf,eAAA,EAAiB,eAAA;AAAA,EACjB,aAAA,EAAe,eAAA;AAAA;AAAA,EAEf,cAAA,EAAgB,gBAAA;AAAA,EAChB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,cAAA,EAAgB,gBAAA;AAAA,EAChB,WAAA,EAAa,gBAAA;AAAA,EACb,cAAA,EAAgB,gBAAA;AAAA,EAChB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,iBAAA,EAAmB,gBAAA;AAAA;AAAA,EAEnB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,OAAA,EAAS,kBAAA;AAAA,EACT,OAAA,EAAS,kBAAA;AAAA,EACT,UAAA,EAAY,kBAAA;AAAA,EACZ,cAAA,EAAgB,kBAAA;AAAA,EAChB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,UAAA,EAAY,kBAAA;AAAA,EACZ,OAAA,EAAS,kBAAA;AAAA,EACT,WAAA,EAAa,kBAAA;AAAA;AAAA,EAEb,YAAA,EAAc,cAAA;AAAA,EACd,GAAA,EAAK,cAAA;AAAA,EACL,QAAA,EAAU,cAAA;AAAA,EACV,aAAA,EAAe,cAAA;AAAA,EACf,aAAA,EAAe,cAAA;AAAA,EACf,cAAA,EAAgB,cAAA;AAAA,EAChB,GAAA,EAAK,cAAA;AAAA,EACL,WAAA,EAAa,cAAA;AAAA,EACb,cAAA,EAAgB,cAAA;AAAA,EAChB,YAAA,EAAc,cAAA;AAAA,EACd,sBAAA,EAAwB,cAAA;AAAA;AAAA,EAExB,cAAA,EAAgB,gBAAA;AAAA,EAChB,cAAA,EAAgB,gBAAA;AAAA,EAChB,iBAAA,EAAmB,gBAAA;AAAA,EACnB,aAAA,EAAe,gBAAA;AAAA;AAAA;AAAA,EAGf,aAAA,EAAe,eAAA;AAAA,EACf,QAAA,EAAU,eAAA;AAAA,EACV,eAAA,EAAiB,eAAA;AAAA;AAAA,EAEjB,iBAAA,EAAmB,gBAAA;AAAA,EACnB,cAAA,EAAgB,gBAAA;AAAA,EAChB,aAAA,EAAe,gBAAA;AAAA,EACf,SAAA,EAAW,gBAAA;AAAA,EACX,SAAA,EAAW,gBAAA;AAAA,EACX,YAAA,EAAc,gBAAA;AAAA,EACd,WAAA,EAAa,gBAAA;AAAA,EACb,eAAA,EAAiB,gBAAA;AAAA;AAAA,EAEjB,cAAA,EAAgB,gBAAA;AAAA,EAChB,iBAAA,EAAmB,gBAAA;AAAA;AAAA,EAEnB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,SAAA,EAAW,oBAAA;AAAA,EACX,eAAA,EAAiB,oBAAA;AAAA;AAAA,EAEjB,oBAAA,EAAsB,sBAAA;AAAA;AAAA,EAEtB,qBAAA,EAAuB,uBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvB,iBAAA,EAAmB,mBAAA;AAAA;AAAA,EAEnB,UAAA,EAAY,YAAA;AAAA,EACZ,mBAAA,EAAqB,qBAAA;AAAA,EACrB,UAAA,EAAY,qBAAA;AAAA,EACZ,OAAA,EAAS;AACX,CAAA;AAKA,IAAM,eAAA,GAA0C;AAAA,EAC9C,gBAAA,EACE,8EAAA;AAAA,EACF,SAAA,EACE,iFAAA;AAAA,EACF,kBAAA,EACE,gFAAA;AAAA,EACF,iBAAA,EACE,2DAAA;AAAA,EACF,aAAA,EAAe,yDAAA;AAAA,EACf,cAAA,EACE,0EAAA;AAAA,EACF,gBAAA,EACE,2EAAA;AAAA,EACF,YAAA,EAAc,4DAAA;AAAA,EACd,cAAA,EAAgB,8CAAA;AAAA,EAChB,aAAA,EACE,sEAAA;AAAA,EACF,QAAA,EACE,sEAAA;AAAA,EACF,iBAAA,EACE,6HAAA;AAAA,EACF,cAAA,EACE,gEAAA;AAAA,EACF,cAAA,EACE,+DAAA;AAAA,EACF,kBAAA,EACE,mDAAA;AAAA,EACF,oBAAA,EACE,yEAAA;AAAA,EACF,qBAAA,EACE,+DAAA;AAAA,EACF,UAAA,EACE,kEAAA;AAAA,EACF,mBAAA,EAAqB,+CAAA;AAAA,EACrB,OAAA,EAAS;AACX,CAAA;AAMA,IAAM,eAAA,GAA0C;AAAA,EAC9C,gBAAA,EAAkB,MAAA;AAAA,EAClB,SAAA,EAAW,MAAA;AAAA,EACX,kBAAA,EAAoB,MAAA;AAAA,EACpB,iBAAA,EAAmB,MAAA;AAAA,EACnB,aAAA,EAAe,UAAA;AAAA,EACf,cAAA,EAAgB,MAAA;AAAA,EAChB,gBAAA,EAAkB,UAAA;AAAA,EAClB,YAAA,EAAc,MAAA;AAAA,EACd,cAAA,EAAgB,QAAA;AAAA,EAChB,aAAA,EAAe,QAAA;AAAA,EACf,QAAA,EAAU,QAAA;AAAA,EACV,iBAAA,EAAmB,MAAA;AAAA,EACnB,cAAA,EAAgB,UAAA;AAAA,EAChB,cAAA,EAAgB,MAAA;AAAA,EAChB,kBAAA,EAAoB,QAAA;AAAA,EACpB,oBAAA,EAAsB,UAAA;AAAA,EACtB,qBAAA,EAAuB,UAAA;AAAA,EACvB,UAAA,EAAY,QAAA;AAAA,EACZ,mBAAA,EAAqB,KAAA;AAAA,EACrB,OAAA,EAAS;AACX,CAAA;AAKA,IAAM,eAAA,uBAAsB,GAAA,CAAI;AAAA,EAC9B,aAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAKM,SAAS,oBAAoB,OAAA,EAA0B;AAC5D,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,SAAA;AAAA,EACT;AACA,EAAA,MAAM,aAAa,OAAA,CAAQ,WAAA,EAAY,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AAC1D,EAAA,IAAI,eAAA,CAAgB,UAAU,CAAA,EAAG;AAC/B,IAAA,OAAO,gBAAgB,UAAU,CAAA;AAAA,EACnC;AAOA,EAAA,IAAI,UAAA,CAAW,UAAA,CAAW,aAAa,CAAA,EAAG;AACxC,IAAA,OAAO,mBAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA;AACT;AAQO,SAAS,cAAA,CACd,YACA,WAAA,EACQ;AACR,EAAA,MAAM,eAAA,uBAAsB,GAAA,CAAI,CAAC,YAAY,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAC,CAAA;AACrE,EAAA,IAAI,eAAe,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,WAAA,EAAa,CAAA,EAAG;AACjE,IAAA,OAAO,YAAY,WAAA,EAAY;AAAA,EACjC;AACA,EAAA,OAAO,eAAA,CAAgB,UAAU,CAAA,IAAK,QAAA;AACxC;AAOO,SAAS,iBAAiB,KAAA,EAAwB;AACvD,EAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,SAAS,GAAA,EAAK;AAChB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,SAAS,GAAA,EAAK;AAChB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAQA,IAAM,2BAAA,GAA8B;AAAA,EAClC,QAAA;AAAA,EACA,aAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA;AAQO,SAAS,kBAAkB,GAAA,EAAoC;AACpE,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAAS,GAAA;AACf,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC3C,IAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,CAAI,CAAC,CAAA,EAAG;AAC3B,MAAA,GAAA,CAAI,CAAC,CAAA,GAAI,CAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAYO,SAAS,qBAAqB,GAAA,EAA6B;AAChE,EAAA,MAAM,IAAA,GAAO,IAAI,IAAA,KAAS,KAAA;AAC1B,EAAA,MAAM,MAAA,GAAqB,EAAE,IAAA,EAAK;AAKlC,EAAA,KAAA,MAAW,SAAS,2BAAA,EAA6B;AAC/C,IAAA,MAAM,KAAA,GAAS,IAAgC,KAAK,CAAA;AACpD,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,MAAC,MAAA,CAAmC,KAAK,CAAA,GAAI,KAAA;AAAA,IAC/C;AAAA,EACF;AAMA,EAAA,MAAM,gBAAgB,GAAA,CAAI,UAAA;AAC1B,EAAA,IAAI,MAAM,OAAA,CAAQ,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5D,IAAA,MAAM,OAAA,GAAU,aAAA,CACb,GAAA,CAAI,CAAC,CAAA,KAAM,iBAAA,CAAkB,CAAC,CAAC,CAAA,CAC/B,MAAA,CAAO,CAAC,CAAA,KAA0B,MAAM,IAAI,CAAA;AAC/C,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,MAAA,CAAO,UAAA,GAAa,OAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,IAAI,IAAA,EAAM;AAER,IAAA,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,IAAU,EAAA;AAC9B,IAAA,OAAO,MAAA;AAAA,EACT;AAOA,EAAA,IAAI,gBAAgB,GAAA,CAAI,WAAA;AACxB,EAAA,IAAI,CAAC,iBAAiB,KAAA,CAAM,OAAA,CAAQ,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC9E,IAAA,MAAM,KAAA,GAAQ,cAAc,CAAC,CAAA;AAC7B,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/D,MAAA,MAAM,cAAe,KAAA,CAAkC,WAAA;AACvD,MAAA,IAAI,OAAO,gBAAgB,QAAA,EAAU;AACnC,QAAA,aAAA,GAAgB,WAAA;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAa,oBAAoB,aAAa,CAAA;AACpD,EAAA,MAAM,UAAA,GAAa,gBAAA;AAAA,IACjB,OAAO,GAAA,CAAI,UAAA,KAAe,QAAA,GAAW,IAAI,UAAA,GAAa;AAAA,GACxD;AACA,EAAA,MAAM,QAAA,GAAW,cAAA;AAAA,IACf,UAAA;AAAA,IACA,OAAO,GAAA,CAAI,QAAA,KAAa,QAAA,GAAW,IAAI,QAAA,GAAW;AAAA,GACpD;AACA,EAAA,MAAM,QAAA,GACJ,eAAA,CAAgB,UAAU,CAAA,IAAK,gBAAgB,SAAS,CAAA;AAE1D,EAAA,MAAA,CAAO,WAAA,GAAc,UAAA;AACrB,EAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,EAAA,MAAA,CAAO,UAAA,GAAa,UAAA;AACpB,EAAA,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,IAAU,QAAA;AAC9B,EAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,EAAA,OAAO,MAAA;AACT;;;AClYO,IAAM,OAAA,GAAU,OAAA;AC+HhB,SAAS,eAAe,MAAA,EAA4B;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,UAAU,IAAA,EAAK;AAC9C;AAwCA,SAAS,YAAA,GAAuB;AAC9B,EAAA,OAAO,sCAAA,CAAuC,OAAA,CAAQ,OAAA,EAAS,CAAC,CAAA,KAAM;AACpE,IAAA,MAAM,CAAA,GAAK,IAAA,CAAK,MAAA,EAAO,GAAI,EAAA,GAAM,CAAA;AACjC,IAAA,MAAM,CAAA,GAAI,CAAA,KAAM,GAAA,GAAM,CAAA,GAAK,IAAI,CAAA,GAAO,CAAA;AACtC,IAAA,OAAO,CAAA,CAAE,SAAS,EAAE,CAAA;AAAA,EACtB,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CACd,cACA,SAAA,EACwB;AACxB,EAAA,OAAO;AAAA,IACL,aAAA,EAAe,UAAU,YAAY,CAAA,CAAA;AAAA,IACrC,cAAA,EAAgB,kBAAA;AAAA,IAChB,cAAA,EAAgB,QAAA;AAAA,IAChB,sBAAA,EAAwB,OAAA;AAAA,IACxB,qBAAA,EAAoC,YAAA;AAAa,GACnD;AACF;AA8HO,SAAS,UAAU,OAAA,EAAwD;AAChF,EAAA,MAAM,SAAU,OAAA,CAAoC,MAAA;AACpD,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,SAAS,CAAA,EAAG;AAEnD,IAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,MAAA,EAAQ,OAAO,KAAA;AAEpD,IAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,kBAAA,EAAoB,OAAO,IAAA;AAAA,EAMlE;AACA,EAAA,MAAM,OAAQ,OAAA,CAAoC,IAAA;AAClD,EAAA,IAAI,OAAO,SAAS,SAAA,EAAW;AAC7B,IAAA,OAAO,CAAC,IAAA;AAAA,EACV;AACA,EAAA,OAAO,KAAA;AACT;AAMO,SAAS,kBAAA,CAAmB,QAAoB,MAAA,EAA4B;AACjF,EAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,EAAA,IAAK,MAAA,CAAe,cAAc,OAAO,MAAA;AACzC,EAAA,OAAO;AAAA,IACL,GAAG,MAAA;AAAA,IACH,KAAA,EAAO;AAAA,GACT;AACF;;;AC1UA,SAAS,WAAW,OAAA,EAAuB;AACzC,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,cAAA,EAAO,OAAO,CAAA,CAAE,CAAA;AAC/B;AA4DO,IAAM,kBAAN,MAAsB;AAAA,EAU3B,WAAA,CAAY,OAAA,GAAkC,EAAC,EAAG;AAGhD,IAAA,MAAM,eAAA,GAAkB,SAAA,CAAQ,mBAAmB,CAAA,CAAE,OAAA;AAErD,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,eAAA,CAAgB;AAAA,MACpC,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,GAAG,OAAA,CAAQ;AAAA,KACZ,CAAA;AACD,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,cAAA,IAAkB,gBAAA,EAAkB,OAAA,CAAQ,OAAO,EAAE,CAAA;AACrF,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,YAAA,IAAgB,EAAA;AAC7C,IAAA,IAAA,CAAK,SAAA,GACH,OAAO,OAAA,CAAQ,QAAA,KAAa,WACvB,OAAA,CAAQ,QAAA,GACT,QAAQ,QAAA,IAAY,iBAAA;AAC1B,IAAA,IAAA,CAAK,YAAA,GAAe,QAAQ,WAAA,IAAe,oBAAA;AAK3C,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA,OAAA,CAAQ,KAAK,mFAA8E,CAAA;AAC3F,MAAA,OAAA,CAAQ,KAAK,+FAA+F,CAAA;AAAA,IAC9G;AAGA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAI,iBAAA,CAAkB,IAAI,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,QAAA,EAAsC;AACxD,IAAA,MAAM,eAAyB,EAAC;AAEhC,IAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,MAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AACvB,QAAA,IAAI,OAAO,GAAA,CAAI,OAAA,KAAY,QAAA,EAAU;AACnC,UAAA,YAAA,CAAa,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,QAC/B,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AACrC,UAAA,KAAA,MAAW,KAAA,IAAS,IAAI,OAAA,EAAS;AAC/B,YAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,KAAA,CAAM,IAAA,EAAM;AACvC,cAAA,YAAA,CAAa,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cAAc,QAAA,EAAmD;AACrE,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,mBAAA,CAAoB,QAAQ,CAAA;AAErD,IAAA,IAAI,CAAC,WAAA,CAAY,IAAA,EAAK,EAAG;AACvB,MAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,yBAAA,EAA0B;AAAA,IACzD;AAGA,IAAA,OAAO,IAAA,CAAK,YAAY,WAAW,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,YAAY,MAAA,EAAqC;AAC7D,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,YAAY,CAAA;AAExE,IAAA,IAAI;AACF,MAAA,MAAM,WAAW,MAAM,KAAA,CAAM,CAAA,EAAG,IAAA,CAAK,eAAe,CAAA,iBAAA,CAAA,EAAqB;AAAA,QACvE,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,cAAA,CAAe,IAAA,CAAK,aAAa,CAAA;AAAA,QAC1C,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,MAAA;AAAA,UACA,OAAA,EAAS;AAAA,YACP,YAAY,YAAA,EAAa;AAAA,YACzB,UAAU,UAAA,EAAW;AAAA,YACrB,kBAAA,EAAoB;AAAA;AACtB,SACD,CAAA;AAAA,QACD,QAAQ,UAAA,CAAW;AAAA,OACpB,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AACpC,UAAA,OAAO,cAAA,CAAe,CAAA,gBAAA,EAAmB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,QAC5D;AACA,QAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,yBAAA,EAA4B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MACzE;AAEA,MAAA,OAAO,kBAAA,CAAmB,qBAAsB,MAAM,QAAA,CAAS,MAAqB,CAAA,EAAG,KAAK,aAAa,CAAA;AAAA,IAC3G,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,eAAA,EAAiB;AACpC,QAAA,MAAM,KAAA;AAAA,MACR;AAEA,MAAA,MAAM,eAAe,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAE1E,MAAA,IAAI,YAAA,CAAa,QAAA,CAAS,OAAO,CAAA,EAAG;AAClC,QAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AAEpC,UAAA,UAAA,CAAW,yDAAyD,CAAA;AACpE,UAAA,OAAO,eAAe,4BAA4B,CAAA;AAAA,QACpD;AACA,QAAA,MAAM,IAAI,gBAAgB,kDAAkD,CAAA;AAAA,MAC9E;AAEA,MAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AACpC,QAAA,OAAO,cAAA,CAAe,CAAA,YAAA,EAAe,YAAY,CAAA,CAAE,CAAA;AAAA,MACrD;AACA,MAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,aAAA,EAAgB,YAAY,CAAA,CAAE,CAAA;AAAA,IAC1D,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,SAAS,CAAA;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,SAAA,GAAuB;AACzB,IAAA,OAAO,IAAA,CAAK,UAAA;AAAA,EACd;AACF;AAKA,IAAM,oBAAN,MAAwB;AAAA,EAGtB,YAAY,MAAA,EAAyB;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,MAAA,EAA+C;AAE1D,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,OAAA,CAAQ,aAAA;AAAA,MACpC,MAAA,CAAO;AAAA,KACT;AAGA,IAAA,IAAI,SAAA,CAAU,UAAU,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,kBAAA;AAAA,QACR,CAAA,iBAAA,EAAoB,UAAA,CAAW,MAAA,IAAU,0BAA0B,CAAA,CAAA;AAAA,QACnE,UAAA,CAAW,WAAA;AAAA,QACX,UAAA,CAAW,UAAA;AAAA,QACX,UAAA,CAAW,cAAc;AAAC,OAC5B;AAAA,IACF;AAGA,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,CAAU,QAAA,CAAS,MAAA,CAAO;AAAA,MAC5C,GAAG,MAAA;AAAA,MACH,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AACF,CAAA","file":"index.js","sourcesContent":["/**\n * Configuration constants and types for the Shrike Guard SDK.\n */\n\nimport { randomUUID } from 'node:crypto';\n\n/**\n * Defines behavior when scan operations fail.\n */\nexport enum FailMode {\n /**\n * Allow the request to proceed (fail-open). Use this mode when\n * availability is prioritized over strict security.\n */\n OPEN = 'open',\n\n /**\n * Block the request and raise an exception (fail-closed). This is the\n * default behavior — matching the MCP server's security posture. Use\n * fail-open only when you've explicitly decided availability matters more.\n */\n CLOSED = 'closed',\n}\n\n/** Default timeout for scan requests in milliseconds */\nexport const DEFAULT_SCAN_TIMEOUT = 10000;\n\n/** Default fail mode - block requests when scan fails (fail-closed) */\nexport const DEFAULT_FAIL_MODE = FailMode.CLOSED;\n\n/** Default Shrike API endpoint (uses load balancer for scalability) */\nexport const DEFAULT_ENDPOINT = 'https://api.shrikesecurity.com/agent';\n\n/** Default rate limit (requests per minute) */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE = 100;\n\n// Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n// No local patterns needed - backend handles all detection logic\n\n/** SDK identification */\nexport const SDK_NAME = 'typescript';\nexport const SDK_USER_AGENT = 'shrike-guard-typescript';\n\n/**\n * Session identity — persists for the lifetime of this SDK process.\n * Maps to L9's SessionCache key for multi-turn attack detection.\n */\nconst SESSION_ID = randomUUID();\nconst AGENT_ID = process.env.SHRIKE_AGENT_ID || `sdk-ts-${randomUUID().slice(0, 8)}`;\n\n/** Returns the stable session ID for this SDK process. */\nexport function getSessionId(): string {\n return SESSION_ID;\n}\n\n/** Returns the agent ID (from SHRIKE_AGENT_ID env or auto-generated). */\nexport function getAgentId(): string {\n return AGENT_ID;\n}\n\n/**\n * Approval information returned when a policy requires human sign-off.\n */\nexport interface ApprovalInfo {\n requires_approval: boolean;\n approval_id: string;\n approval_level: string;\n action_summary: string;\n policy_name: string;\n expires_in_seconds: number;\n threat_type?: string;\n severity?: string;\n owasp_category?: string;\n risk_factors?: string[];\n original_action?: string;\n}\n\n/**\n * Retry configuration for transient failure resilience.\n * Matches MCP server's RETRY_CONFIG for consistent behavior.\n */\nexport const RETRY_CONFIG = {\n maxRetries: 2,\n initialDelayMs: 200,\n maxDelayMs: 2000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'fetch failed'],\n};\n","/**\n * Custom exceptions for the Shrike Guard SDK.\n */\n\n/**\n * Base exception for all Shrike SDK errors.\n */\nexport class ShrikeError extends Error {\n public readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'ShrikeError';\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Raised when a scan operation fails and fail_mode is 'closed'.\n *\n * This exception is raised when:\n * - The Shrike API times out\n * - A network error occurs\n * - The API returns an unexpected error\n *\n * The default fail_mode is 'closed' (secure by default), so a scan\n * failure raises this exception. Set fail_mode='open' to instead\n * silently handle these errors and allow the request to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult, ScanViolation } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n // L8 tonality drift — backend normalizes profanity + hostile as toxic_content;\n // casual stays as jailbreak (matches platform/common/models/response.go:309-313).\n tonality_drift_profanity: 'toxic_content',\n tonality_drift_hostile: 'toxic_content',\n tonality_drift_casual: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxic content (canonical name as of the L7 rewrite). `toxicity` kept as a\n // legacy alias so older backend builds + customer code don't break.\n toxic_content: 'toxic_content',\n toxicity: 'toxic_content',\n harmful_content: 'toxic_content',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // L9 multi-turn correlation — the pseudo-category emitted when any\n // multi_turn_* pattern fires (crescendo, blocked_retry, trust-building,\n // memory_poisoning, etc.). The normalizeThreatType prefix check below\n // handles the specific pattern names; this entry catches the pseudo\n // value if the backend pre-normalizes.\n multi_turn_attack: 'multi_turn_attack',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxic_content:\n 'This content contains potentially harmful or inappropriate language.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n multi_turn_attack:\n 'A pattern was detected across multiple turns of this session that suggests a coordinated attempt to bypass safety controls.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxic_content: 'medium',\n toxicity: 'medium',\n multi_turn_attack: 'high',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n if (THREAT_TYPE_MAP[normalized]) {\n return THREAT_TYPE_MAP[normalized];\n }\n // L9 multi-turn correlation patterns flow as `multi_turn_<pattern>`\n // (crescendo, blocked_retry, topic_pivot, threat_diversity, safe_then_unsafe,\n // tool_sequence_anomaly, coded_language_setup, context_overflow,\n // memory_poisoning, velocity_burst). Backend collapses these to\n // multi_turn_attack — mirror the prefix logic from\n // platform/common/models/response.go:380.\n if (normalized.startsWith('multi_turn_')) {\n return 'multi_turn_attack';\n }\n return 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Governance-outcome fields the sanitizer preserves verbatim on BOTH safe\n * and unsafe branches. These are OUTCOME state (customer sees outcomes;\n * timings/attribution stay provider-only) — the contract-symmetry surface\n * (safe/refuse_tier/recovery/session_state on every response).\n */\nconst PRESERVED_GOVERNANCE_FIELDS = [\n 'action',\n 'refuse_tier',\n 'recovery',\n 'session_state',\n 'content_type',\n 'approval_info',\n 'client_session_rotation',\n] as const;\n\n/**\n * Sanitize one entry from the backend violations[] array.\n * Preserves customer-visible outcome fields (severity, action, threat_type,\n * owasp_category, user_message, suggested_action); drops attribution\n * fields (policy_id, policy_name, matched_pattern, ai_reasoning, etc.).\n */\nexport function sanitizeViolation(raw: unknown): ScanViolation | null {\n if (!raw || typeof raw !== 'object') {\n return null;\n }\n const source = raw as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(source)) {\n if (!INTERNAL_FIELDS.has(k)) {\n out[k] = v;\n }\n }\n return out as ScanViolation;\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection attribution (layer timings, per-detector\n * confidences, pattern names, policy IDs) — NOT outcome state.\n * Preserves the four-state governance surface (`action`, `refuse_tier`,\n * `recovery`, `session_state`) so callers can distinguish\n * allow/warn/require_approval/block. Contract symmetry — safe and refuse\n * verdicts carry the same governance fields.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n const result: ScanResult = { safe };\n\n // Governance-outcome fields pass through on BOTH branches. This is the\n // symmetric contract — a `warn` verdict (safe=true, action=\"warn\") must\n // carry recovery/refuse_tier the same as a `block` (safe=false).\n for (const field of PRESERVED_GOVERNANCE_FIELDS) {\n const value = (raw as Record<string, unknown>)[field];\n if (value !== undefined && value !== null) {\n (result as Record<string, unknown>)[field] = value;\n }\n }\n\n // violations[] is a mixed shape — pass each entry through per-item\n // sanitization to drop policy_id / policy_name / matched_pattern while\n // keeping customer-visible severity, threat_type, owasp_category,\n // user_message, suggested_action. Empty array is dropped (noise).\n const rawViolations = raw.violations;\n if (Array.isArray(rawViolations) && rawViolations.length > 0) {\n const cleaned = rawViolations\n .map((v) => sanitizeViolation(v))\n .filter((v): v is ScanViolation => v !== null);\n if (cleaned.length > 0) {\n result.violations = cleaned;\n }\n }\n\n if (safe) {\n // Safe branch: reason may carry advisory copy for warn tier.\n result.reason = raw.reason || '';\n return result;\n }\n\n // Unsafe branch: derive threat classification for legacy callers that\n // read top-level threat_type/severity/guidance. Backend /api/scan/enforce\n // returns top-level threat_type=null and puts detail inside violations[];\n // for those responses, prefer the first violation's threat_type when\n // the raw top-level field is missing.\n let rawThreatType = raw.threat_type;\n if (!rawThreatType && Array.isArray(rawViolations) && rawViolations.length > 0) {\n const first = rawViolations[0];\n if (first && typeof first === 'object' && !Array.isArray(first)) {\n const firstThreat = (first as Record<string, unknown>).threat_type;\n if (typeof firstThreat === 'string') {\n rawThreatType = firstThreat;\n }\n }\n }\n\n const threatType = normalizeThreatType(rawThreatType);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n result.threat_type = threatType;\n result.severity = severity;\n result.confidence = confidence;\n result.reason = raw.reason || guidance;\n result.guidance = guidance;\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","/**\n * SDK version - keep in sync with package.json\n */\nexport const VERSION = '1.5.0';\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { AUTO_CHUNK_THRESHOLD, aggregateChunkResults, chunkContent } from './chunker';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\n/**\n * Governance action per Cooperative Governance 4-state contract (allow / warn / require_approval / block).\n * The `action` field is the server-authoritative signal for proceed-vs-refuse;\n * `isBlocked()` in this module consumes it directly.\n */\nexport type ScanAction = 'allow' | 'warn' | 'require_approval' | 'block';\n\n/**\n * Recovery guidance emitted by the backend on refuse verdicts.\n * `available_tools` names the MCP tools the caller can still invoke while\n * under quarantine (typically read-only surfaces like session_status).\n */\nexport interface ScanRecovery {\n instruction?: string;\n available_tools?: string[];\n patterns_triggered?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Session-level state carried on every scan response (contract symmetry).\n * Fields are outcome state per the dashboard IP boundary — customer-visible.\n */\nexport interface ScanSessionState {\n session_risk_score?: number;\n session_turn_number?: number;\n session_patterns?: string[];\n session_locked?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * A single policy violation as emitted by the /api/scan/enforce endpoint.\n * The sanitizer strips policy_id / policy_name / matched_pattern / other\n * attribution before this reaches callers; what remains here is the\n * customer-visible outcome surface.\n */\nexport interface ScanViolation {\n threat_type?: string;\n owasp_category?: string;\n severity?: string;\n action?: string;\n user_message?: string;\n suggested_action?: string;\n [key: string]: unknown;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: ScanViolation[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** Cooperative Governance action — server-authoritative proceed/refuse signal */\n action?: ScanAction;\n /** Refuse tier: allow / warn / require_approval / block */\n refuse_tier?: ScanAction;\n /** Recovery guidance, present on refuse verdicts */\n recovery?: ScanRecovery;\n /** L9 session correlation state — outcome, customer-visible */\n session_state?: ScanSessionState;\n /** Specialized scan input type, e.g. 'sql' / 'file_path' / 'a2a_message' */\n content_type?: string;\n /** Client-side session rotation guidance */\n client_session_rotation?: unknown;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n /**\n * True when this is a fail-open verdict returned without a completed scan —\n * i.e. `failMode: 'open'` was set and the backend was unreachable, so\n * enforcement was skipped. Lets a caller distinguish \"scanned and clean\"\n * from \"not scanned, allowed anyway.\"\n */\n degraded?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * Build a fail-open ALLOW verdict. Returned only when `failMode === 'open'` is\n * explicitly set and the scan could not complete (timeout, backend error).\n * Marked `degraded: true` — centralize all fail-open returns through this so the\n * marker can never be forgotten on a new wrapper.\n */\nexport function failOpenResult(reason: string): ScanResult {\n return { safe: true, reason, degraded: true };\n}\n\n/**\n * Result of ScanClient.declareScope — the persisted scope row plus derived\n * active_until + expired fields. Mirrors the shape returned by\n * POST /api/v1/agent/scope/declare on the backend.\n */\nexport interface DeclareScopeResult {\n scope_id?: string;\n agent_id?: string;\n purpose?: string;\n allowed_tools?: string[];\n forbidden_tools?: string[];\n max_duration_seconds?: number;\n expires_at?: string;\n active_until?: string;\n expired?: boolean;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\n/**\n * Decide whether a scan verdict should be enforced as a block.\n *\n * Prefers the server-authoritative `action` field emitted by\n * /api/scan/enforce. Falls back to the legacy\n * `safe` boolean for verdicts that predate the enforce endpoint (older\n * backends, circuit-breaker fail-open synthetic verdicts, cached responses).\n *\n * Behavior:\n * - action = \"block\" → true\n * - action = \"require_approval\" → true (held actions are refused at the\n * tool-call boundary; caller can re-issue\n * after approval lands)\n * - action = \"allow\" → false\n * - action = \"warn\" → false (advisory; caller should surface\n * via block-feedback but not refuse)\n * - action absent → fall back to `!safe`\n *\n * This helper is the ONLY place the SDK converts a verdict into a\n * proceed-vs-refuse decision. Every LLM wrapper (OpenAI, Anthropic, Gemini)\n * routes through it so behavior stays uniform.\n *\n * Contract-symmetry pin: mirrors Python `_is_blocked` in shrike_guard.scanner.\n * If either SDK diverges, that is a bug in one of the two.\n */\nexport function isBlocked(verdict: ScanResult | Record<string, unknown>): boolean {\n const action = (verdict as Record<string, unknown>).action;\n if (typeof action === 'string' && action.length > 0) {\n // Known non-blocking tiers proceed.\n if (action === 'allow' || action === 'warn') return false;\n // Known blocking tiers refuse.\n if (action === 'block' || action === 'require_approval') return true;\n // Unknown/future tier (e.g. a new \"quarantine\" the backend adds): do NOT\n // fail open on an unrecognized name — fall through to `safe`. A new\n // blocking tier also sets safe:false, so this fails closed until the\n // client learns the tier explicitly. Forward-compat contract, see\n // tests/unit/contract-forward-compat.test.ts.\n }\n const safe = (verdict as Record<string, unknown>).safe;\n if (typeof safe === 'boolean') {\n return !safe;\n }\n return false;\n}\n\n/**\n * When running without an API key, appends a signup hint to scan results\n * so agents/users know they can upgrade from regex-only to full scanning.\n */\nexport function maybeAddSignupHint(result: ScanResult, apiKey: string): ScanResult {\n if (apiKey) return result;\n // Don't override if backend already provided upgrade_hint\n if ((result as any).upgrade_hint) return result;\n return {\n ...result,\n _note: 'Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup',\n };\n}\n\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n\n if (!this.apiKey) {\n console.warn('[shrike-guard] No API key provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n // Auto-chunk large prompts. Below the threshold we keep the single-shot\n // path unchanged; above it we split on natural boundaries, scan each\n // chunk sequentially, and stop on the first block verdict. The\n // conversation_history context is NOT chunked — it rides on every scan\n // so downstream layers see the same session shape.\n if (prompt.length > AUTO_CHUNK_THRESHOLD) {\n return this.scanChunked(prompt, context);\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Chunk-and-scan path for prompts above AUTO_CHUNK_THRESHOLD. Splits on\n * natural boundaries, scans sequentially, aggregates. Stops on the first\n * block verdict (fail-fast) so a compromised chunk doesn't pay for the\n * remaining scans.\n *\n * Known limit: same session ID rides every chunk, so L9 turn count\n * inflates by chunk-count. Fix post-launch via backend chunk_group field.\n */\n private async scanChunked(prompt: string, context?: string): Promise<ScanResult> {\n const chunks = chunkContent(prompt);\n const results: ScanResult[] = [];\n for (const chunk of chunks) {\n const chunkResult = await this.scan(chunk, context);\n results.push(chunkResult);\n const action = chunkResult.action || chunkResult.refuse_tier;\n if (action === 'block') {\n break;\n }\n }\n return maybeAddSignupHint(aggregateChunkResults(results), this.apiKey);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Declare (or refresh) the operating scope for a task-scoped agent.\n *\n * Once declared, every subsequent scan for the same agent_id is enforced\n * against the scope on the backend. Tool calls outside allowed_tools —\n * or explicitly on forbidden_tools — route to refuse_tier:\n * \"require_approval\" with threat_type: \"scope_violation\". Expired scopes\n * emit threat_type: \"scope_expired\". Absent a declaration, no scope\n * check runs; every existing SDK integration is unaffected until it\n * opts in.\n *\n * Wire contract mirrors ScanClient.declare_scope on the Python SDK and\n * scan_declare_scope on the shrike-mcp package — do not diverge without\n * updating those siblings.\n *\n * @param options.agentId - Agent identity this scope applies to.\n * @param options.allowedTools - Exact tool names permitted; [\"*\"] = any.\n * @param options.forbiddenTools - Optional. Wins over allowedTools.\n * @param options.purpose - Optional audit + dashboard label.\n * @param options.maxDurationSeconds - Optional TTL from created_at.\n * @param options.expiresAt - Optional ISO-8601 absolute expiry.\n * @returns The persisted scope row, including scope_id + active_until.\n */\n async declareScope(options: {\n agentId: string;\n allowedTools: string[];\n forbiddenTools?: string[];\n purpose?: string;\n maxDurationSeconds?: number;\n expiresAt?: string;\n }): Promise<DeclareScopeResult> {\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n agent_id: options.agentId,\n allowed_tools: options.allowedTools,\n };\n if (options.purpose !== undefined) payload.purpose = options.purpose;\n if (options.forbiddenTools !== undefined) payload.forbidden_tools = options.forbiddenTools;\n if (options.maxDurationSeconds !== undefined) {\n payload.max_duration_seconds = options.maxDurationSeconds;\n }\n if (options.expiresAt !== undefined) payload.expires_at = options.expiresAt;\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/v1/agent/scope/declare`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `declareScope failed: ${response.status}${text ? ` — ${text.trim()}` : ''}`\n );\n }\n\n return (await response.json()) as DeclareScopeResult;\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, isBlocked, maybeAddSignupHint, ScanResult, failOpenResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n if (!this._shrikeApiKey) {\n console.warn('[shrike-guard] No shrikeApiKey provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L4 (regex, unicode, encoding, token normalization)\n * - Pro tier: L1-L8 (full scan including LLM)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan API error: ${response.status}`);\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this._shrikeApiKey);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return failOpenResult('Scan timeout, failing open');\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan error: ${errorMessage}`);\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (isBlocked(scanResult)) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
|
package/dist/anthropic/index.mjs
CHANGED
|
@@ -72,9 +72,11 @@ var THREAT_TYPE_MAP = {
|
|
|
72
72
|
completion_baiting: "jailbreak",
|
|
73
73
|
override: "jailbreak",
|
|
74
74
|
manipulate: "jailbreak",
|
|
75
|
-
|
|
75
|
+
// L8 tonality drift — backend normalizes profanity + hostile as toxic_content;
|
|
76
|
+
// casual stays as jailbreak (matches platform/common/models/response.go:309-313).
|
|
77
|
+
tonality_drift_profanity: "toxic_content",
|
|
78
|
+
tonality_drift_hostile: "toxic_content",
|
|
76
79
|
tonality_drift_casual: "jailbreak",
|
|
77
|
-
tonality_drift_hostile: "jailbreak",
|
|
78
80
|
// System prompt leak
|
|
79
81
|
system_prompt_leak: "system_prompt_leak",
|
|
80
82
|
system_prompt_extraction: "system_prompt_leak",
|
|
@@ -130,9 +132,11 @@ var THREAT_TYPE_MAP = {
|
|
|
130
132
|
suspicious_tld: "blocked_domain",
|
|
131
133
|
suspicious_domain: "blocked_domain",
|
|
132
134
|
malicious_url: "blocked_domain",
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
// Toxic content (canonical name as of the L7 rewrite). `toxicity` kept as a
|
|
136
|
+
// legacy alias so older backend builds + customer code don't break.
|
|
137
|
+
toxic_content: "toxic_content",
|
|
138
|
+
toxicity: "toxic_content",
|
|
139
|
+
harmful_content: "toxic_content",
|
|
136
140
|
// Malicious code
|
|
137
141
|
malicious_content: "malicious_code",
|
|
138
142
|
malicious_code: "malicious_code",
|
|
@@ -153,6 +157,12 @@ var THREAT_TYPE_MAP = {
|
|
|
153
157
|
privilege_escalation: "privilege_escalation",
|
|
154
158
|
// Destructive operation
|
|
155
159
|
destructive_operation: "destructive_operation",
|
|
160
|
+
// L9 multi-turn correlation — the pseudo-category emitted when any
|
|
161
|
+
// multi_turn_* pattern fires (crescendo, blocked_retry, trust-building,
|
|
162
|
+
// memory_poisoning, etc.). The normalizeThreatType prefix check below
|
|
163
|
+
// handles the specific pattern names; this entry catches the pseudo
|
|
164
|
+
// value if the backend pre-normalizes.
|
|
165
|
+
multi_turn_attack: "multi_turn_attack",
|
|
156
166
|
// Errors
|
|
157
167
|
scan_error: "scan_error",
|
|
158
168
|
size_limit_exceeded: "size_limit_exceeded",
|
|
@@ -169,7 +179,9 @@ var THREAT_GUIDANCE = {
|
|
|
169
179
|
secrets_exposure: "This content contains patterns matching API keys, tokens, or credentials.",
|
|
170
180
|
pii_exposure: "This content contains personally identifiable information.",
|
|
171
181
|
blocked_domain: "This web search targets a restricted domain.",
|
|
182
|
+
toxic_content: "This content contains potentially harmful or inappropriate language.",
|
|
172
183
|
toxicity: "This content contains potentially harmful or inappropriate language.",
|
|
184
|
+
multi_turn_attack: "A pattern was detected across multiple turns of this session that suggests a coordinated attempt to bypass safety controls.",
|
|
173
185
|
malicious_code: "This content contains patterns associated with malicious code.",
|
|
174
186
|
harmful_intent: "This request contains content associated with harmful intent.",
|
|
175
187
|
social_engineering: "This prompt contains social engineering patterns.",
|
|
@@ -189,7 +201,9 @@ var THREAT_SEVERITY = {
|
|
|
189
201
|
secrets_exposure: "critical",
|
|
190
202
|
pii_exposure: "high",
|
|
191
203
|
blocked_domain: "medium",
|
|
204
|
+
toxic_content: "medium",
|
|
192
205
|
toxicity: "medium",
|
|
206
|
+
multi_turn_attack: "high",
|
|
193
207
|
malicious_code: "critical",
|
|
194
208
|
harmful_intent: "high",
|
|
195
209
|
social_engineering: "medium",
|
|
@@ -199,12 +213,31 @@ var THREAT_SEVERITY = {
|
|
|
199
213
|
size_limit_exceeded: "low",
|
|
200
214
|
unknown: "medium"
|
|
201
215
|
};
|
|
216
|
+
var INTERNAL_FIELDS = /* @__PURE__ */ new Set([
|
|
217
|
+
"detected_by",
|
|
218
|
+
"policy_id",
|
|
219
|
+
"policy_name",
|
|
220
|
+
"matched_pattern",
|
|
221
|
+
"matched_text",
|
|
222
|
+
"pattern",
|
|
223
|
+
"scan_stage",
|
|
224
|
+
"ai_reasoning",
|
|
225
|
+
"llm_analysis",
|
|
226
|
+
"performance_metrics",
|
|
227
|
+
"performance"
|
|
228
|
+
]);
|
|
202
229
|
function normalizeThreatType(rawType) {
|
|
203
230
|
if (!rawType) {
|
|
204
231
|
return "unknown";
|
|
205
232
|
}
|
|
206
233
|
const normalized = rawType.toLowerCase().replace(/-/g, "_");
|
|
207
|
-
|
|
234
|
+
if (THREAT_TYPE_MAP[normalized]) {
|
|
235
|
+
return THREAT_TYPE_MAP[normalized];
|
|
236
|
+
}
|
|
237
|
+
if (normalized.startsWith("multi_turn_")) {
|
|
238
|
+
return "multi_turn_attack";
|
|
239
|
+
}
|
|
240
|
+
return "unknown";
|
|
208
241
|
}
|
|
209
242
|
function deriveSeverity(threatType, rawSeverity) {
|
|
210
243
|
const validSeverities = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
|
|
@@ -225,20 +258,59 @@ function bucketConfidence(score) {
|
|
|
225
258
|
}
|
|
226
259
|
return "low";
|
|
227
260
|
}
|
|
261
|
+
var PRESERVED_GOVERNANCE_FIELDS = [
|
|
262
|
+
"action",
|
|
263
|
+
"refuse_tier",
|
|
264
|
+
"recovery",
|
|
265
|
+
"session_state",
|
|
266
|
+
"content_type",
|
|
267
|
+
"approval_info",
|
|
268
|
+
"client_session_rotation"
|
|
269
|
+
];
|
|
270
|
+
function sanitizeViolation(raw) {
|
|
271
|
+
if (!raw || typeof raw !== "object") {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const source = raw;
|
|
275
|
+
const out = {};
|
|
276
|
+
for (const [k, v] of Object.entries(source)) {
|
|
277
|
+
if (!INTERNAL_FIELDS.has(k)) {
|
|
278
|
+
out[k] = v;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
228
283
|
function sanitizeScanResponse(raw) {
|
|
229
284
|
const safe = raw.safe !== false;
|
|
230
|
-
const
|
|
285
|
+
const result = { safe };
|
|
286
|
+
for (const field of PRESERVED_GOVERNANCE_FIELDS) {
|
|
287
|
+
const value = raw[field];
|
|
288
|
+
if (value !== void 0 && value !== null) {
|
|
289
|
+
result[field] = value;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const rawViolations = raw.violations;
|
|
293
|
+
if (Array.isArray(rawViolations) && rawViolations.length > 0) {
|
|
294
|
+
const cleaned = rawViolations.map((v) => sanitizeViolation(v)).filter((v) => v !== null);
|
|
295
|
+
if (cleaned.length > 0) {
|
|
296
|
+
result.violations = cleaned;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
231
299
|
if (safe) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
300
|
+
result.reason = raw.reason || "";
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
let rawThreatType = raw.threat_type;
|
|
304
|
+
if (!rawThreatType && Array.isArray(rawViolations) && rawViolations.length > 0) {
|
|
305
|
+
const first = rawViolations[0];
|
|
306
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
307
|
+
const firstThreat = first.threat_type;
|
|
308
|
+
if (typeof firstThreat === "string") {
|
|
309
|
+
rawThreatType = firstThreat;
|
|
310
|
+
}
|
|
238
311
|
}
|
|
239
|
-
return result2;
|
|
240
312
|
}
|
|
241
|
-
const threatType = normalizeThreatType(
|
|
313
|
+
const threatType = normalizeThreatType(rawThreatType);
|
|
242
314
|
const confidence = bucketConfidence(
|
|
243
315
|
typeof raw.confidence === "number" ? raw.confidence : void 0
|
|
244
316
|
);
|
|
@@ -247,22 +319,19 @@ function sanitizeScanResponse(raw) {
|
|
|
247
319
|
typeof raw.severity === "string" ? raw.severity : void 0
|
|
248
320
|
);
|
|
249
321
|
const guidance = THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE["unknown"];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
reason: raw.reason || guidance,
|
|
256
|
-
guidance
|
|
257
|
-
};
|
|
258
|
-
if (approvalInfo) {
|
|
259
|
-
result.approval_info = approvalInfo;
|
|
260
|
-
}
|
|
322
|
+
result.threat_type = threatType;
|
|
323
|
+
result.severity = severity;
|
|
324
|
+
result.confidence = confidence;
|
|
325
|
+
result.reason = raw.reason || guidance;
|
|
326
|
+
result.guidance = guidance;
|
|
261
327
|
return result;
|
|
262
328
|
}
|
|
263
329
|
|
|
264
330
|
// src/version.ts
|
|
265
|
-
var VERSION = "1.
|
|
331
|
+
var VERSION = "1.5.0";
|
|
332
|
+
function failOpenResult(reason) {
|
|
333
|
+
return { safe: true, reason, degraded: true };
|
|
334
|
+
}
|
|
266
335
|
function generateUUID() {
|
|
267
336
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
268
337
|
const r = Math.random() * 16 | 0;
|
|
@@ -279,6 +348,18 @@ function getScanHeaders(shrikeApiKey, requestId) {
|
|
|
279
348
|
"X-Shrike-Request-ID": generateUUID()
|
|
280
349
|
};
|
|
281
350
|
}
|
|
351
|
+
function isBlocked(verdict) {
|
|
352
|
+
const action = verdict.action;
|
|
353
|
+
if (typeof action === "string" && action.length > 0) {
|
|
354
|
+
if (action === "allow" || action === "warn") return false;
|
|
355
|
+
if (action === "block" || action === "require_approval") return true;
|
|
356
|
+
}
|
|
357
|
+
const safe = verdict.safe;
|
|
358
|
+
if (typeof safe === "boolean") {
|
|
359
|
+
return !safe;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
282
363
|
function maybeAddSignupHint(result, apiKey) {
|
|
283
364
|
if (apiKey) return result;
|
|
284
365
|
if (result.upgrade_hint) return result;
|
|
@@ -351,7 +432,7 @@ var ShrikeAnthropic = class {
|
|
|
351
432
|
const controller = new AbortController();
|
|
352
433
|
const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);
|
|
353
434
|
try {
|
|
354
|
-
const response = await fetch(`${this._shrikeEndpoint}/scan`, {
|
|
435
|
+
const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {
|
|
355
436
|
method: "POST",
|
|
356
437
|
headers: getScanHeaders(this._shrikeApiKey),
|
|
357
438
|
body: JSON.stringify({
|
|
@@ -366,7 +447,7 @@ var ShrikeAnthropic = class {
|
|
|
366
447
|
});
|
|
367
448
|
if (!response.ok) {
|
|
368
449
|
if (this._failMode === "open" /* OPEN */) {
|
|
369
|
-
return
|
|
450
|
+
return failOpenResult(`Scan API error: ${response.status}`);
|
|
370
451
|
}
|
|
371
452
|
throw new ShrikeScanError(`Scan API returned error: ${response.status}`);
|
|
372
453
|
}
|
|
@@ -379,12 +460,12 @@ var ShrikeAnthropic = class {
|
|
|
379
460
|
if (errorMessage.includes("abort")) {
|
|
380
461
|
if (this._failMode === "open" /* OPEN */) {
|
|
381
462
|
logWarning("Scan request timed out, failing open (allowing request)");
|
|
382
|
-
return
|
|
463
|
+
return failOpenResult("Scan timeout, failing open");
|
|
383
464
|
}
|
|
384
465
|
throw new ShrikeScanError("Scan request timed out and fail_mode is 'closed'");
|
|
385
466
|
}
|
|
386
467
|
if (this._failMode === "open" /* OPEN */) {
|
|
387
|
-
return
|
|
468
|
+
return failOpenResult(`Scan error: ${errorMessage}`);
|
|
388
469
|
}
|
|
389
470
|
throw new ShrikeScanError(`Scan failed: ${errorMessage}`);
|
|
390
471
|
} finally {
|
|
@@ -410,7 +491,7 @@ var MessagesNamespace = class {
|
|
|
410
491
|
const scanResult = await this._client._scanMessages(
|
|
411
492
|
params.messages
|
|
412
493
|
);
|
|
413
|
-
if (
|
|
494
|
+
if (isBlocked(scanResult)) {
|
|
414
495
|
throw new ShrikeBlockedError(
|
|
415
496
|
`Request blocked: ${scanResult.reason || "Security threat detected"}`,
|
|
416
497
|
scanResult.threat_type,
|