shrike-guard 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anthropic/index.d.mts +1 -1
- package/dist/anthropic/index.d.ts +1 -1
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.d.mts +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/gemini/index.d.mts +1 -1
- package/dist/gemini/index.d.ts +1 -1
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -0
- 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.map +1 -1
- package/dist/openai/index.mjs.map +1 -1
- package/dist/{scanner-CtVbh83-.d.mts → scanner-DcDJnMF-.d.mts} +21 -0
- package/dist/{scanner-CtVbh83-.d.ts → scanner-DcDJnMF-.d.ts} +21 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';
|
|
3
|
-
import { F as FailMode, S as ScanResult } from '../scanner-
|
|
3
|
+
import { F as FailMode, S as ScanResult } from '../scanner-DcDJnMF-.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';
|
|
3
|
-
import { F as FailMode, S as ScanResult } from '../scanner-
|
|
3
|
+
import { F as FailMode, S as ScanResult } from '../scanner-DcDJnMF-.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
|
@@ -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.0.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 */\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\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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, 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 // 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 sanitizeScanResponse((await response.json()) as ScanResult);\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","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.0.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 */\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\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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, 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 // 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 sanitizeScanResponse((await response.json()) as ScanResult);\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 +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":["result"],"mappings":"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","file":"index.mjs","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.0.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 */\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\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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, 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 // 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 sanitizeScanResponse((await response.json()) as ScanResult);\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":["result"],"mappings":"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","file":"index.mjs","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.0.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 */\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\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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 sanitizeScanResponse((await response.json()) as ScanResult);\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, 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 // 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 sanitizeScanResponse((await response.json()) as ScanResult);\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"]}
|
package/dist/api/index.d.mts
CHANGED
package/dist/api/index.d.ts
CHANGED