shrike-guard 1.3.0 → 1.3.3

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/LICENSE CHANGED
@@ -48,7 +48,7 @@
48
48
  "Contribution" shall mean any work of authorship, including
49
49
  the original version of the Work and any modifications or additions
50
50
  to that Work or Derivative Works thereof, that is intentionally
51
- submitted to the Licensor for inclusion in the Work by the copyright owner
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
52
  or by an individual or Legal Entity authorized to submit on behalf of
53
53
  the copyright owner. For the purposes of this definition, "submitted"
54
54
  means any form of electronic, verbal, or written communication sent
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2024 Shrike Security
189
+ Copyright [yyyy] [name of copyright owner]
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,14 +1,10 @@
1
- # Shrike Guard — TypeScript SDK
2
-
3
- > **Public repo:** https://github.com/Shrike-Security/shrike-guard-js
4
- >
5
- > This directory is mirrored from the public repository. For the latest version, issues, and contributions, please use the public repo.
1
+ # Shrike Guard
6
2
 
7
3
  [![npm version](https://badge.fury.io/js/shrike-guard.svg)](https://badge.fury.io/js/shrike-guard)
8
4
  [![Node.js 18+](https://img.shields.io/badge/node-18+-green.svg)](https://nodejs.org/)
9
5
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
10
6
 
11
- **Shrike Guard** is a TypeScript SDK that provides security protection for your LLM applications. It wraps OpenAI, Anthropic (Claude), and Google Gemini clients to automatically scan all prompts for security threats before they reach the LLM.
7
+ **Shrike Guard** is a TypeScript SDK for the [Shrike](https://shrikesecurity.com) platform — AI governance for every AI interaction. It wraps OpenAI, Anthropic (Claude), and Google Gemini clients to automatically evaluate all prompts against policy before they reach the LLM. Whether you're governing a customer-facing chatbot, securing developer AI tools, or managing autonomous agent actions — the same multi-layered cognitive pipeline evaluates every interaction.
12
8
 
13
9
  ## Features
14
10
 
@@ -24,6 +20,35 @@
24
20
  - **Subpath imports**: `shrike-guard/openai`, `shrike-guard/anthropic`, `shrike-guard/gemini`
25
21
  - **Zero code changes**: Just replace your import
26
22
 
23
+ ## What Shrike Detects
24
+
25
+ Shrike's backend runs a multi-stage detection pipeline with security rules across **7 compliance frameworks**:
26
+
27
+ | Framework | Coverage |
28
+ |-----------|----------|
29
+ | **GDPR** | EU personal data — names, addresses, national IDs |
30
+ | **HIPAA** | Protected health information (PHI) |
31
+ | **ISO 27001** | Information security — passwords, tokens, certificates |
32
+ | **SOC 2** | Secrets, credentials, API keys, cloud tokens |
33
+ | **NIST** | AI risk management (IR 8596), cybersecurity framework (CSF 2.0) |
34
+ | **PCI-DSS** | Cardholder data — PAN, CVV, expiry, track data |
35
+ | **WebMCP** | MCP tool description injection, data exfiltration |
36
+
37
+ Plus built-in detection for prompt injection, jailbreaks, social engineering, and dangerous requests.
38
+
39
+ ### Tiers
40
+
41
+ Detection depth depends on your tier. All tiers get the same SDK wrappers — tiers control which backend layers run.
42
+
43
+ | | Anonymous | Community | Pro | Enterprise |
44
+ |---|---|---|---|---|
45
+ | Detection Layers | L1-L5 | L1-L7 | L1-L8 | L1-L9 |
46
+ | API Key | Not needed | Free signup | Paid | Paid |
47
+ | Rate Limit | — | 10/min | 100/min | 1,000/min |
48
+ | Scans/month | — | 1,000 | 25,000 | 1,000,000 |
49
+
50
+ **Anonymous** (no API key): Pattern-based detection (L1-L5). **Community** (free): Adds LLM-powered semantic analysis. Register at [shrikesecurity.com/signup](https://shrikesecurity.com/signup) — instant, no credit card.
51
+
27
52
  ## Installation
28
53
 
29
54
  ```bash
@@ -132,18 +157,16 @@ const client = new ShrikeOpenAI({
132
157
 
133
158
  ### Custom Endpoint
134
159
 
135
- For enterprise customers with a dedicated Shrike deployment (VPC or single-tenant):
160
+ For self-hosted Shrike deployments:
136
161
 
137
162
  ```typescript
138
163
  const client = new ShrikeOpenAI({
139
164
  apiKey: 'sk-...',
140
165
  shrikeApiKey: 'shrike-...',
141
- shrikeEndpoint: 'https://your-org.shrike.internal',
166
+ shrikeEndpoint: 'https://your-shrike-instance.com',
142
167
  });
143
168
  ```
144
169
 
145
- Dedicated endpoints are provisioned by Shrike during enterprise onboarding. Contact sales@shrikesecurity.com for details.
146
-
147
170
  ## SQL and File Scanning
148
171
 
149
172
  The OpenAI client also provides standalone scanning for SQL queries and file paths:
@@ -233,20 +256,9 @@ if (result.safe) {
233
256
  export OPENAI_API_KEY="sk-..."
234
257
  export ANTHROPIC_API_KEY="sk-ant-..."
235
258
  export SHRIKE_API_KEY="shrike-..."
236
- export SHRIKE_ENDPOINT="https://your-org.shrike.internal" # Enterprise/VPC deployments only
259
+ export SHRIKE_ENDPOINT="https://your-shrike-instance.com"
237
260
  ```
238
261
 
239
- ## Pricing Tiers
240
-
241
- | | Community (Free) | Pro ($99/mo) | Enterprise |
242
- |---|---|---|---|
243
- | Scans/month | 1,000 | 50,000 | Unlimited |
244
- | Detection layers | L1-L4 (regex) | L1-L8 (full pipeline) | L1-L8 + custom |
245
- | LLM analysis | No | Yes | Yes |
246
- | Rate limit | 10 req/min | 100 req/min | 1,000 req/min |
247
-
248
- Get a free Community API key at [shrikesecurity.com](https://api.shrikesecurity.com/dashboard/api/v1/community/register) or via [GCP Marketplace](https://console.cloud.google.com/marketplace/product/shrike-security/shrike).
249
-
250
262
  ## Scope and Limitations
251
263
 
252
264
  | Scanned | Not Scanned |
@@ -258,10 +270,43 @@ Get a free Community API key at [shrikesecurity.com](https://api.shrikesecurity.
258
270
 
259
271
  ### Why Input-Only Scanning?
260
272
 
261
- Shrike Guard focuses on **pre-flight protection** - blocking malicious prompts BEFORE they reach the LLM. This:
273
+ Shrike Guard focuses on **pre-flight protection** — blocking malicious prompts BEFORE they reach the LLM. This:
262
274
  - Prevents prompt injection attacks at the source
263
275
  - Has zero latency impact on LLM responses
264
- - Catches 95%+ of threats (attacks are in the INPUT)
276
+ - Catches the vast majority of threats at the input layer
277
+
278
+ ## Other Integration Surfaces
279
+
280
+ Shrike Guard is one of several ways to integrate with the Shrike platform:
281
+
282
+ - **MCP Server** — `npx shrike-mcp` ([GitHub](https://github.com/Shrike-Security/shrike-mcp))
283
+ - **Python SDK** — `pip install shrike-guard` ([GitHub](https://github.com/Shrike-Security/shrike-guard-python))
284
+ - **REST API** — `POST https://api.shrikesecurity.com/agent/scan`
285
+ - **LLM Gateway** — Change one URL, scan everything
286
+ - **Browser Extension** — Chrome/Edge for ChatGPT, Claude, Gemini
287
+ - **Dashboard** — [shrikesecurity.com](https://shrikesecurity.com)
288
+
289
+ ## Use Cases
290
+
291
+ | Scenario | How Shrike Guard Helps |
292
+ |---|---|
293
+ | **Customer chatbot** | Wrap your OpenAI/Anthropic client. Every user message scanned for injection before it reaches the model. |
294
+ | **Internal RAG pipeline** | Scan retrieved context + user queries for PII leakage and injection attempts. |
295
+ | **AI coding assistant** | Scan prompts for proprietary code patterns before they leave your environment. |
296
+ | **Agent orchestration** | Scan every tool call and LLM request in your LangChain/CrewAI/AutoGen pipeline. |
297
+
298
+ ## Alternatives
299
+
300
+ Looking for an AI security SDK? Here's how Shrike Guard compares:
301
+
302
+ | Feature | Shrike Guard | Lakera | Prompt Armor |
303
+ |---|---|---|---|
304
+ | Drop-in OpenAI/Anthropic/Gemini wrapper | Yes | No | No |
305
+ | Multi-layered evaluation pipeline | Yes | Limited | Limited |
306
+ | PII detection + redaction | Yes | Partial | No |
307
+ | Session correlation | Yes (Enterprise) | No | No |
308
+ | Free tier (no API key) | Yes | No | No |
309
+ | Open source client | Yes (Apache 2.0) | No | No |
265
310
 
266
311
  ## License
267
312
 
@@ -269,6 +314,8 @@ Apache 2.0
269
314
 
270
315
  ## Support
271
316
 
272
- - Documentation: https://docs.shrike.security/sdk/typescript
273
- - Issues: https://github.com/shrike-security/shrike-guard/issues
274
- - Email: support@shrikesecurity.com
317
+ - [Shrike](https://shrikesecurity.com) — Sign up, dashboard, docs
318
+ - [Documentation](https://shrikesecurity.com/docs) — Quick start, API reference
319
+ - [GitHub Issues](https://github.com/Shrike-Security/shrike-guard-js/issues) — Bug reports
320
+ - [MCP Server](https://github.com/Shrike-Security/shrike-mcp) — For MCP/agent integration
321
+ - [Python SDK](https://github.com/Shrike-Security/shrike-guard-python) — Python equivalent
@@ -1,6 +1,6 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';
3
- import { F as FailMode, S as ScanResult } from '../scanner-DcDJnMF-.mjs';
3
+ import { F as FailMode, S as ScanResult } from '../scanner-CkCXFY3H.mjs';
4
4
 
5
5
  /**
6
6
  * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
@@ -1,6 +1,6 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';
3
- import { F as FailMode, S as ScanResult } from '../scanner-DcDJnMF-.js';
3
+ import { F as FailMode, S as ScanResult } from '../scanner-CkCXFY3H.js';
4
4
 
5
5
  /**
6
6
  * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
@@ -264,7 +264,7 @@ function sanitizeScanResponse(raw) {
264
264
  }
265
265
 
266
266
  // src/version.ts
267
- var VERSION = "1.0.1";
267
+ var VERSION = "1.3.1";
268
268
  function generateUUID() {
269
269
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
270
270
  const r = Math.random() * 16 | 0;
@@ -281,6 +281,14 @@ function getScanHeaders(shrikeApiKey, requestId) {
281
281
  "X-Shrike-Request-ID": generateUUID()
282
282
  };
283
283
  }
284
+ function maybeAddSignupHint(result, apiKey) {
285
+ if (apiKey) return result;
286
+ if (result.upgrade_hint) return result;
287
+ return {
288
+ ...result,
289
+ _note: "Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup"
290
+ };
291
+ }
284
292
 
285
293
  // src/anthropic/client.ts
286
294
  function logWarning(message) {
@@ -297,6 +305,10 @@ var ShrikeAnthropic = class {
297
305
  this._shrikeApiKey = options.shrikeApiKey || "";
298
306
  this._failMode = typeof options.failMode === "string" ? options.failMode : options.failMode || DEFAULT_FAIL_MODE;
299
307
  this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;
308
+ if (!this._shrikeApiKey) {
309
+ console.warn("[shrike-guard] No shrikeApiKey provided \u2014 running in free tier (regex-only).");
310
+ console.warn("[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup");
311
+ }
300
312
  this.messages = new MessagesNamespace(this);
301
313
  }
302
314
  /**
@@ -360,7 +372,7 @@ var ShrikeAnthropic = class {
360
372
  }
361
373
  throw new ShrikeScanError(`Scan API returned error: ${response.status}`);
362
374
  }
363
- return sanitizeScanResponse(await response.json());
375
+ return maybeAddSignupHint(sanitizeScanResponse(await response.json()), this._shrikeApiKey);
364
376
  } catch (error) {
365
377
  if (error instanceof ShrikeScanError) {
366
378
  throw error;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration constants and types for the Shrike Guard SDK.\n */\n\nimport { randomUUID } from 'node:crypto';\n\n/**\n * Defines behavior when scan operations fail.\n */\nexport enum FailMode {\n /**\n * Allow the request to proceed (fail-open). Use this mode when\n * availability is prioritized over strict security.\n */\n OPEN = 'open',\n\n /**\n * Block the request and raise an exception (fail-closed). This is the\n * default behavior — matching the MCP server's security posture. Use\n * fail-open only when you've explicitly decided availability matters more.\n */\n CLOSED = 'closed',\n}\n\n/** Default timeout for scan requests in milliseconds */\nexport const DEFAULT_SCAN_TIMEOUT = 10000;\n\n/** Default fail mode - block requests when scan fails (fail-closed) */\nexport const DEFAULT_FAIL_MODE = FailMode.CLOSED;\n\n/** Default Shrike API endpoint (uses load balancer for scalability) */\nexport const DEFAULT_ENDPOINT = 'https://api.shrikesecurity.com/agent';\n\n/** Default rate limit (requests per minute) */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE = 100;\n\n// Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n// No local patterns needed - backend handles all detection logic\n\n/** SDK identification */\nexport const SDK_NAME = 'typescript';\nexport const SDK_USER_AGENT = 'shrike-guard-typescript';\n\n/**\n * Session identity — persists for the lifetime of this SDK process.\n * Maps to L9's SessionCache key for multi-turn attack detection.\n */\nconst SESSION_ID = randomUUID();\nconst AGENT_ID = process.env.SHRIKE_AGENT_ID || `sdk-ts-${randomUUID().slice(0, 8)}`;\n\n/** Returns the stable session ID for this SDK process. */\nexport function getSessionId(): string {\n return SESSION_ID;\n}\n\n/** Returns the agent ID (from SHRIKE_AGENT_ID env or auto-generated). */\nexport function getAgentId(): string {\n return AGENT_ID;\n}\n\n/**\n * Approval information returned when a policy requires human sign-off.\n */\nexport interface ApprovalInfo {\n requires_approval: boolean;\n approval_id: string;\n approval_level: string;\n action_summary: string;\n policy_name: string;\n expires_in_seconds: number;\n threat_type?: string;\n severity?: string;\n owasp_category?: string;\n risk_factors?: string[];\n original_action?: string;\n}\n\n/**\n * Retry configuration for transient failure resilience.\n * Matches MCP server's RETRY_CONFIG for consistent behavior.\n */\nexport const RETRY_CONFIG = {\n maxRetries: 2,\n initialDelayMs: 200,\n maxDelayMs: 2000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'fetch failed'],\n};\n","/**\n * Custom exceptions for the Shrike Guard SDK.\n */\n\n/**\n * Base exception for all Shrike SDK errors.\n */\nexport class ShrikeError extends Error {\n public readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'ShrikeError';\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Raised when a scan operation fails and fail_mode is 'closed'.\n *\n * This exception is raised when:\n * - The Shrike API times out\n * - A network error occurs\n * - The API returns an unexpected error\n *\n * When fail_mode is 'open' (default), these errors are silently\n * handled and the request is allowed to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n tonality_drift_profanity: 'jailbreak',\n tonality_drift_casual: 'jailbreak',\n tonality_drift_hostile: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxicity\n toxicity: 'toxicity',\n harmful_content: 'toxicity',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxicity: 'medium',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n return THREAT_TYPE_MAP[normalized] || 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection details, normalizes threat types,\n * and buckets confidence scores.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n\n // Pass through approval_info if present (not an IP concern)\n const approvalInfo = raw.approval_info;\n\n if (safe) {\n const result: ScanResult = {\n safe: true,\n reason: raw.reason || '',\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n }\n\n // Unsafe: normalize and sanitize\n const threatType = normalizeThreatType(raw.threat_type);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n const result: ScanResult = {\n safe: false,\n threat_type: threatType,\n severity: severity,\n confidence: confidence,\n reason: raw.reason || guidance,\n guidance: guidance,\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","/**\n * SDK version - keep in sync with package.json\n */\nexport const VERSION = '1.0.1';\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: unknown[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n [key: string]: unknown;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/scan`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, ScanResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L4 (regex, unicode, encoding, token normalization)\n * - Pro tier: L1-L8 (full scan including LLM)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/scan`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan API error: ${response.status}` };\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return sanitizeScanResponse((await response.json()) as ScanResult);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return { safe: true, reason: 'Scan timeout, failing open' };\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan error: ${errorMessage}` };\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (!scanResult.safe) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/config.ts","../../src/errors.ts","../../src/sanitizer.ts","../../src/version.ts","../../src/scanner.ts","../../src/anthropic/client.ts"],"names":["randomUUID","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Configuration constants and types for the Shrike Guard SDK.\n */\n\nimport { randomUUID } from 'node:crypto';\n\n/**\n * Defines behavior when scan operations fail.\n */\nexport enum FailMode {\n /**\n * Allow the request to proceed (fail-open). Use this mode when\n * availability is prioritized over strict security.\n */\n OPEN = 'open',\n\n /**\n * Block the request and raise an exception (fail-closed). This is the\n * default behavior — matching the MCP server's security posture. Use\n * fail-open only when you've explicitly decided availability matters more.\n */\n CLOSED = 'closed',\n}\n\n/** Default timeout for scan requests in milliseconds */\nexport const DEFAULT_SCAN_TIMEOUT = 10000;\n\n/** Default fail mode - block requests when scan fails (fail-closed) */\nexport const DEFAULT_FAIL_MODE = FailMode.CLOSED;\n\n/** Default Shrike API endpoint (uses load balancer for scalability) */\nexport const DEFAULT_ENDPOINT = 'https://api.shrikesecurity.com/agent';\n\n/** Default rate limit (requests per minute) */\nexport const DEFAULT_RATE_LIMIT_PER_MINUTE = 100;\n\n// Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n// No local patterns needed - backend handles all detection logic\n\n/** SDK identification */\nexport const SDK_NAME = 'typescript';\nexport const SDK_USER_AGENT = 'shrike-guard-typescript';\n\n/**\n * Session identity — persists for the lifetime of this SDK process.\n * Maps to L9's SessionCache key for multi-turn attack detection.\n */\nconst SESSION_ID = randomUUID();\nconst AGENT_ID = process.env.SHRIKE_AGENT_ID || `sdk-ts-${randomUUID().slice(0, 8)}`;\n\n/** Returns the stable session ID for this SDK process. */\nexport function getSessionId(): string {\n return SESSION_ID;\n}\n\n/** Returns the agent ID (from SHRIKE_AGENT_ID env or auto-generated). */\nexport function getAgentId(): string {\n return AGENT_ID;\n}\n\n/**\n * Approval information returned when a policy requires human sign-off.\n */\nexport interface ApprovalInfo {\n requires_approval: boolean;\n approval_id: string;\n approval_level: string;\n action_summary: string;\n policy_name: string;\n expires_in_seconds: number;\n threat_type?: string;\n severity?: string;\n owasp_category?: string;\n risk_factors?: string[];\n original_action?: string;\n}\n\n/**\n * Retry configuration for transient failure resilience.\n * Matches MCP server's RETRY_CONFIG for consistent behavior.\n */\nexport const RETRY_CONFIG = {\n maxRetries: 2,\n initialDelayMs: 200,\n maxDelayMs: 2000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'fetch failed'],\n};\n","/**\n * Custom exceptions for the Shrike Guard SDK.\n */\n\n/**\n * Base exception for all Shrike SDK errors.\n */\nexport class ShrikeError extends Error {\n public readonly details: Record<string, unknown>;\n\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message);\n this.name = 'ShrikeError';\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Raised when a scan operation fails and fail_mode is 'closed'.\n *\n * This exception is raised when:\n * - The Shrike API times out\n * - A network error occurs\n * - The API returns an unexpected error\n *\n * When fail_mode is 'open' (default), these errors are silently\n * handled and the request is allowed to proceed.\n */\nexport class ShrikeScanError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeScanError';\n }\n}\n\n/**\n * Raised when a prompt is blocked by Shrike security checks.\n *\n * This exception indicates that the prompt was scanned and determined\n * to be unsafe. The scan result details are available in the `details`\n * attribute.\n */\nexport class ShrikeBlockedError extends ShrikeError {\n /** The type of threat detected (e.g., 'prompt_injection', 'pii') */\n public readonly threatType?: string;\n\n /** The confidence level of the detection ('high', 'medium', 'low') or raw score (0.0-1.0) */\n public readonly confidence?: number | string;\n\n /** List of specific violations detected */\n public readonly violations: unknown[];\n\n constructor(\n message: string,\n threatType?: string,\n confidence?: number | string,\n violations: unknown[] = []\n ) {\n super(message, {\n threat_type: threatType,\n confidence,\n violations,\n });\n this.name = 'ShrikeBlockedError';\n this.threatType = threatType;\n this.confidence = confidence;\n this.violations = violations;\n }\n}\n\n/**\n * Raised when there's a configuration error in the SDK.\n */\nexport class ShrikeConfigError extends ShrikeError {\n constructor(message: string, details: Record<string, unknown> = {}) {\n super(message, details);\n this.name = 'ShrikeConfigError';\n }\n}\n\n/**\n * Raised when the client-side rate limit is exceeded.\n */\nexport class ShrikeRateLimitError extends ShrikeError {\n /** Milliseconds until the next request will be allowed */\n public readonly retryAfterMs: number;\n\n constructor(retryAfterMs: number) {\n super(`Rate limit exceeded. Retry after ${retryAfterMs}ms`, {\n retry_after_ms: retryAfterMs,\n });\n this.name = 'ShrikeRateLimitError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n","/**\n * Response sanitization for IP protection.\n *\n * Mirrors the MCP server's responseFormatter.ts to ensure SDK responses\n * do not expose internal detection methodology, layer details, or patterns.\n */\n\nimport type { ScanResult } from './scanner';\n\n/**\n * Standard threat types exposed to SDK users (matches MCP ThreatType enum).\n */\nconst THREAT_TYPE_MAP: Record<string, string> = {\n // Prompt injection variants\n prompt_injection: 'prompt_injection',\n injection: 'prompt_injection',\n inject: 'prompt_injection',\n instruction_override: 'prompt_injection',\n role_hijacking: 'prompt_injection',\n context_manipulation: 'prompt_injection',\n token_manipulation: 'prompt_injection',\n indirect_injection: 'prompt_injection',\n context_poisoning: 'prompt_injection',\n function_injection: 'prompt_injection',\n memory_injection: 'prompt_injection',\n topic_mismatch: 'prompt_injection',\n // Jailbreak\n jailbreak: 'jailbreak',\n jailbreak_attempt: 'jailbreak',\n safety_bypass: 'jailbreak',\n roleplay: 'jailbreak',\n hypothetical: 'jailbreak',\n completion_baiting: 'jailbreak',\n override: 'jailbreak',\n manipulate: 'jailbreak',\n tonality_drift_profanity: 'jailbreak',\n tonality_drift_casual: 'jailbreak',\n tonality_drift_hostile: 'jailbreak',\n // System prompt leak\n system_prompt_leak: 'system_prompt_leak',\n system_prompt_extraction: 'system_prompt_leak',\n // Data exfiltration\n data_exfiltration: 'data_exfiltration',\n exfiltration: 'data_exfiltration',\n exfiltrate: 'data_exfiltration',\n extract: 'data_exfiltration',\n data_leak: 'data_exfiltration',\n information_disclosure: 'data_exfiltration',\n credential_extraction: 'data_exfiltration',\n // SQL injection\n sql_injection: 'sql_injection',\n sqli: 'sql_injection',\n tautology: 'sql_injection',\n tautology_or: 'sql_injection',\n tautology_and: 'sql_injection',\n union_injection: 'sql_injection',\n stacked_query: 'sql_injection',\n // Path traversal\n path_traversal: 'path_traversal',\n directory_traversal: 'path_traversal',\n path_violation: 'path_traversal',\n file_access: 'path_traversal',\n sensitive_path: 'path_traversal',\n sensitive_extension: 'path_traversal',\n blocked_extension: 'path_traversal',\n // Secrets\n secrets_exposure: 'secrets_exposure',\n secrets: 'secrets_exposure',\n api_key: 'secrets_exposure',\n credential: 'secrets_exposure',\n sensitive_file: 'secrets_exposure',\n content_violation: 'secrets_exposure',\n sensitive_content: 'secrets_exposure',\n secret_key: 'secrets_exposure',\n aws_key: 'secrets_exposure',\n private_key: 'secrets_exposure',\n // PII\n pii_exposure: 'pii_exposure',\n pii: 'pii_exposure',\n pii_leak: 'pii_exposure',\n personal_data: 'pii_exposure',\n pii_in_search: 'pii_exposure',\n pii_extraction: 'pii_exposure',\n ssn: 'pii_exposure',\n credit_card: 'pii_exposure',\n email_exposure: 'pii_exposure',\n phone_number: 'pii_exposure',\n unexpected_pii_leakage: 'pii_exposure',\n // Domain blocking\n blocked_domain: 'blocked_domain',\n suspicious_tld: 'blocked_domain',\n suspicious_domain: 'blocked_domain',\n malicious_url: 'blocked_domain',\n // Toxicity\n toxicity: 'toxicity',\n harmful_content: 'toxicity',\n // Malicious code\n malicious_content: 'malicious_code',\n malicious_code: 'malicious_code',\n reverse_shell: 'malicious_code',\n web_shell: 'malicious_code',\n fork_bomb: 'malicious_code',\n crypto_miner: 'malicious_code',\n persistence: 'malicious_code',\n shell_injection: 'malicious_code',\n // Harmful intent\n harmful_intent: 'harmful_intent',\n dangerous_request: 'harmful_intent',\n // Social engineering\n social_engineering: 'social_engineering',\n emotional: 'social_engineering',\n authority_claim: 'social_engineering',\n // Privilege escalation\n privilege_escalation: 'privilege_escalation',\n // Destructive operation\n destructive_operation: 'destructive_operation',\n // Errors\n scan_error: 'scan_error',\n size_limit_exceeded: 'size_limit_exceeded',\n size_limit: 'size_limit_exceeded',\n timeout: 'scan_error',\n};\n\n/**\n * User-friendly guidance (matches MCP THREAT_GUIDANCE).\n */\nconst THREAT_GUIDANCE: Record<string, string> = {\n prompt_injection:\n 'This prompt contains patterns consistent with instruction override attempts.',\n jailbreak:\n 'This prompt attempts to bypass safety guidelines. The request has been blocked.',\n system_prompt_leak:\n 'The response contains system prompt disclosure. The response has been blocked.',\n data_exfiltration:\n 'This prompt may attempt to extract sensitive information.',\n sql_injection: 'This query contains potentially dangerous SQL patterns.',\n path_traversal:\n 'This file path attempts to access directories outside the allowed scope.',\n secrets_exposure:\n 'This content contains patterns matching API keys, tokens, or credentials.',\n pii_exposure: 'This content contains personally identifiable information.',\n blocked_domain: 'This web search targets a restricted domain.',\n toxicity:\n 'This content contains potentially harmful or inappropriate language.',\n malicious_code:\n 'This content contains patterns associated with malicious code.',\n harmful_intent:\n 'This request contains content associated with harmful intent.',\n social_engineering:\n 'This prompt contains social engineering patterns.',\n privilege_escalation:\n 'This query attempts to escalate privileges or gain unauthorized access.',\n destructive_operation:\n 'This query contains destructive operations. Review carefully.',\n scan_error:\n 'The security scan could not be completed. Blocked as precaution.',\n size_limit_exceeded: 'The content exceeds the maximum allowed size.',\n unknown: 'A security concern was detected. Please review the content.',\n};\n\n/**\n * Default severity per normalized threat type (matches MCP severity semantics).\n * critical > high > medium > low\n */\nconst THREAT_SEVERITY: Record<string, string> = {\n prompt_injection: 'high',\n jailbreak: 'high',\n system_prompt_leak: 'high',\n data_exfiltration: 'high',\n sql_injection: 'critical',\n path_traversal: 'high',\n secrets_exposure: 'critical',\n pii_exposure: 'high',\n blocked_domain: 'medium',\n toxicity: 'medium',\n malicious_code: 'critical',\n harmful_intent: 'high',\n social_engineering: 'medium',\n privilege_escalation: 'critical',\n destructive_operation: 'critical',\n scan_error: 'medium',\n size_limit_exceeded: 'low',\n unknown: 'medium',\n};\n\n/**\n * Fields that expose internal detection methodology - must be stripped.\n */\nconst INTERNAL_FIELDS = new Set([\n 'detected_by',\n 'policy_id',\n 'policy_name',\n 'matched_pattern',\n 'matched_text',\n 'pattern',\n 'scan_stage',\n 'ai_reasoning',\n 'llm_analysis',\n 'performance_metrics',\n 'performance',\n]);\n\n/**\n * Normalize internal threat type to standard enum.\n */\nexport function normalizeThreatType(rawType?: string): string {\n if (!rawType) {\n return 'unknown';\n }\n const normalized = rawType.toLowerCase().replace(/-/g, '_');\n return THREAT_TYPE_MAP[normalized] || 'unknown';\n}\n\n/**\n * Derive severity from threat type or pass through raw severity.\n *\n * If the backend provides a severity, validate and use it.\n * Otherwise, derive from the normalized threat type.\n */\nexport function deriveSeverity(\n threatType: string,\n rawSeverity?: string\n): string {\n const validSeverities = new Set(['critical', 'high', 'medium', 'low']);\n if (rawSeverity && validSeverities.has(rawSeverity.toLowerCase())) {\n return rawSeverity.toLowerCase();\n }\n return THREAT_SEVERITY[threatType] || 'medium';\n}\n\n/**\n * Convert raw confidence score to bucketed level.\n *\n * Protects IP by not exposing exact detection thresholds.\n */\nexport function bucketConfidence(score?: number): string {\n if (score === undefined || score === null) {\n return 'medium';\n }\n if (score >= 0.9) {\n return 'high';\n }\n if (score >= 0.7) {\n return 'medium';\n }\n return 'low';\n}\n\n/**\n * Sanitize a raw backend scan response for IP protection.\n *\n * Strips internal detection details, normalizes threat types,\n * and buckets confidence scores.\n */\nexport function sanitizeScanResponse(raw: ScanResult): ScanResult {\n const safe = raw.safe !== false;\n\n // Pass through approval_info if present (not an IP concern)\n const approvalInfo = raw.approval_info;\n\n if (safe) {\n const result: ScanResult = {\n safe: true,\n reason: raw.reason || '',\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n }\n\n // Unsafe: normalize and sanitize\n const threatType = normalizeThreatType(raw.threat_type);\n const confidence = bucketConfidence(\n typeof raw.confidence === 'number' ? raw.confidence : undefined\n );\n const severity = deriveSeverity(\n threatType,\n typeof raw.severity === 'string' ? raw.severity : undefined\n );\n const guidance =\n THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE['unknown'];\n\n const result: ScanResult = {\n safe: false,\n threat_type: threatType,\n severity: severity,\n confidence: confidence,\n reason: raw.reason || guidance,\n guidance: guidance,\n };\n if (approvalInfo) {\n result.approval_info = approvalInfo;\n }\n return result;\n}\n\nexport { THREAT_TYPE_MAP, THREAT_GUIDANCE, THREAT_SEVERITY, INTERNAL_FIELDS };\n","/**\n * SDK version - keep in sync with package.json\n */\nexport const VERSION = '1.3.1';\n","/**\n * HTTP client for the Shrike scan API.\n * Includes retry with exponential backoff, session correlation,\n * approval workflow, 401 key refresh, and rate limiting.\n */\n\nimport {\n DEFAULT_ENDPOINT,\n DEFAULT_SCAN_TIMEOUT,\n DEFAULT_RATE_LIMIT_PER_MINUTE,\n SDK_NAME,\n RETRY_CONFIG,\n getSessionId,\n getAgentId,\n} from './config';\nimport type { ApprovalInfo } from './config';\nimport { ShrikeRateLimitError } from './errors';\nimport { RateLimiter } from './rateLimiter';\nimport { sanitizeScanResponse } from './sanitizer';\nimport { VERSION } from './version';\n\n/**\n * Phase 8b: Client-side size limits to fail fast before network round-trip.\n * These limits match the backend limits for consistency.\n */\nconst MAX_CONTENT_SIZE = 100 * 1024; // 100KB - matches backend MaxRequestBodySize\n\n/**\n * Result from a scan operation.\n */\n/**\n * Correlation pattern from L9 session-aware correlation engine.\n */\nexport interface CorrelationPattern {\n pattern_id: string;\n pattern_name: string;\n category: string;\n confidence: number;\n description: string;\n}\n\nexport interface ScanResult {\n safe: boolean;\n reason?: string;\n threat_type?: string;\n severity?: string;\n confidence?: number | string;\n violations?: unknown[];\n guidance?: string;\n approval_info?: ApprovalInfo;\n /** L9 session risk score (0.0-1.0), present when session correlation is active */\n session_risk_score?: number;\n /** L9 matched correlation patterns, present when multi-turn patterns are detected */\n correlation_patterns?: CorrelationPattern[];\n [key: string]: unknown;\n}\n\n/**\n * Options for the ScanClient.\n */\nexport interface ScanClientOptions {\n /** Shrike API key for authentication */\n apiKey: string;\n /** Shrike API endpoint URL */\n endpoint?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Rate limit in requests per minute (default: 100) */\n rateLimitPerMinute?: number;\n /** Optional callback to refresh API key on 401. Return new key or null. */\n onKeyRefresh?: () => Promise<string | null>;\n}\n\n/**\n * Generate a UUID v4 string.\n */\nfunction generateUUID(): string {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Generate headers for scan API requests.\n */\nexport function getScanHeaders(\n shrikeApiKey: string,\n requestId?: string\n): Record<string, string> {\n return {\n Authorization: `Bearer ${shrikeApiKey}`,\n 'Content-Type': 'application/json',\n 'X-Shrike-SDK': SDK_NAME,\n 'X-Shrike-SDK-Version': VERSION,\n 'X-Shrike-Request-ID': requestId || generateUUID(),\n };\n}\n\n/** Check if an error is transient and retryable. */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n const cause = 'cause' in error ? String((error as { cause?: unknown }).cause) : '';\n const errorStr = `${error.name} ${error.message} ${cause}`;\n return RETRY_CONFIG.retryableErrors.some((e) => errorStr.includes(e));\n }\n return false;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Fetch with retry for cold-start and transient failure resilience.\n * Matches MCP server's fetchWithRetry pattern.\n */\nasync function fetchWithRetry(\n url: string,\n options: RequestInit,\n timeoutMs: number,\n apiKey: string,\n onKeyRefresh?: () => Promise<string | null>\n): Promise<Response> {\n let lastError: Error | null = null;\n let delay = RETRY_CONFIG.initialDelayMs;\n\n for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n clearTimeout(timeoutId);\n\n // On 401, try key refresh and retry once\n if (response.status === 401 && onKeyRefresh && attempt === 0) {\n const newKey = await onKeyRefresh();\n if (newKey && newKey !== apiKey) {\n const retryController = new AbortController();\n const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);\n try {\n const headers = {\n ...(options.headers as Record<string, string>),\n Authorization: `Bearer ${newKey}`,\n };\n const retryResponse = await fetch(url, {\n ...options,\n headers,\n signal: retryController.signal,\n });\n clearTimeout(retryTimeout);\n return retryResponse;\n } catch {\n clearTimeout(retryTimeout);\n // Fall through to return original 401\n }\n }\n }\n\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry on abort (timeout) or non-retryable errors\n if (lastError.name === 'AbortError' || !isRetryableError(error)) {\n throw lastError;\n }\n\n if (attempt < RETRY_CONFIG.maxRetries) {\n await sleep(delay);\n delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);\n }\n }\n }\n\n throw lastError || new Error('Fetch failed after retries');\n}\n\n/**\n * Build the session context object for backend requests.\n */\nfunction buildSessionContext(extraContext?: Record<string, unknown>): Record<string, unknown> {\n return {\n ...extraContext,\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n };\n}\n\n/**\n * HTTP client for the Shrike scan API.\n */\n/**\n * When running without an API key, appends a signup hint to scan results\n * so agents/users know they can upgrade from regex-only to full scanning.\n */\nexport function maybeAddSignupHint(result: ScanResult, apiKey: string): ScanResult {\n if (apiKey) return result;\n // Don't override if backend already provided upgrade_hint\n if ((result as any).upgrade_hint) return result;\n return {\n ...result,\n _note: 'Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup',\n };\n}\n\nexport class ScanClient {\n private apiKey: string;\n private readonly endpoint: string;\n private readonly timeout: number;\n private readonly rateLimiter: RateLimiter;\n private readonly onKeyRefresh?: () => Promise<string | null>;\n\n constructor(options: ScanClientOptions) {\n this.apiKey = options.apiKey;\n this.endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this.timeout = options.timeout || DEFAULT_SCAN_TIMEOUT;\n this.rateLimiter = new RateLimiter(options.rateLimitPerMinute || DEFAULT_RATE_LIMIT_PER_MINUTE);\n this.onKeyRefresh = options.onKeyRefresh;\n\n if (!this.apiKey) {\n console.warn('[shrike-guard] No API key provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n }\n\n /** Check rate limit before each request. */\n private checkRateLimit(): void {\n const result = this.rateLimiter.consume();\n if (!result.allowed) {\n throw new ShrikeRateLimitError(result.retryAfterMs!);\n }\n }\n\n /**\n * Scan a prompt for security threats.\n *\n * @param prompt - The user prompt to scan.\n * @param context - Optional conversation context for better analysis.\n * @returns Scan result with 'safe' boolean and additional details.\n * @throws Error if the request fails or times out.\n */\n async scan(prompt: string, context?: string): Promise<ScanResult> {\n // Client-side size validation to fail fast\n const totalSize = prompt.length + (context?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const payload: Record<string, unknown> = {\n prompt,\n scan_type: 'full',\n context: buildSessionContext(),\n };\n if (context) {\n payload.conversation_history = context;\n }\n\n const response = await fetchWithRetry(\n `${this.endpoint}/scan`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a SQL query for injection attacks.\n *\n * @param query - The SQL query to scan.\n * @param database - Optional database name for context.\n * @param allowDestructive - If true, allows DROP/TRUNCATE operations.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanSql(\n query: string,\n database?: string,\n allowDestructive = false\n ): Promise<ScanResult> {\n if (query.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `SQL query too large (${Math.round(query.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Query exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const toolContext: Record<string, unknown> = {};\n if (database) {\n toolContext.database = database;\n }\n if (allowDestructive) {\n toolContext.allow_destructive = 'true';\n }\n\n const payload = {\n content: query,\n content_type: 'sql',\n context: buildSessionContext(toolContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`SQL scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan a file path for security risks.\n *\n * @param path - The file path to validate.\n * @param content - Optional file content to scan for secrets/PII.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanFile(path: string, content?: string): Promise<ScanResult> {\n const totalSize = path.length + (content?.length || 0);\n if (totalSize > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `File content too large (${Math.round(totalSize / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const contentType = content ? 'file_content' : 'file_path';\n const extraContext: Record<string, unknown> = {};\n if (content) {\n extraContext.file_content = content;\n }\n\n const payload = {\n content: path,\n content_type: contentType,\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`File scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A (Agent-to-Agent) protocol message for security threats.\n *\n * @param message - The A2A message text content to scan.\n * @param options - Optional context: sender/receiver agent IDs, task ID, role.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanA2AMessage(\n message: string,\n options?: {\n senderAgentId?: string;\n receiverAgentId?: string;\n taskId?: string;\n role?: 'user' | 'agent';\n }\n ): Promise<ScanResult> {\n if (message.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Message too large (${Math.round(message.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (options?.senderAgentId) extraContext.sender_agent_id = options.senderAgentId;\n if (options?.receiverAgentId) extraContext.receiver_agent_id = options.receiverAgentId;\n if (options?.taskId) extraContext.task_id = options.taskId;\n if (options?.role) extraContext.role = options.role;\n\n const payload = {\n content: message,\n content_type: 'a2a_message',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`A2A message scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n\n /**\n * Scan an A2A AgentCard JSON for security threats.\n *\n * @param agentCard - The raw JSON string of the A2A AgentCard to scan.\n * @param verifySignature - Reserved for future JWS signature verification.\n * @returns Scan result with 'safe' boolean and additional details.\n */\n async scanAgentCard(\n agentCard: string,\n verifySignature = false\n ): Promise<ScanResult> {\n if (agentCard.length > MAX_CONTENT_SIZE) {\n return {\n safe: false,\n reason: `Agent card too large (${Math.round(agentCard.length / 1024)}KB > ${MAX_CONTENT_SIZE / 1024}KB limit)`,\n threat_type: 'size_limit_exceeded',\n confidence: 1.0,\n violations: [\n {\n type: 'size_limit',\n description: `Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`,\n },\n ],\n };\n }\n\n this.checkRateLimit();\n\n const extraContext: Record<string, unknown> = {};\n if (verifySignature) extraContext.verify_signature = 'true';\n\n const payload = {\n content: agentCard,\n content_type: 'agent_card',\n context: buildSessionContext(extraContext),\n };\n\n const response = await fetchWithRetry(\n `${this.endpoint}/api/scan/specialized`,\n {\n method: 'POST',\n headers: getScanHeaders(this.apiKey),\n body: JSON.stringify(payload),\n },\n this.timeout,\n this.apiKey,\n this.onKeyRefresh\n );\n\n if (!response.ok) {\n throw new Error(`Agent card scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this.apiKey);\n }\n}\n","/**\n * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.\n */\n\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';\n\nimport {\n FailMode,\n DEFAULT_ENDPOINT,\n DEFAULT_FAIL_MODE,\n DEFAULT_SCAN_TIMEOUT,\n getSessionId,\n getAgentId,\n} from '../config';\nimport { ShrikeBlockedError, ShrikeScanError } from '../errors';\nimport { sanitizeScanResponse } from '../sanitizer';\nimport { getScanHeaders, maybeAddSignupHint, ScanResult } from '../scanner';\n\n/**\n * Simple logger for warnings.\n */\nfunction logWarning(message: string): void {\n console.warn(`⚠️ ${message}`);\n}\n\n/**\n * Content block type for Anthropic messages.\n */\ninterface ContentBlock {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\n/**\n * Anthropic message type.\n */\ninterface AnthropicMessage {\n role: string;\n content: string | ContentBlock[];\n}\n\n/**\n * Options for creating a ShrikeAnthropic client.\n */\nexport interface ShrikeAnthropicOptions {\n /** Anthropic API key. If not provided, uses ANTHROPIC_API_KEY env var. */\n apiKey?: string;\n /** Shrike API key for authentication with the scan service. */\n shrikeApiKey?: string;\n /** Shrike API endpoint URL. */\n shrikeEndpoint?: string;\n /** Behavior on scan failure - 'open' (allow) or 'closed' (block). */\n failMode?: FailMode | 'open' | 'closed';\n /** Timeout for scan requests in milliseconds. */\n scanTimeout?: number;\n /** Additional options passed to the Anthropic client. */\n anthropicOptions?: Record<string, unknown>;\n}\n\n/**\n * Drop-in replacement for Anthropic with Shrike security protection.\n *\n * This class wraps the official Anthropic client and automatically scans\n * all prompts before they are sent to Claude. If a prompt is detected\n * as unsafe, the request is blocked and a ShrikeBlockedError is raised.\n *\n * @example\n * ```typescript\n * import { ShrikeAnthropic } from 'shrike-guard/anthropic';\n *\n * const client = new ShrikeAnthropic({\n * apiKey: 'sk-ant-...',\n * shrikeApiKey: 'shrike-...',\n * });\n *\n * const message = await client.messages.create({\n * model: 'claude-3-opus-20240229',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport class ShrikeAnthropic {\n private _anthropic: Anthropic;\n private _shrikeEndpoint: string;\n private _shrikeApiKey: string;\n private _failMode: FailMode;\n private _scanTimeout: number;\n\n /** Namespace for messages operations */\n public readonly messages: MessagesNamespace;\n\n constructor(options: ShrikeAnthropicOptions = {}) {\n // Dynamically import Anthropic to make it a peer dependency\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const AnthropicClient = require('@anthropic-ai/sdk').default;\n\n this._anthropic = new AnthropicClient({\n apiKey: options.apiKey,\n ...options.anthropicOptions,\n });\n this._shrikeEndpoint = (options.shrikeEndpoint || DEFAULT_ENDPOINT).replace(/\\/$/, '');\n this._shrikeApiKey = options.shrikeApiKey || '';\n this._failMode =\n typeof options.failMode === 'string'\n ? (options.failMode as FailMode)\n : options.failMode || DEFAULT_FAIL_MODE;\n this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;\n\n // Note: All scanning is done via backend API (tier-based: community=L1-L4, pro=L1-L8)\n // No local scanning - backend has full regex patterns (~50+) and normalizers\n\n if (!this._shrikeApiKey) {\n console.warn('[shrike-guard] No shrikeApiKey provided — running in free tier (regex-only).');\n console.warn('[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup');\n }\n\n // Expose messages interface\n this.messages = new MessagesNamespace(this);\n }\n\n /**\n * Extract all user message content from a messages list.\n */\n _extractUserContent(messages: AnthropicMessage[]): string {\n const userContents: string[] = [];\n\n for (const msg of messages) {\n if (msg.role === 'user') {\n if (typeof msg.content === 'string') {\n userContents.push(msg.content);\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content) {\n if (block.type === 'text' && block.text) {\n userContents.push(block.text);\n }\n }\n }\n }\n }\n\n return userContents.join('\\n');\n }\n\n /**\n * Scan user messages for security threats via backend API.\n *\n * Always calls backend - backend handles tier-based scanning:\n * - Community tier (no API key): L1-L4 (regex, unicode, encoding, token normalization)\n * - Pro tier: L1-L8 (full scan including LLM)\n */\n async _scanMessages(messages: AnthropicMessage[]): Promise<ScanResult> {\n const userContent = this._extractUserContent(messages);\n\n if (!userContent.trim()) {\n return { safe: true, reason: 'No user content to scan' };\n }\n\n // Always call backend API - tier detection happens server-side\n return this._remoteScan(userContent);\n }\n\n /**\n * Full scan via Shrike backend API.\n * Backend handles tier-based scanning automatically based on API key presence.\n */\n private async _remoteScan(prompt: string): Promise<ScanResult> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);\n\n try {\n const response = await fetch(`${this._shrikeEndpoint}/scan`, {\n method: 'POST',\n headers: getScanHeaders(this._shrikeApiKey),\n body: JSON.stringify({\n prompt,\n context: {\n session_id: getSessionId(),\n agent_id: getAgentId(),\n source_application: 'shrike-guard-ts',\n },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan API error: ${response.status}` };\n }\n throw new ShrikeScanError(`Scan API returned error: ${response.status}`);\n }\n\n return maybeAddSignupHint(sanitizeScanResponse((await response.json()) as ScanResult), this._shrikeApiKey);\n } catch (error) {\n if (error instanceof ShrikeScanError) {\n throw error;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n if (errorMessage.includes('abort')) {\n if (this._failMode === FailMode.OPEN) {\n // No local fallback - just fail open\n logWarning('Scan request timed out, failing open (allowing request)');\n return { safe: true, reason: 'Scan timeout, failing open' };\n }\n throw new ShrikeScanError(\"Scan request timed out and fail_mode is 'closed'\");\n }\n\n if (this._failMode === FailMode.OPEN) {\n return { safe: true, reason: `Scan error: ${errorMessage}` };\n }\n throw new ShrikeScanError(`Scan failed: ${errorMessage}`);\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /** Access the underlying Anthropic client */\n get anthropic(): Anthropic {\n return this._anthropic;\n }\n}\n\n/**\n * Namespace for messages operations.\n */\nclass MessagesNamespace {\n private _client: ShrikeAnthropic;\n\n constructor(client: ShrikeAnthropic) {\n this._client = client;\n }\n\n /**\n * Create a message with security scanning.\n *\n * This method scans the user messages before sending them to Anthropic.\n * If the scan detects a security threat, a ShrikeBlockedError is raised.\n */\n async create(params: MessageCreateParams): Promise<Message> {\n // 1. Scan messages for security threats\n const scanResult = await this._client._scanMessages(\n params.messages as unknown as AnthropicMessage[]\n );\n\n // 2. Block if unsafe\n if (!scanResult.safe) {\n throw new ShrikeBlockedError(\n `Request blocked: ${scanResult.reason || 'Security threat detected'}`,\n scanResult.threat_type,\n scanResult.confidence,\n scanResult.violations || []\n );\n }\n\n // 3. Proxy to Anthropic (non-streaming)\n return this._client.anthropic.messages.create({\n ...params,\n stream: false,\n }) as unknown as Promise<Message>;\n }\n}\n"]}
@@ -262,7 +262,7 @@ function sanitizeScanResponse(raw) {
262
262
  }
263
263
 
264
264
  // src/version.ts
265
- var VERSION = "1.0.1";
265
+ var VERSION = "1.3.1";
266
266
  function generateUUID() {
267
267
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
268
268
  const r = Math.random() * 16 | 0;
@@ -279,6 +279,14 @@ function getScanHeaders(shrikeApiKey, requestId) {
279
279
  "X-Shrike-Request-ID": generateUUID()
280
280
  };
281
281
  }
282
+ function maybeAddSignupHint(result, apiKey) {
283
+ if (apiKey) return result;
284
+ if (result.upgrade_hint) return result;
285
+ return {
286
+ ...result,
287
+ _note: "Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup"
288
+ };
289
+ }
282
290
 
283
291
  // src/anthropic/client.ts
284
292
  function logWarning(message) {
@@ -295,6 +303,10 @@ var ShrikeAnthropic = class {
295
303
  this._shrikeApiKey = options.shrikeApiKey || "";
296
304
  this._failMode = typeof options.failMode === "string" ? options.failMode : options.failMode || DEFAULT_FAIL_MODE;
297
305
  this._scanTimeout = options.scanTimeout || DEFAULT_SCAN_TIMEOUT;
306
+ if (!this._shrikeApiKey) {
307
+ console.warn("[shrike-guard] No shrikeApiKey provided \u2014 running in free tier (regex-only).");
308
+ console.warn("[shrike-guard] For full scanning (LLM analysis, session correlation): npx shrike-mcp --signup");
309
+ }
298
310
  this.messages = new MessagesNamespace(this);
299
311
  }
300
312
  /**
@@ -358,7 +370,7 @@ var ShrikeAnthropic = class {
358
370
  }
359
371
  throw new ShrikeScanError(`Scan API returned error: ${response.status}`);
360
372
  }
361
- return sanitizeScanResponse(await response.json());
373
+ return maybeAddSignupHint(sanitizeScanResponse(await response.json()), this._shrikeApiKey);
362
374
  } catch (error) {
363
375
  if (error instanceof ShrikeScanError) {
364
376
  throw error;