shrike-guard 4.0.3 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +41 -0
- package/dist/anthropic/index.d.mts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.js +3 -3
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs +3 -3
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +1 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/gemini/index.d.mts +16 -2
- package/dist/gemini/index.d.ts +16 -2
- package/dist/gemini/index.js +9 -4
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs +9 -4
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.d.mts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.js +3 -3
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/index.mjs +3 -3
- package/dist/openai/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.0.5] - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Custom endpoint for the Gemini wrapper (`baseUrl`).** `ShrikeGemini` now
|
|
7
|
+
accepts `baseUrl` (applied as `httpOptions.baseUrl` on the underlying
|
|
8
|
+
`@google/genai` client) and a `geminiOptions` passthrough for advanced
|
|
9
|
+
constructor options — parity with the OpenAI and Anthropic wrappers, which
|
|
10
|
+
already forwarded a custom endpoint via `openaiOptions.baseURL` /
|
|
11
|
+
`anthropicOptions.baseURL`. An explicit `geminiOptions.httpOptions` takes
|
|
12
|
+
precedence over `baseUrl`.
|
|
13
|
+
- **Documented local / self-hosted LLM governance.** New README section shows
|
|
14
|
+
governing an OpenAI-compatible local runtime (Ollama, vLLM, LM Studio) by
|
|
15
|
+
forwarding `openaiOptions.baseURL`, plus the Gemini and Anthropic equivalents.
|
|
16
|
+
The plumbing already worked for OpenAI/Anthropic; this documents it and closes
|
|
17
|
+
the Gemini gap.
|
|
18
|
+
|
|
19
|
+
_(Version aligned to 4.0.5 with the Python SDK for this coordinated local-LLM
|
|
20
|
+
parity release; 4.0.4 was not published for the TypeScript SDK.)_
|
|
21
|
+
|
|
3
22
|
## [4.0.3] - 2026-07-17
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -167,6 +167,47 @@ const client = new ShrikeOpenAI({
|
|
|
167
167
|
});
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
### Local and self-hosted LLMs
|
|
171
|
+
|
|
172
|
+
Shrike governs the model you point it at — it does not have to be a hosted
|
|
173
|
+
frontier API. Local runtimes like [Ollama](https://ollama.com),
|
|
174
|
+
[vLLM](https://docs.vllm.ai), and LM Studio expose an OpenAI-compatible
|
|
175
|
+
endpoint, so `ShrikeOpenAI` guards them by forwarding a `baseURL` through
|
|
176
|
+
`openaiOptions`:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { ShrikeOpenAI } from 'shrike-guard/openai';
|
|
180
|
+
|
|
181
|
+
// Ollama serving llama3 locally, governed by Shrike before every call
|
|
182
|
+
const client = new ShrikeOpenAI({
|
|
183
|
+
apiKey: 'ollama', // local servers ignore the value
|
|
184
|
+
shrikeApiKey: 'shrike-...', // governance still runs server-side
|
|
185
|
+
openaiOptions: { baseURL: 'http://localhost:11434/v1' },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const response = await client.chat.completions.create({
|
|
189
|
+
model: 'llama3',
|
|
190
|
+
messages: [{ role: 'user', content: 'Summarize this ticket…' }],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The same pattern works for Anthropic-compatible gateways via
|
|
195
|
+
`anthropicOptions.baseURL`. For Gemini, use the `baseUrl` option (added in
|
|
196
|
+
4.0.5), applied as `httpOptions.baseUrl` on the `@google/genai` client:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { ShrikeGemini } from 'shrike-guard/gemini';
|
|
200
|
+
|
|
201
|
+
const client = new ShrikeGemini({
|
|
202
|
+
apiKey: '…',
|
|
203
|
+
shrikeApiKey: 'shrike-...',
|
|
204
|
+
baseUrl: 'https://your-gemini-gateway.example',
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The prompt still leaves your process to reach the Shrike backend for scanning;
|
|
209
|
+
the *model call* stays on your local/self-hosted endpoint.
|
|
210
|
+
|
|
170
211
|
## SQL and File Scanning
|
|
171
212
|
|
|
172
213
|
The OpenAI client also provides standalone scanning for SQL queries and file paths:
|
|
@@ -78,8 +78,8 @@ declare class ShrikeAnthropic {
|
|
|
78
78
|
* Scan user messages for security threats via backend API.
|
|
79
79
|
*
|
|
80
80
|
* Always calls backend - backend handles tier-based scanning:
|
|
81
|
-
* - Community tier (no API key): L1-
|
|
82
|
-
* - Pro tier: L1-
|
|
81
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
82
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
83
83
|
*/
|
|
84
84
|
_scanMessages(messages: AnthropicMessage[]): Promise<ScanResult>;
|
|
85
85
|
/**
|
|
@@ -78,8 +78,8 @@ declare class ShrikeAnthropic {
|
|
|
78
78
|
* Scan user messages for security threats via backend API.
|
|
79
79
|
*
|
|
80
80
|
* Always calls backend - backend handles tier-based scanning:
|
|
81
|
-
* - Community tier (no API key): L1-
|
|
82
|
-
* - Pro tier: L1-
|
|
81
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
82
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
83
83
|
*/
|
|
84
84
|
_scanMessages(messages: AnthropicMessage[]): Promise<ScanResult>;
|
|
85
85
|
/**
|
package/dist/anthropic/index.js
CHANGED
|
@@ -330,7 +330,7 @@ function sanitizeScanResponse(raw) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// package.json
|
|
333
|
-
var version = "4.0.
|
|
333
|
+
var version = "4.0.5";
|
|
334
334
|
|
|
335
335
|
// src/version.ts
|
|
336
336
|
var VERSION = version;
|
|
@@ -419,8 +419,8 @@ var ShrikeAnthropic = class {
|
|
|
419
419
|
* Scan user messages for security threats via backend API.
|
|
420
420
|
*
|
|
421
421
|
* Always calls backend - backend handles tier-based scanning:
|
|
422
|
-
* - Community tier (no API key): L1-
|
|
423
|
-
* - Pro tier: L1-
|
|
422
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
423
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
424
424
|
*/
|
|
425
425
|
async _scanMessages(messages) {
|
|
426
426
|
const userContent = this._extractUserContent(messages);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../package.json","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID"],"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 * The default fail_mode is 'closed' (secure by default), so a scan\n * failure raises this exception. Set fail_mode='open' to instead\n * silently handle these errors and allow the request to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult, ScanViolation } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n // L8 tonality drift — backend normalizes profanity + hostile as toxic_content;\n // casual stays as jailbreak (matches platform/common/models/response.go:309-313).\n tonality_drift_profanity: 'toxic_content',\n tonality_drift_hostile: 'toxic_content',\n tonality_drift_casual: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxic content (canonical name as of the L7 rewrite). `toxicity` kept as a\n // legacy alias so older backend builds + customer code don't break.\n toxic_content: 'toxic_content',\n toxicity: 'toxic_content',\n harmful_content: 'toxic_content',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // L9 multi-turn correlation — the pseudo-category emitted when any\n // multi_turn_* pattern fires (crescendo, blocked_retry, trust-building,\n // memory_poisoning, etc.). The normalizeThreatType prefix check below\n // handles the specific pattern names; this entry catches the pseudo\n // value if the backend pre-normalizes.\n multi_turn_attack: 'multi_turn_attack',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxic_content:\n 'This content contains potentially harmful or inappropriate language.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n multi_turn_attack:\n 'A pattern was detected across multiple turns of this session that suggests a coordinated attempt to bypass safety controls.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxic_content: 'medium',\n toxicity: 'medium',\n multi_turn_attack: 'high',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n if (THREAT_TYPE_MAP[normalized]) {\n return THREAT_TYPE_MAP[normalized];\n }\n // L9 multi-turn correlation patterns flow as `multi_turn_<pattern>`\n // (crescendo, blocked_retry, topic_pivot, threat_diversity, safe_then_unsafe,\n // tool_sequence_anomaly, coded_language_setup, context_overflow,\n // memory_poisoning, velocity_burst). Backend collapses these to\n // multi_turn_attack — mirror the prefix logic from\n // platform/common/models/response.go:380.\n if (normalized.startsWith('multi_turn_')) {\n return 'multi_turn_attack';\n }\n return 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Governance-outcome fields the sanitizer preserves verbatim on BOTH safe\n * and unsafe branches. These are OUTCOME state (customer sees outcomes;\n * timings/attribution stay provider-only) — the contract-symmetry surface\n * (safe/refuse_tier/recovery/session_state on every response).\n */\nconst PRESERVED_GOVERNANCE_FIELDS = [\n 'action',\n 'refuse_tier',\n 'recovery',\n 'session_state',\n 'content_type',\n 'approval_info',\n 'client_session_rotation',\n] as const;\n\n/**\n * Sanitize one entry from the backend violations[] array.\n * Preserves customer-visible outcome fields (severity, action, threat_type,\n * owasp_category, user_message, suggested_action); drops attribution\n * fields (policy_id, policy_name, matched_pattern, ai_reasoning, etc.).\n */\nexport function sanitizeViolation(raw: unknown): ScanViolation | null {\n if (!raw || typeof raw !== 'object') {\n return null;\n }\n const source = raw as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(source)) {\n if (!INTERNAL_FIELDS.has(k)) {\n out[k] = v;\n }\n }\n return out as ScanViolation;\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection attribution (layer timings, per-detector\n * confidences, pattern names, policy IDs) — NOT outcome state.\n * Preserves the four-state governance surface (`action`, `refuse_tier`,\n * `recovery`, `session_state`) so callers can distinguish\n * allow/warn/require_approval/block. Contract symmetry — safe and refuse\n * verdicts carry the same governance fields.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n const result: ScanResult = { safe };\n\n // Governance-outcome fields pass through on BOTH branches. This is the\n // symmetric contract — a `warn` verdict (safe=true, action=\"warn\") must\n // carry recovery/refuse_tier the same as a `block` (safe=false).\n for (const field of PRESERVED_GOVERNANCE_FIELDS) {\n const value = (raw as Record<string, unknown>)[field];\n if (value !== undefined && value !== null) {\n (result as Record<string, unknown>)[field] = value;\n }\n }\n\n // violations[] is a mixed shape — pass each entry through per-item\n // sanitization to drop policy_id / policy_name / matched_pattern while\n // keeping customer-visible severity, threat_type, owasp_category,\n // user_message, suggested_action. Empty array is dropped (noise).\n const rawViolations = raw.violations;\n if (Array.isArray(rawViolations) && rawViolations.length > 0) {\n const cleaned = rawViolations\n .map((v) => sanitizeViolation(v))\n .filter((v): v is ScanViolation => v !== null);\n if (cleaned.length > 0) {\n result.violations = cleaned;\n }\n }\n\n if (safe) {\n // Safe branch: reason may carry advisory copy for warn tier.\n result.reason = raw.reason || '';\n return result;\n }\n\n // Unsafe branch: derive threat classification for legacy callers that\n // read top-level threat_type/severity/guidance. Backend /api/scan/enforce\n // returns top-level threat_type=null and puts detail inside violations[];\n // for those responses, prefer the first violation's threat_type when\n // the raw top-level field is missing.\n let rawThreatType = raw.threat_type;\n if (!rawThreatType && Array.isArray(rawViolations) && rawViolations.length > 0) {\n const first = rawViolations[0];\n if (first && typeof first === 'object' && !Array.isArray(first)) {\n const firstThreat = (first as Record<string, unknown>).threat_type;\n if (typeof firstThreat === 'string') {\n rawThreatType = firstThreat;\n }\n }\n }\n\n const threatType = normalizeThreatType(rawThreatType);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n result.threat_type = threatType;\n result.severity = severity;\n result.confidence = confidence;\n result.reason = raw.reason || guidance;\n result.guidance = guidance;\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","{\n \"name\": \"shrike-guard\",\n \"version\": \"4.0.3\",\n \"description\": \"Shrike SDK for AI agent action governance — drop-in wrapper for OpenAI, Anthropic, and Gemini that governs every tool call, prompt, and response server-side. Allow / approve / block. Free tier available.\",\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./openai\": {\n \"types\": \"./dist/openai/index.d.ts\",\n \"import\": \"./dist/openai/index.mjs\",\n \"require\": \"./dist/openai/index.js\"\n },\n \"./anthropic\": {\n \"types\": \"./dist/anthropic/index.d.ts\",\n \"import\": \"./dist/anthropic/index.mjs\",\n \"require\": \"./dist/anthropic/index.js\"\n },\n \"./gemini\": {\n \"types\": \"./dist/gemini/index.d.ts\",\n \"import\": \"./dist/gemini/index.mjs\",\n \"require\": \"./dist/gemini/index.js\"\n },\n \"./api\": {\n \"types\": \"./dist/api/index.d.ts\",\n \"import\": \"./dist/api/index.mjs\",\n \"require\": \"./dist/api/index.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"test\": \"jest\",\n \"test:coverage\": \"jest --coverage\",\n \"lint\": \"eslint src --ext .ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\"\n },\n \"keywords\": [\n \"action-governance\",\n \"ai-agents\",\n \"agent-security\",\n \"agent-governance\",\n \"tool-call-gating\",\n \"ai-governance\",\n \"openai\",\n \"anthropic\",\n \"claude\",\n \"gemini\",\n \"llm\",\n \"prompt-injection\",\n \"pii\",\n \"jailbreak\",\n \"ai-safety\"\n ],\n \"author\": \"Shrike Security <security@shrikesecurity.com>\",\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://shrikesecurity.com\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Shrike-Security/shrike-guard-js\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Shrike-Security/shrike-guard-js/issues\"\n },\n \"peerDependencies\": {\n \"@anthropic-ai/sdk\": \">=0.18.0\",\n \"@google/genai\": \">=1.0.0\",\n \"openai\": \">=4.0.0\"\n },\n \"peerDependenciesMeta\": {\n \"openai\": {\n \"optional\": true\n },\n \"@anthropic-ai/sdk\": {\n \"optional\": true\n },\n \"@google/genai\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@anthropic-ai/sdk\": \"^0.20.0\",\n \"@google/genai\": \"^2.12.0\",\n \"@types/jest\": \"^29.5.12\",\n \"@types/node\": \"^20.11.0\",\n \"jest\": \"^29.7.0\",\n \"openai\": \"^4.28.0\",\n \"ts-jest\": \"^29.1.2\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.3.3\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n }\n}\n","// SDK version — derived from package.json at build time (tsup/esbuild inlines\n// the JSON literal, resolveJsonModule is on), so it can never drift from the\n// published version. It was previously a hardcoded '1.5.0' that fell out of\n// sync with package.json (4.0.2) and mis-stamped X-Shrike-SDK-Version on every\n// scan. Guarded by tests/unit/version.test.ts. Mirrors the Python SDK, which\n// derives __version__ from installed distribution metadata.\nimport { version } from '../package.json';\n\nexport const VERSION: string = version;\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { AUTO_CHUNK_THRESHOLD, aggregateChunkResults, chunkContent } from './chunker';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\n/**\n * Governance action per Cooperative Governance 4-state contract (allow / warn / require_approval / block).\n * The `action` field is the server-authoritative signal for proceed-vs-refuse;\n * `isBlocked()` in this module consumes it directly.\n */\nexport type ScanAction = 'allow' | 'warn' | 'require_approval' | 'block';\n\n/**\n * Recovery guidance emitted by the backend on refuse verdicts.\n * `available_tools` names the MCP tools the caller can still invoke while\n * under quarantine (typically read-only surfaces like session_status).\n */\nexport interface ScanRecovery {\n instruction?: string;\n available_tools?: string[];\n patterns_triggered?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Session-level state carried on every scan response (contract symmetry).\n * Fields are outcome state per the dashboard IP boundary — customer-visible.\n */\nexport interface ScanSessionState {\n session_risk_score?: number;\n session_turn_number?: number;\n session_patterns?: string[];\n session_locked?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * A single policy violation as emitted by the /api/scan/enforce endpoint.\n * The sanitizer strips policy_id / policy_name / matched_pattern / other\n * attribution before this reaches callers; what remains here is the\n * customer-visible outcome surface.\n */\nexport interface ScanViolation {\n threat_type?: string;\n owasp_category?: string;\n severity?: string;\n action?: string;\n user_message?: string;\n suggested_action?: string;\n [key: string]: unknown;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: ScanViolation[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** Cooperative Governance action — server-authoritative proceed/refuse signal */\n action?: ScanAction;\n /** Refuse tier: allow / warn / require_approval / block */\n refuse_tier?: ScanAction;\n /** Recovery guidance, present on refuse verdicts */\n recovery?: ScanRecovery;\n /** L9 session correlation state — outcome, customer-visible */\n session_state?: ScanSessionState;\n /** Specialized scan input type, e.g. 'sql' / 'file_path' / 'a2a_message' */\n content_type?: string;\n /** Client-side session rotation guidance */\n client_session_rotation?: unknown;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n /**\n * True when this is a fail-open verdict returned without a completed scan —\n * i.e. `failMode: 'open'` was set and the backend was unreachable, so\n * enforcement was skipped. Lets a caller distinguish \"scanned and clean\"\n * from \"not scanned, allowed anyway.\"\n */\n degraded?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * Build a fail-open ALLOW verdict. Returned only when `failMode === 'open'` is\n * explicitly set and the scan could not complete (timeout, backend error).\n * Marked `degraded: true` — centralize all fail-open returns through this so the\n * marker can never be forgotten on a new wrapper.\n */\nexport function failOpenResult(reason: string): ScanResult {\n return { safe: true, reason, degraded: true };\n}\n\n/**\n * Result of ScanClient.declareScope — the persisted scope row plus derived\n * active_until + expired fields. Mirrors the shape returned by\n * POST /api/v1/agent/scope/declare on the backend.\n */\nexport interface DeclareScopeResult {\n scope_id?: string;\n agent_id?: string;\n purpose?: string;\n allowed_tools?: string[];\n forbidden_tools?: string[];\n max_duration_seconds?: number;\n expires_at?: string;\n active_until?: string;\n expired?: boolean;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\n/**\n * Decide whether a scan verdict should be enforced as a block.\n *\n * Prefers the server-authoritative `action` field emitted by\n * /api/scan/enforce. Falls back to the legacy\n * `safe` boolean for verdicts that predate the enforce endpoint (older\n * backends, circuit-breaker fail-open synthetic verdicts, cached responses).\n *\n * Behavior:\n * - action = \"block\" → true\n * - action = \"require_approval\" → true (held actions are refused at the\n * tool-call boundary; caller can re-issue\n * after approval lands)\n * - action = \"allow\" → false\n * - action = \"warn\" → false (advisory; caller should surface\n * via block-feedback but not refuse)\n * - action absent → fall back to `!safe`\n *\n * This helper is the ONLY place the SDK converts a verdict into a\n * proceed-vs-refuse decision. Every LLM wrapper (OpenAI, Anthropic, Gemini)\n * routes through it so behavior stays uniform.\n *\n * Contract-symmetry pin: mirrors Python `_is_blocked` in shrike_guard.scanner.\n * If either SDK diverges, that is a bug in one of the two.\n */\nexport function isBlocked(verdict: ScanResult | Record<string, unknown>): boolean {\n const action = (verdict as Record<string, unknown>).action;\n if (typeof action === 'string' && action.length > 0) {\n // Known non-blocking tiers proceed.\n if (action === 'allow' || action === 'warn') return false;\n // Known blocking tiers refuse.\n if (action === 'block' || action === 'require_approval') return true;\n // Unknown/future tier (e.g. a new \"quarantine\" the backend adds): do NOT\n // fail open on an unrecognized name — fall through to `safe`. A new\n // blocking tier also sets safe:false, so this fails closed until the\n // client learns the tier explicitly. Forward-compat contract, see\n // tests/unit/contract-forward-compat.test.ts.\n }\n const safe = (verdict as Record<string, unknown>).safe;\n if (typeof safe === 'boolean') {\n return !safe;\n }\n return false;\n}\n\n/**\n * When running without an API key, appends a signup hint to scan results\n * so agents/users know they can upgrade from regex-only to full scanning.\n */\nexport function maybeAddSignupHint(result: ScanResult, apiKey: string): ScanResult {\n if (apiKey) return result;\n // Don't override if backend already provided upgrade_hint\n if ((result as any).upgrade_hint) return result;\n return {\n ...result,\n _note: 'Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup',\n };\n}\n\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n\n if (!this.apiKey) {\n console.warn('[shrike-guard] No API key provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n // Auto-chunk large prompts. Below the threshold we keep the single-shot\n // path unchanged; above it we split on natural boundaries, scan each\n // chunk sequentially, and stop on the first block verdict. The\n // conversation_history context is NOT chunked — it rides on every scan\n // so downstream layers see the same session shape.\n if (prompt.length > AUTO_CHUNK_THRESHOLD) {\n return this.scanChunked(prompt, context);\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Chunk-and-scan path for prompts above AUTO_CHUNK_THRESHOLD. Splits on\n * natural boundaries, scans sequentially, aggregates. Stops on the first\n * block verdict (fail-fast) so a compromised chunk doesn't pay for the\n * remaining scans.\n *\n * Known limit: same session ID rides every chunk, so L9 turn count\n * inflates by chunk-count. Fix post-launch via backend chunk_group field.\n */\n private async scanChunked(prompt: string, context?: string): Promise<ScanResult> {\n const chunks = chunkContent(prompt);\n const results: ScanResult[] = [];\n for (const chunk of chunks) {\n const chunkResult = await this.scan(chunk, context);\n results.push(chunkResult);\n const action = chunkResult.action || chunkResult.refuse_tier;\n if (action === 'block') {\n break;\n }\n }\n return maybeAddSignupHint(aggregateChunkResults(results), this.apiKey);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Declare (or refresh) the operating scope for a task-scoped agent.\n *\n * Once declared, every subsequent scan for the same agent_id is enforced\n * against the scope on the backend. Tool calls outside allowed_tools —\n * or explicitly on forbidden_tools — route to refuse_tier:\n * \"require_approval\" with threat_type: \"scope_violation\". Expired scopes\n * emit threat_type: \"scope_expired\". Absent a declaration, no scope\n * check runs; every existing SDK integration is unaffected until it\n * opts in.\n *\n * Wire contract mirrors ScanClient.declare_scope on the Python SDK and\n * scan_declare_scope on the shrike-mcp package — do not diverge without\n * updating those siblings.\n *\n * @param options.agentId - Agent identity this scope applies to.\n * @param options.allowedTools - Exact tool names permitted; [\"*\"] = any.\n * @param options.forbiddenTools - Optional. Wins over allowedTools.\n * @param options.purpose - Optional audit + dashboard label.\n * @param options.maxDurationSeconds - Optional TTL from created_at.\n * @param options.expiresAt - Optional ISO-8601 absolute expiry.\n * @returns The persisted scope row, including scope_id + active_until.\n */\n async declareScope(options: {\n agentId: string;\n allowedTools: string[];\n forbiddenTools?: string[];\n purpose?: string;\n maxDurationSeconds?: number;\n expiresAt?: string;\n }): Promise<DeclareScopeResult> {\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n agent_id: options.agentId,\n allowed_tools: options.allowedTools,\n };\n if (options.purpose !== undefined) payload.purpose = options.purpose;\n if (options.forbiddenTools !== undefined) payload.forbidden_tools = options.forbiddenTools;\n if (options.maxDurationSeconds !== undefined) {\n payload.max_duration_seconds = options.maxDurationSeconds;\n }\n if (options.expiresAt !== undefined) payload.expires_at = options.expiresAt;\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/v1/agent/scope/declare`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `declareScope failed: ${response.status}${text ? ` — ${text.trim()}` : ''}`\n );\n }\n\n return (await response.json()) as DeclareScopeResult;\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, isBlocked, maybeAddSignupHint, ScanResult, failOpenResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n if (!this._shrikeApiKey) {\n console.warn('[shrike-guard] No shrikeApiKey provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L4 (regex, unicode, encoding, token normalization)\n * - Pro tier: L1-L8 (full scan including LLM)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan API error: ${response.status}`);\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this._shrikeApiKey);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return failOpenResult('Scan timeout, failing open');\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan error: ${errorMessage}`);\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (isBlocked(scanResult)) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../package.json","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID"],"mappings":";;;;;;;;;;AAyBO,IAAM,oBAAA,GAAuB,GAAA;AAG7B,IAAM,iBAAA,GAAoB,QAAA;AAG1B,IAAM,gBAAA,GAAmB,sCAAA;AAWzB,IAAM,QAAA,GAAW,YAAA;AAOxB,IAAM,aAAaA,iBAAA,EAAW;AAC9B,IAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,eAAA,IAAmB,CAAA,OAAA,EAAUA,mBAAW,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAG3E,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO,UAAA;AACT;AAGO,SAAS,UAAA,GAAqB;AACnC,EAAA,OAAO,QAAA;AACT;;;ACrDO,IAAM,WAAA,GAAN,cAA0B,KAAA,CAAM;AAAA,EAGrC,WAAA,CAAY,OAAA,EAAiB,OAAA,GAAmC,EAAC,EAAG;AAClE,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAEf,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF,CAAA;AAcO,IAAM,eAAA,GAAN,cAA8B,WAAA,CAAY;AAAA,EAC/C,WAAA,CAAY,OAAA,EAAiB,OAAA,GAAmC,EAAC,EAAG;AAClE,IAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF,CAAA;AASO,IAAM,kBAAA,GAAN,cAAiC,WAAA,CAAY;AAAA,EAUlD,YACE,OAAA,EACA,UAAA,EACA,UAAA,EACA,UAAA,GAAwB,EAAC,EACzB;AACA,IAAA,KAAA,CAAM,OAAA,EAAS;AAAA,MACb,WAAA,EAAa,UAAA;AAAA,MACb,UAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACpB;AACF,CAAA;;;AC3DA,IAAM,eAAA,GAA0C;AAAA;AAAA,EAE9C,gBAAA,EAAkB,kBAAA;AAAA,EAClB,SAAA,EAAW,kBAAA;AAAA,EACX,MAAA,EAAQ,kBAAA;AAAA,EACR,oBAAA,EAAsB,kBAAA;AAAA,EACtB,cAAA,EAAgB,kBAAA;AAAA,EAChB,oBAAA,EAAsB,kBAAA;AAAA,EACtB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,cAAA,EAAgB,kBAAA;AAAA;AAAA,EAEhB,SAAA,EAAW,WAAA;AAAA,EACX,iBAAA,EAAmB,WAAA;AAAA,EACnB,aAAA,EAAe,WAAA;AAAA,EACf,QAAA,EAAU,WAAA;AAAA,EACV,YAAA,EAAc,WAAA;AAAA,EACd,kBAAA,EAAoB,WAAA;AAAA,EACpB,QAAA,EAAU,WAAA;AAAA,EACV,UAAA,EAAY,WAAA;AAAA;AAAA;AAAA,EAGZ,wBAAA,EAA0B,eAAA;AAAA,EAC1B,sBAAA,EAAwB,eAAA;AAAA,EACxB,qBAAA,EAAuB,WAAA;AAAA;AAAA,EAEvB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,wBAAA,EAA0B,oBAAA;AAAA;AAAA,EAE1B,iBAAA,EAAmB,mBAAA;AAAA,EACnB,YAAA,EAAc,mBAAA;AAAA,EACd,UAAA,EAAY,mBAAA;AAAA,EACZ,OAAA,EAAS,mBAAA;AAAA,EACT,SAAA,EAAW,mBAAA;AAAA,EACX,sBAAA,EAAwB,mBAAA;AAAA,EACxB,qBAAA,EAAuB,mBAAA;AAAA;AAAA,EAEvB,aAAA,EAAe,eAAA;AAAA,EACf,IAAA,EAAM,eAAA;AAAA,EACN,SAAA,EAAW,eAAA;AAAA,EACX,YAAA,EAAc,eAAA;AAAA,EACd,aAAA,EAAe,eAAA;AAAA,EACf,eAAA,EAAiB,eAAA;AAAA,EACjB,aAAA,EAAe,eAAA;AAAA;AAAA,EAEf,cAAA,EAAgB,gBAAA;AAAA,EAChB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,cAAA,EAAgB,gBAAA;AAAA,EAChB,WAAA,EAAa,gBAAA;AAAA,EACb,cAAA,EAAgB,gBAAA;AAAA,EAChB,mBAAA,EAAqB,gBAAA;AAAA,EACrB,iBAAA,EAAmB,gBAAA;AAAA;AAAA,EAEnB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,OAAA,EAAS,kBAAA;AAAA,EACT,OAAA,EAAS,kBAAA;AAAA,EACT,UAAA,EAAY,kBAAA;AAAA,EACZ,cAAA,EAAgB,kBAAA;AAAA,EAChB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,iBAAA,EAAmB,kBAAA;AAAA,EACnB,UAAA,EAAY,kBAAA;AAAA,EACZ,OAAA,EAAS,kBAAA;AAAA,EACT,WAAA,EAAa,kBAAA;AAAA;AAAA,EAEb,YAAA,EAAc,cAAA;AAAA,EACd,GAAA,EAAK,cAAA;AAAA,EACL,QAAA,EAAU,cAAA;AAAA,EACV,aAAA,EAAe,cAAA;AAAA,EACf,aAAA,EAAe,cAAA;AAAA,EACf,cAAA,EAAgB,cAAA;AAAA,EAChB,GAAA,EAAK,cAAA;AAAA,EACL,WAAA,EAAa,cAAA;AAAA,EACb,cAAA,EAAgB,cAAA;AAAA,EAChB,YAAA,EAAc,cAAA;AAAA,EACd,sBAAA,EAAwB,cAAA;AAAA;AAAA,EAExB,cAAA,EAAgB,gBAAA;AAAA,EAChB,cAAA,EAAgB,gBAAA;AAAA,EAChB,iBAAA,EAAmB,gBAAA;AAAA,EACnB,aAAA,EAAe,gBAAA;AAAA;AAAA;AAAA,EAGf,aAAA,EAAe,eAAA;AAAA,EACf,QAAA,EAAU,eAAA;AAAA,EACV,eAAA,EAAiB,eAAA;AAAA;AAAA,EAEjB,iBAAA,EAAmB,gBAAA;AAAA,EACnB,cAAA,EAAgB,gBAAA;AAAA,EAChB,aAAA,EAAe,gBAAA;AAAA,EACf,SAAA,EAAW,gBAAA;AAAA,EACX,SAAA,EAAW,gBAAA;AAAA,EACX,YAAA,EAAc,gBAAA;AAAA,EACd,WAAA,EAAa,gBAAA;AAAA,EACb,eAAA,EAAiB,gBAAA;AAAA;AAAA,EAEjB,cAAA,EAAgB,gBAAA;AAAA,EAChB,iBAAA,EAAmB,gBAAA;AAAA;AAAA,EAEnB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,SAAA,EAAW,oBAAA;AAAA,EACX,eAAA,EAAiB,oBAAA;AAAA;AAAA,EAEjB,oBAAA,EAAsB,sBAAA;AAAA;AAAA,EAEtB,qBAAA,EAAuB,uBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvB,iBAAA,EAAmB,mBAAA;AAAA;AAAA,EAEnB,UAAA,EAAY,YAAA;AAAA,EACZ,mBAAA,EAAqB,qBAAA;AAAA,EACrB,UAAA,EAAY,qBAAA;AAAA,EACZ,OAAA,EAAS;AACX,CAAA;AAKA,IAAM,eAAA,GAA0C;AAAA,EAC9C,gBAAA,EACE,8EAAA;AAAA,EACF,SAAA,EACE,iFAAA;AAAA,EACF,kBAAA,EACE,gFAAA;AAAA,EACF,iBAAA,EACE,2DAAA;AAAA,EACF,aAAA,EAAe,yDAAA;AAAA,EACf,cAAA,EACE,0EAAA;AAAA,EACF,gBAAA,EACE,2EAAA;AAAA,EACF,YAAA,EAAc,4DAAA;AAAA,EACd,cAAA,EAAgB,8CAAA;AAAA,EAChB,aAAA,EACE,sEAAA;AAAA,EACF,QAAA,EACE,sEAAA;AAAA,EACF,iBAAA,EACE,6HAAA;AAAA,EACF,cAAA,EACE,gEAAA;AAAA,EACF,cAAA,EACE,+DAAA;AAAA,EACF,kBAAA,EACE,mDAAA;AAAA,EACF,oBAAA,EACE,yEAAA;AAAA,EACF,qBAAA,EACE,+DAAA;AAAA,EACF,UAAA,EACE,kEAAA;AAAA,EACF,mBAAA,EAAqB,+CAAA;AAAA,EACrB,OAAA,EAAS;AACX,CAAA;AAMA,IAAM,eAAA,GAA0C;AAAA,EAC9C,gBAAA,EAAkB,MAAA;AAAA,EAClB,SAAA,EAAW,MAAA;AAAA,EACX,kBAAA,EAAoB,MAAA;AAAA,EACpB,iBAAA,EAAmB,MAAA;AAAA,EACnB,aAAA,EAAe,UAAA;AAAA,EACf,cAAA,EAAgB,MAAA;AAAA,EAChB,gBAAA,EAAkB,UAAA;AAAA,EAClB,YAAA,EAAc,MAAA;AAAA,EACd,cAAA,EAAgB,QAAA;AAAA,EAChB,aAAA,EAAe,QAAA;AAAA,EACf,QAAA,EAAU,QAAA;AAAA,EACV,iBAAA,EAAmB,MAAA;AAAA,EACnB,cAAA,EAAgB,UAAA;AAAA,EAChB,cAAA,EAAgB,MAAA;AAAA,EAChB,kBAAA,EAAoB,QAAA;AAAA,EACpB,oBAAA,EAAsB,UAAA;AAAA,EACtB,qBAAA,EAAuB,UAAA;AAAA,EACvB,UAAA,EAAY,QAAA;AAAA,EACZ,mBAAA,EAAqB,KAAA;AAAA,EACrB,OAAA,EAAS;AACX,CAAA;AAKA,IAAM,eAAA,uBAAsB,GAAA,CAAI;AAAA,EAC9B,aAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAC,CAAA;AAKM,SAAS,oBAAoB,OAAA,EAA0B;AAC5D,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,SAAA;AAAA,EACT;AACA,EAAA,MAAM,aAAa,OAAA,CAAQ,WAAA,EAAY,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AAC1D,EAAA,IAAI,eAAA,CAAgB,UAAU,CAAA,EAAG;AAC/B,IAAA,OAAO,gBAAgB,UAAU,CAAA;AAAA,EACnC;AAOA,EAAA,IAAI,UAAA,CAAW,UAAA,CAAW,aAAa,CAAA,EAAG;AACxC,IAAA,OAAO,mBAAA;AAAA,EACT;AACA,EAAA,OAAO,SAAA;AACT;AAQO,SAAS,cAAA,CACd,YACA,WAAA,EACQ;AACR,EAAA,MAAM,eAAA,uBAAsB,GAAA,CAAI,CAAC,YAAY,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAC,CAAA;AACrE,EAAA,IAAI,eAAe,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,WAAA,EAAa,CAAA,EAAG;AACjE,IAAA,OAAO,YAAY,WAAA,EAAY;AAAA,EACjC;AACA,EAAA,OAAO,eAAA,CAAgB,UAAU,CAAA,IAAK,QAAA;AACxC;AAOO,SAAS,iBAAiB,KAAA,EAAwB;AACvD,EAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,SAAS,GAAA,EAAK;AAChB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,SAAS,GAAA,EAAK;AAChB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA;AACT;AAQA,IAAM,2BAAA,GAA8B;AAAA,EAClC,QAAA;AAAA,EACA,aAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA;AAQO,SAAS,kBAAkB,GAAA,EAAoC;AACpE,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAAS,GAAA;AACf,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC3C,IAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,CAAI,CAAC,CAAA,EAAG;AAC3B,MAAA,GAAA,CAAI,CAAC,CAAA,GAAI,CAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAYO,SAAS,qBAAqB,GAAA,EAA6B;AAChE,EAAA,MAAM,IAAA,GAAO,IAAI,IAAA,KAAS,KAAA;AAC1B,EAAA,MAAM,MAAA,GAAqB,EAAE,IAAA,EAAK;AAKlC,EAAA,KAAA,MAAW,SAAS,2BAAA,EAA6B;AAC/C,IAAA,MAAM,KAAA,GAAS,IAAgC,KAAK,CAAA;AACpD,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,MAAC,MAAA,CAAmC,KAAK,CAAA,GAAI,KAAA;AAAA,IAC/C;AAAA,EACF;AAMA,EAAA,MAAM,gBAAgB,GAAA,CAAI,UAAA;AAC1B,EAAA,IAAI,MAAM,OAAA,CAAQ,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5D,IAAA,MAAM,OAAA,GAAU,aAAA,CACb,GAAA,CAAI,CAAC,CAAA,KAAM,iBAAA,CAAkB,CAAC,CAAC,CAAA,CAC/B,MAAA,CAAO,CAAC,CAAA,KAA0B,MAAM,IAAI,CAAA;AAC/C,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,MAAA,CAAO,UAAA,GAAa,OAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,IAAI,IAAA,EAAM;AAER,IAAA,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,IAAU,EAAA;AAC9B,IAAA,OAAO,MAAA;AAAA,EACT;AAOA,EAAA,IAAI,gBAAgB,GAAA,CAAI,WAAA;AACxB,EAAA,IAAI,CAAC,iBAAiB,KAAA,CAAM,OAAA,CAAQ,aAAa,CAAA,IAAK,aAAA,CAAc,SAAS,CAAA,EAAG;AAC9E,IAAA,MAAM,KAAA,GAAQ,cAAc,CAAC,CAAA;AAC7B,IAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC/D,MAAA,MAAM,cAAe,KAAA,CAAkC,WAAA;AACvD,MAAA,IAAI,OAAO,gBAAgB,QAAA,EAAU;AACnC,QAAA,aAAA,GAAgB,WAAA;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAa,oBAAoB,aAAa,CAAA;AACpD,EAAA,MAAM,UAAA,GAAa,gBAAA;AAAA,IACjB,OAAO,GAAA,CAAI,UAAA,KAAe,QAAA,GAAW,IAAI,UAAA,GAAa;AAAA,GACxD;AACA,EAAA,MAAM,QAAA,GAAW,cAAA;AAAA,IACf,UAAA;AAAA,IACA,OAAO,GAAA,CAAI,QAAA,KAAa,QAAA,GAAW,IAAI,QAAA,GAAW;AAAA,GACpD;AACA,EAAA,MAAM,QAAA,GACJ,eAAA,CAAgB,UAAU,CAAA,IAAK,gBAAgB,SAAS,CAAA;AAE1D,EAAA,MAAA,CAAO,WAAA,GAAc,UAAA;AACrB,EAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,EAAA,MAAA,CAAO,UAAA,GAAa,UAAA;AACpB,EAAA,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,IAAU,QAAA;AAC9B,EAAA,MAAA,CAAO,QAAA,GAAW,QAAA;AAClB,EAAA,OAAO,MAAA;AACT;;;ACnYE,IAAA,OAAA,GAAW,OAAA;;;ACMN,IAAM,OAAA,GAAkB,OAAA;AC0HxB,SAAS,eAAe,MAAA,EAA4B;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,UAAU,IAAA,EAAK;AAC9C;AAwCA,SAAS,YAAA,GAAuB;AAC9B,EAAA,OAAO,sCAAA,CAAuC,OAAA,CAAQ,OAAA,EAAS,CAAC,CAAA,KAAM;AACpE,IAAA,MAAM,CAAA,GAAK,IAAA,CAAK,MAAA,EAAO,GAAI,EAAA,GAAM,CAAA;AACjC,IAAA,MAAM,CAAA,GAAI,CAAA,KAAM,GAAA,GAAM,CAAA,GAAK,IAAI,CAAA,GAAO,CAAA;AACtC,IAAA,OAAO,CAAA,CAAE,SAAS,EAAE,CAAA;AAAA,EACtB,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CACd,cACA,SAAA,EACwB;AACxB,EAAA,OAAO;AAAA,IACL,aAAA,EAAe,UAAU,YAAY,CAAA,CAAA;AAAA,IACrC,cAAA,EAAgB,kBAAA;AAAA,IAChB,cAAA,EAAgB,QAAA;AAAA,IAChB,sBAAA,EAAwB,OAAA;AAAA,IACxB,qBAAA,EAAoC,YAAA;AAAa,GACnD;AACF;AA8HO,SAAS,UAAU,OAAA,EAAwD;AAChF,EAAA,MAAM,SAAU,OAAA,CAAoC,MAAA;AACpD,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,SAAS,CAAA,EAAG;AAEnD,IAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,MAAA,EAAQ,OAAO,KAAA;AAEpD,IAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,kBAAA,EAAoB,OAAO,IAAA;AAAA,EAMlE;AACA,EAAA,MAAM,OAAQ,OAAA,CAAoC,IAAA;AAClD,EAAA,IAAI,OAAO,SAAS,SAAA,EAAW;AAC7B,IAAA,OAAO,CAAC,IAAA;AAAA,EACV;AACA,EAAA,OAAO,KAAA;AACT;AAMO,SAAS,kBAAA,CAAmB,QAAoB,MAAA,EAA4B;AACjF,EAAA,IAAI,QAAQ,OAAO,MAAA;AAEnB,EAAA,IAAK,MAAA,CAAe,cAAc,OAAO,MAAA;AACzC,EAAA,OAAO;AAAA,IACL,GAAG,MAAA;AAAA,IACH,KAAA,EAAO;AAAA,GACT;AACF;;;AC1UA,SAAS,WAAW,OAAA,EAAuB;AACzC,EAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,cAAA,EAAO,OAAO,CAAA,CAAE,CAAA;AAC/B;AA4DO,IAAM,kBAAN,MAAsB;AAAA,EAU3B,WAAA,CAAY,OAAA,GAAkC,EAAC,EAAG;AAGhD,IAAA,MAAM,eAAA,GAAkB,SAAA,CAAQ,mBAAmB,CAAA,CAAE,OAAA;AAErD,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,eAAA,CAAgB;AAAA,MACpC,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,GAAG,OAAA,CAAQ;AAAA,KACZ,CAAA;AACD,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,cAAA,IAAkB,gBAAA,EAAkB,OAAA,CAAQ,OAAO,EAAE,CAAA;AACrF,IAAA,IAAA,CAAK,aAAA,GAAgB,QAAQ,YAAA,IAAgB,EAAA;AAC7C,IAAA,IAAA,CAAK,SAAA,GACH,OAAO,OAAA,CAAQ,QAAA,KAAa,WACvB,OAAA,CAAQ,QAAA,GACT,QAAQ,QAAA,IAAY,iBAAA;AAC1B,IAAA,IAAA,CAAK,YAAA,GAAe,QAAQ,WAAA,IAAe,oBAAA;AAK3C,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA,OAAA,CAAQ,KAAK,mFAA8E,CAAA;AAC3F,MAAA,OAAA,CAAQ,KAAK,+FAA+F,CAAA;AAAA,IAC9G;AAGA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAI,iBAAA,CAAkB,IAAI,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,QAAA,EAAsC;AACxD,IAAA,MAAM,eAAyB,EAAC;AAEhC,IAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,MAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AACvB,QAAA,IAAI,OAAO,GAAA,CAAI,OAAA,KAAY,QAAA,EAAU;AACnC,UAAA,YAAA,CAAa,IAAA,CAAK,IAAI,OAAO,CAAA;AAAA,QAC/B,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AACrC,UAAA,KAAA,MAAW,KAAA,IAAS,IAAI,OAAA,EAAS;AAC/B,YAAA,IAAI,KAAA,CAAM,IAAA,KAAS,MAAA,IAAU,KAAA,CAAM,IAAA,EAAM;AACvC,cAAA,YAAA,CAAa,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAO,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cAAc,QAAA,EAAmD;AACrE,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,mBAAA,CAAoB,QAAQ,CAAA;AAErD,IAAA,IAAI,CAAC,WAAA,CAAY,IAAA,EAAK,EAAG;AACvB,MAAA,OAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,yBAAA,EAA0B;AAAA,IACzD;AAGA,IAAA,OAAO,IAAA,CAAK,YAAY,WAAW,CAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,YAAY,MAAA,EAAqC;AAC7D,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,YAAY,CAAA;AAExE,IAAA,IAAI;AACF,MAAA,MAAM,WAAW,MAAM,KAAA,CAAM,CAAA,EAAG,IAAA,CAAK,eAAe,CAAA,iBAAA,CAAA,EAAqB;AAAA,QACvE,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,cAAA,CAAe,IAAA,CAAK,aAAa,CAAA;AAAA,QAC1C,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,MAAA;AAAA,UACA,OAAA,EAAS;AAAA,YACP,YAAY,YAAA,EAAa;AAAA,YACzB,UAAU,UAAA,EAAW;AAAA,YACrB,kBAAA,EAAoB;AAAA;AACtB,SACD,CAAA;AAAA,QACD,QAAQ,UAAA,CAAW;AAAA,OACpB,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AACpC,UAAA,OAAO,cAAA,CAAe,CAAA,gBAAA,EAAmB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,QAC5D;AACA,QAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,yBAAA,EAA4B,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MACzE;AAEA,MAAA,OAAO,kBAAA,CAAmB,qBAAsB,MAAM,QAAA,CAAS,MAAqB,CAAA,EAAG,KAAK,aAAa,CAAA;AAAA,IAC3G,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,eAAA,EAAiB;AACpC,QAAA,MAAM,KAAA;AAAA,MACR;AAEA,MAAA,MAAM,eAAe,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAE1E,MAAA,IAAI,YAAA,CAAa,QAAA,CAAS,OAAO,CAAA,EAAG;AAClC,QAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AAEpC,UAAA,UAAA,CAAW,yDAAyD,CAAA;AACpE,UAAA,OAAO,eAAe,4BAA4B,CAAA;AAAA,QACpD;AACA,QAAA,MAAM,IAAI,gBAAgB,kDAAkD,CAAA;AAAA,MAC9E;AAEA,MAAA,IAAI,KAAK,SAAA,KAAA,MAAA,aAA6B;AACpC,QAAA,OAAO,cAAA,CAAe,CAAA,YAAA,EAAe,YAAY,CAAA,CAAE,CAAA;AAAA,MACrD;AACA,MAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,aAAA,EAAgB,YAAY,CAAA,CAAE,CAAA;AAAA,IAC1D,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,SAAS,CAAA;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,SAAA,GAAuB;AACzB,IAAA,OAAO,IAAA,CAAK,UAAA;AAAA,EACd;AACF;AAKA,IAAM,oBAAN,MAAwB;AAAA,EAGtB,YAAY,MAAA,EAAyB;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,MAAA,EAA+C;AAE1D,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,OAAA,CAAQ,aAAA;AAAA,MACpC,MAAA,CAAO;AAAA,KACT;AAGA,IAAA,IAAI,SAAA,CAAU,UAAU,CAAA,EAAG;AACzB,MAAA,MAAM,IAAI,kBAAA;AAAA,QACR,CAAA,iBAAA,EAAoB,UAAA,CAAW,MAAA,IAAU,0BAA0B,CAAA,CAAA;AAAA,QACnE,UAAA,CAAW,WAAA;AAAA,QACX,UAAA,CAAW,UAAA;AAAA,QACX,UAAA,CAAW,cAAc;AAAC,OAC5B;AAAA,IACF;AAGA,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,CAAU,QAAA,CAAS,MAAA,CAAO;AAAA,MAC5C,GAAG,MAAA;AAAA,MACH,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AACF,CAAA","file":"index.js","sourcesContent":["/**\n * Configuration constants and types for the Shrike Guard SDK.\n */\n\nimport { randomUUID } from 'node:crypto';\n\n/**\n * Defines behavior when scan operations fail.\n */\nexport enum FailMode {\n /**\n * Allow the request to proceed (fail-open). Use this mode when\n * availability is prioritized over strict security.\n */\n OPEN = 'open',\n\n /**\n * Block the request and raise an exception (fail-closed). This is the\n * default behavior — matching the MCP server's security posture. Use\n * fail-open only when you've explicitly decided availability matters more.\n */\n CLOSED = 'closed',\n}\n\n/** Default timeout for scan requests in milliseconds */\nexport const DEFAULT_SCAN_TIMEOUT = 10000;\n\n/** Default fail mode - block requests when scan fails (fail-closed) */\nexport const DEFAULT_FAIL_MODE = FailMode.CLOSED;\n\n/** Default Shrike API endpoint (uses load balancer for scalability) */\nexport const DEFAULT_ENDPOINT = 'https://api.shrikesecurity.com/agent';\n\n/** Default rate limit (requests per minute) */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE = 100;\n\n// Note: Scan depth is set by the backend from the license tier (community = L1-L5 deterministic layers; Pro and above = full L1-L9 including LLM semantic, response intel and session correlation).\n// Detection logic is backend-side. The one deliberate exception is PII redaction,\n// which runs locally from a bundled pattern set so redaction survives a backend\n// outage; see piiRedactor / piiSync.\n\n/** SDK identification */\nexport const SDK_NAME = 'typescript';\nexport const SDK_USER_AGENT = 'shrike-guard-typescript';\n\n/**\n * Session identity — persists for the lifetime of this SDK process.\n * Maps to L9's SessionCache key for multi-turn attack detection.\n */\nconst SESSION_ID = randomUUID();\nconst AGENT_ID = process.env.SHRIKE_AGENT_ID || `sdk-ts-${randomUUID().slice(0, 8)}`;\n\n/** Returns the stable session ID for this SDK process. */\nexport function getSessionId(): string {\n return SESSION_ID;\n}\n\n/** Returns the agent ID (from SHRIKE_AGENT_ID env or auto-generated). */\nexport function getAgentId(): string {\n return AGENT_ID;\n}\n\n/**\n * Approval information returned when a policy requires human sign-off.\n */\nexport interface ApprovalInfo {\n requires_approval: boolean;\n approval_id: string;\n approval_level: string;\n action_summary: string;\n policy_name: string;\n expires_in_seconds: number;\n threat_type?: string;\n severity?: string;\n owasp_category?: string;\n risk_factors?: string[];\n original_action?: string;\n}\n\n/**\n * Retry configuration for transient failure resilience.\n * Matches MCP server's RETRY_CONFIG for consistent behavior.\n */\nexport const RETRY_CONFIG = {\n maxRetries: 2,\n initialDelayMs: 200,\n maxDelayMs: 2000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'fetch failed'],\n};\n","/**\n * Custom exceptions for the Shrike Guard SDK.\n */\n\n/**\n * Base exception for all Shrike SDK errors.\n */\nexport class ShrikeError extends Error {\n public readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'ShrikeError';\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Raised when a scan operation fails and fail_mode is 'closed'.\n *\n * This exception is raised when:\n * - The Shrike API times out\n * - A network error occurs\n * - The API returns an unexpected error\n *\n * The default fail_mode is 'closed' (secure by default), so a scan\n * failure raises this exception. Set fail_mode='open' to instead\n * silently handle these errors and allow the request to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult, ScanViolation } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n // L8 tonality drift — backend normalizes profanity + hostile as toxic_content;\n // casual stays as jailbreak (matches platform/common/models/response.go:309-313).\n tonality_drift_profanity: 'toxic_content',\n tonality_drift_hostile: 'toxic_content',\n tonality_drift_casual: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxic content (canonical name as of the L7 rewrite). `toxicity` kept as a\n // legacy alias so older backend builds + customer code don't break.\n toxic_content: 'toxic_content',\n toxicity: 'toxic_content',\n harmful_content: 'toxic_content',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // L9 multi-turn correlation — the pseudo-category emitted when any\n // multi_turn_* pattern fires (crescendo, blocked_retry, trust-building,\n // memory_poisoning, etc.). The normalizeThreatType prefix check below\n // handles the specific pattern names; this entry catches the pseudo\n // value if the backend pre-normalizes.\n multi_turn_attack: 'multi_turn_attack',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxic_content:\n 'This content contains potentially harmful or inappropriate language.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n multi_turn_attack:\n 'A pattern was detected across multiple turns of this session that suggests a coordinated attempt to bypass safety controls.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxic_content: 'medium',\n toxicity: 'medium',\n multi_turn_attack: 'high',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n if (THREAT_TYPE_MAP[normalized]) {\n return THREAT_TYPE_MAP[normalized];\n }\n // L9 multi-turn correlation patterns flow as `multi_turn_<pattern>`\n // (crescendo, blocked_retry, topic_pivot, threat_diversity, safe_then_unsafe,\n // tool_sequence_anomaly, coded_language_setup, context_overflow,\n // memory_poisoning, velocity_burst). Backend collapses these to\n // multi_turn_attack — mirror the prefix logic from\n // platform/common/models/response.go:380.\n if (normalized.startsWith('multi_turn_')) {\n return 'multi_turn_attack';\n }\n return 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Governance-outcome fields the sanitizer preserves verbatim on BOTH safe\n * and unsafe branches. These are OUTCOME state (customer sees outcomes;\n * timings/attribution stay provider-only) — the contract-symmetry surface\n * (safe/refuse_tier/recovery/session_state on every response).\n */\nconst PRESERVED_GOVERNANCE_FIELDS = [\n 'action',\n 'refuse_tier',\n 'recovery',\n 'session_state',\n 'content_type',\n 'approval_info',\n 'client_session_rotation',\n] as const;\n\n/**\n * Sanitize one entry from the backend violations[] array.\n * Preserves customer-visible outcome fields (severity, action, threat_type,\n * owasp_category, user_message, suggested_action); drops attribution\n * fields (policy_id, policy_name, matched_pattern, ai_reasoning, etc.).\n */\nexport function sanitizeViolation(raw: unknown): ScanViolation | null {\n if (!raw || typeof raw !== 'object') {\n return null;\n }\n const source = raw as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(source)) {\n if (!INTERNAL_FIELDS.has(k)) {\n out[k] = v;\n }\n }\n return out as ScanViolation;\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection attribution (layer timings, per-detector\n * confidences, pattern names, policy IDs) — NOT outcome state.\n * Preserves the four-state governance surface (`action`, `refuse_tier`,\n * `recovery`, `session_state`) so callers can distinguish\n * allow/warn/require_approval/block. Contract symmetry — safe and refuse\n * verdicts carry the same governance fields.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n const result: ScanResult = { safe };\n\n // Governance-outcome fields pass through on BOTH branches. This is the\n // symmetric contract — a `warn` verdict (safe=true, action=\"warn\") must\n // carry recovery/refuse_tier the same as a `block` (safe=false).\n for (const field of PRESERVED_GOVERNANCE_FIELDS) {\n const value = (raw as Record<string, unknown>)[field];\n if (value !== undefined && value !== null) {\n (result as Record<string, unknown>)[field] = value;\n }\n }\n\n // violations[] is a mixed shape — pass each entry through per-item\n // sanitization to drop policy_id / policy_name / matched_pattern while\n // keeping customer-visible severity, threat_type, owasp_category,\n // user_message, suggested_action. Empty array is dropped (noise).\n const rawViolations = raw.violations;\n if (Array.isArray(rawViolations) && rawViolations.length > 0) {\n const cleaned = rawViolations\n .map((v) => sanitizeViolation(v))\n .filter((v): v is ScanViolation => v !== null);\n if (cleaned.length > 0) {\n result.violations = cleaned;\n }\n }\n\n if (safe) {\n // Safe branch: reason may carry advisory copy for warn tier.\n result.reason = raw.reason || '';\n return result;\n }\n\n // Unsafe branch: derive threat classification for legacy callers that\n // read top-level threat_type/severity/guidance. Backend /api/scan/enforce\n // returns top-level threat_type=null and puts detail inside violations[];\n // for those responses, prefer the first violation's threat_type when\n // the raw top-level field is missing.\n let rawThreatType = raw.threat_type;\n if (!rawThreatType && Array.isArray(rawViolations) && rawViolations.length > 0) {\n const first = rawViolations[0];\n if (first && typeof first === 'object' && !Array.isArray(first)) {\n const firstThreat = (first as Record<string, unknown>).threat_type;\n if (typeof firstThreat === 'string') {\n rawThreatType = firstThreat;\n }\n }\n }\n\n const threatType = normalizeThreatType(rawThreatType);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n result.threat_type = threatType;\n result.severity = severity;\n result.confidence = confidence;\n result.reason = raw.reason || guidance;\n result.guidance = guidance;\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","{\n \"name\": \"shrike-guard\",\n \"version\": \"4.0.5\",\n \"description\": \"Shrike SDK for AI agent action governance — drop-in wrapper for OpenAI, Anthropic, and Gemini that governs every tool call, prompt, and response server-side. Allow / approve / block. Free tier available.\",\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./openai\": {\n \"types\": \"./dist/openai/index.d.ts\",\n \"import\": \"./dist/openai/index.mjs\",\n \"require\": \"./dist/openai/index.js\"\n },\n \"./anthropic\": {\n \"types\": \"./dist/anthropic/index.d.ts\",\n \"import\": \"./dist/anthropic/index.mjs\",\n \"require\": \"./dist/anthropic/index.js\"\n },\n \"./gemini\": {\n \"types\": \"./dist/gemini/index.d.ts\",\n \"import\": \"./dist/gemini/index.mjs\",\n \"require\": \"./dist/gemini/index.js\"\n },\n \"./api\": {\n \"types\": \"./dist/api/index.d.ts\",\n \"import\": \"./dist/api/index.mjs\",\n \"require\": \"./dist/api/index.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"test\": \"jest\",\n \"test:coverage\": \"jest --coverage\",\n \"lint\": \"eslint src --ext .ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\"\n },\n \"keywords\": [\n \"action-governance\",\n \"ai-agents\",\n \"agent-security\",\n \"agent-governance\",\n \"tool-call-gating\",\n \"ai-governance\",\n \"openai\",\n \"anthropic\",\n \"claude\",\n \"gemini\",\n \"llm\",\n \"prompt-injection\",\n \"pii\",\n \"jailbreak\",\n \"ai-safety\"\n ],\n \"author\": \"Shrike Security <security@shrikesecurity.com>\",\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://shrikesecurity.com\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Shrike-Security/shrike-guard-js\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Shrike-Security/shrike-guard-js/issues\"\n },\n \"peerDependencies\": {\n \"@anthropic-ai/sdk\": \">=0.18.0\",\n \"@google/genai\": \">=1.0.0\",\n \"openai\": \">=4.0.0\"\n },\n \"peerDependenciesMeta\": {\n \"openai\": {\n \"optional\": true\n },\n \"@anthropic-ai/sdk\": {\n \"optional\": true\n },\n \"@google/genai\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@anthropic-ai/sdk\": \"^0.20.0\",\n \"@google/genai\": \"^2.12.0\",\n \"@types/jest\": \"^29.5.12\",\n \"@types/node\": \"^20.11.0\",\n \"jest\": \"^29.7.0\",\n \"openai\": \"^4.28.0\",\n \"ts-jest\": \"^29.1.2\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.3.3\"\n },\n \"engines\": {\n \"node\": \">=18.0.0\"\n }\n}\n","// SDK version — derived from package.json at build time (tsup/esbuild inlines\n// the JSON literal, resolveJsonModule is on), so it can never drift from the\n// published version. It was previously a hardcoded '1.5.0' that fell out of\n// sync with package.json (4.0.2) and mis-stamped X-Shrike-SDK-Version on every\n// scan. Guarded by tests/unit/version.test.ts. Mirrors the Python SDK, which\n// derives __version__ from installed distribution metadata.\nimport { version } from '../package.json';\n\nexport const VERSION: string = version;\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { AUTO_CHUNK_THRESHOLD, aggregateChunkResults, chunkContent } from './chunker';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\n/**\n * Governance action per Cooperative Governance 4-state contract (allow / warn / require_approval / block).\n * The `action` field is the server-authoritative signal for proceed-vs-refuse;\n * `isBlocked()` in this module consumes it directly.\n */\nexport type ScanAction = 'allow' | 'warn' | 'require_approval' | 'block';\n\n/**\n * Recovery guidance emitted by the backend on refuse verdicts.\n * `available_tools` names the MCP tools the caller can still invoke while\n * under quarantine (typically read-only surfaces like session_status).\n */\nexport interface ScanRecovery {\n instruction?: string;\n available_tools?: string[];\n patterns_triggered?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Session-level state carried on every scan response (contract symmetry).\n * Fields are outcome state per the dashboard IP boundary — customer-visible.\n */\nexport interface ScanSessionState {\n session_risk_score?: number;\n session_turn_number?: number;\n session_patterns?: string[];\n session_locked?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * A single policy violation as emitted by the /api/scan/enforce endpoint.\n * The sanitizer strips policy_id / policy_name / matched_pattern / other\n * attribution before this reaches callers; what remains here is the\n * customer-visible outcome surface.\n */\nexport interface ScanViolation {\n threat_type?: string;\n owasp_category?: string;\n severity?: string;\n action?: string;\n user_message?: string;\n suggested_action?: string;\n [key: string]: unknown;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: ScanViolation[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** Cooperative Governance action — server-authoritative proceed/refuse signal */\n action?: ScanAction;\n /** Refuse tier: allow / warn / require_approval / block */\n refuse_tier?: ScanAction;\n /** Recovery guidance, present on refuse verdicts */\n recovery?: ScanRecovery;\n /** L9 session correlation state — outcome, customer-visible */\n session_state?: ScanSessionState;\n /** Specialized scan input type, e.g. 'sql' / 'file_path' / 'a2a_message' */\n content_type?: string;\n /** Client-side session rotation guidance */\n client_session_rotation?: unknown;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n /**\n * True when this is a fail-open verdict returned without a completed scan —\n * i.e. `failMode: 'open'` was set and the backend was unreachable, so\n * enforcement was skipped. Lets a caller distinguish \"scanned and clean\"\n * from \"not scanned, allowed anyway.\"\n */\n degraded?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * Build a fail-open ALLOW verdict. Returned only when `failMode === 'open'` is\n * explicitly set and the scan could not complete (timeout, backend error).\n * Marked `degraded: true` — centralize all fail-open returns through this so the\n * marker can never be forgotten on a new wrapper.\n */\nexport function failOpenResult(reason: string): ScanResult {\n return { safe: true, reason, degraded: true };\n}\n\n/**\n * Result of ScanClient.declareScope — the persisted scope row plus derived\n * active_until + expired fields. Mirrors the shape returned by\n * POST /api/v1/agent/scope/declare on the backend.\n */\nexport interface DeclareScopeResult {\n scope_id?: string;\n agent_id?: string;\n purpose?: string;\n allowed_tools?: string[];\n forbidden_tools?: string[];\n max_duration_seconds?: number;\n expires_at?: string;\n active_until?: string;\n expired?: boolean;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\n/**\n * Decide whether a scan verdict should be enforced as a block.\n *\n * Prefers the server-authoritative `action` field emitted by\n * /api/scan/enforce. Falls back to the legacy\n * `safe` boolean for verdicts that predate the enforce endpoint (older\n * backends, circuit-breaker fail-open synthetic verdicts, cached responses).\n *\n * Behavior:\n * - action = \"block\" → true\n * - action = \"require_approval\" → true (held actions are refused at the\n * tool-call boundary; caller can re-issue\n * after approval lands)\n * - action = \"allow\" → false\n * - action = \"warn\" → false (advisory; caller should surface\n * via block-feedback but not refuse)\n * - action absent → fall back to `!safe`\n *\n * This helper is the ONLY place the SDK converts a verdict into a\n * proceed-vs-refuse decision. Every LLM wrapper (OpenAI, Anthropic, Gemini)\n * routes through it so behavior stays uniform.\n *\n * Contract-symmetry pin: mirrors Python `_is_blocked` in shrike_guard.scanner.\n * If either SDK diverges, that is a bug in one of the two.\n */\nexport function isBlocked(verdict: ScanResult | Record<string, unknown>): boolean {\n const action = (verdict as Record<string, unknown>).action;\n if (typeof action === 'string' && action.length > 0) {\n // Known non-blocking tiers proceed.\n if (action === 'allow' || action === 'warn') return false;\n // Known blocking tiers refuse.\n if (action === 'block' || action === 'require_approval') return true;\n // Unknown/future tier (e.g. a new \"quarantine\" the backend adds): do NOT\n // fail open on an unrecognized name — fall through to `safe`. A new\n // blocking tier also sets safe:false, so this fails closed until the\n // client learns the tier explicitly. Forward-compat contract, see\n // tests/unit/contract-forward-compat.test.ts.\n }\n const safe = (verdict as Record<string, unknown>).safe;\n if (typeof safe === 'boolean') {\n return !safe;\n }\n return false;\n}\n\n/**\n * When running without an API key, appends a signup hint to scan results\n * so agents/users know they can upgrade from regex-only to full scanning.\n */\nexport function maybeAddSignupHint(result: ScanResult, apiKey: string): ScanResult {\n if (apiKey) return result;\n // Don't override if backend already provided upgrade_hint\n if ((result as any).upgrade_hint) return result;\n return {\n ...result,\n _note: 'Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup',\n };\n}\n\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n\n if (!this.apiKey) {\n console.warn('[shrike-guard] No API key provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n // Auto-chunk large prompts. Below the threshold we keep the single-shot\n // path unchanged; above it we split on natural boundaries, scan each\n // chunk sequentially, and stop on the first block verdict. The\n // conversation_history context is NOT chunked — it rides on every scan\n // so downstream layers see the same session shape.\n if (prompt.length > AUTO_CHUNK_THRESHOLD) {\n return this.scanChunked(prompt, context);\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Chunk-and-scan path for prompts above AUTO_CHUNK_THRESHOLD. Splits on\n * natural boundaries, scans sequentially, aggregates. Stops on the first\n * block verdict (fail-fast) so a compromised chunk doesn't pay for the\n * remaining scans.\n *\n * Known limit: same session ID rides every chunk, so L9 turn count\n * inflates by chunk-count. Fix post-launch via backend chunk_group field.\n */\n private async scanChunked(prompt: string, context?: string): Promise<ScanResult> {\n const chunks = chunkContent(prompt);\n const results: ScanResult[] = [];\n for (const chunk of chunks) {\n const chunkResult = await this.scan(chunk, context);\n results.push(chunkResult);\n const action = chunkResult.action || chunkResult.refuse_tier;\n if (action === 'block') {\n break;\n }\n }\n return maybeAddSignupHint(aggregateChunkResults(results), this.apiKey);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/enforce/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Declare (or refresh) the operating scope for a task-scoped agent.\n *\n * Once declared, every subsequent scan for the same agent_id is enforced\n * against the scope on the backend. Tool calls outside allowed_tools —\n * or explicitly on forbidden_tools — route to refuse_tier:\n * \"require_approval\" with threat_type: \"scope_violation\". Expired scopes\n * emit threat_type: \"scope_expired\". Absent a declaration, no scope\n * check runs; every existing SDK integration is unaffected until it\n * opts in.\n *\n * Wire contract mirrors ScanClient.declare_scope on the Python SDK and\n * scan_declare_scope on the shrike-mcp package — do not diverge without\n * updating those siblings.\n *\n * @param options.agentId - Agent identity this scope applies to.\n * @param options.allowedTools - Exact tool names permitted; [\"*\"] = any.\n * @param options.forbiddenTools - Optional. Wins over allowedTools.\n * @param options.purpose - Optional audit + dashboard label.\n * @param options.maxDurationSeconds - Optional TTL from created_at.\n * @param options.expiresAt - Optional ISO-8601 absolute expiry.\n * @returns The persisted scope row, including scope_id + active_until.\n */\n async declareScope(options: {\n agentId: string;\n allowedTools: string[];\n forbiddenTools?: string[];\n purpose?: string;\n maxDurationSeconds?: number;\n expiresAt?: string;\n }): Promise<DeclareScopeResult> {\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n agent_id: options.agentId,\n allowed_tools: options.allowedTools,\n };\n if (options.purpose !== undefined) payload.purpose = options.purpose;\n if (options.forbiddenTools !== undefined) payload.forbidden_tools = options.forbiddenTools;\n if (options.maxDurationSeconds !== undefined) {\n payload.max_duration_seconds = options.maxDurationSeconds;\n }\n if (options.expiresAt !== undefined) payload.expires_at = options.expiresAt;\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/v1/agent/scope/declare`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n const text = await response.text().catch(() => '');\n throw new Error(\n `declareScope failed: ${response.status}${text ? ` — ${text.trim()}` : ''}`\n );\n }\n\n return (await response.json()) as DeclareScopeResult;\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, isBlocked, maybeAddSignupHint, ScanResult, failOpenResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: Scan depth is set by the backend from the license tier (community = L1-L5 deterministic layers; Pro and above = full L1-L9 including LLM semantic, response intel and session correlation).\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n if (!this._shrikeApiKey) {\n console.warn('[shrike-guard] No shrikeApiKey provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)\n * - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan API error: ${response.status}`);\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this._shrikeApiKey);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return failOpenResult('Scan timeout, failing open');\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return failOpenResult(`Scan error: ${errorMessage}`);\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (isBlocked(scanResult)) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
|
package/dist/anthropic/index.mjs
CHANGED
|
@@ -328,7 +328,7 @@ function sanitizeScanResponse(raw) {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// package.json
|
|
331
|
-
var version = "4.0.
|
|
331
|
+
var version = "4.0.5";
|
|
332
332
|
|
|
333
333
|
// src/version.ts
|
|
334
334
|
var VERSION = version;
|
|
@@ -417,8 +417,8 @@ var ShrikeAnthropic = class {
|
|
|
417
417
|
* Scan user messages for security threats via backend API.
|
|
418
418
|
*
|
|
419
419
|
* Always calls backend - backend handles tier-based scanning:
|
|
420
|
-
* - Community tier (no API key): L1-
|
|
421
|
-
* - Pro tier: L1-
|
|
420
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
421
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
422
422
|
*/
|
|
423
423
|
async _scanMessages(messages) {
|
|
424
424
|
const userContent = this._extractUserContent(messages);
|