shrike-guard 1.3.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 9-layer cognitive pipeline evaluates every interaction.
12
8
 
13
9
  ## Features
14
10
 
@@ -19,11 +15,38 @@
19
15
  - Jailbreak attempts
20
16
  - SQL injection
21
17
  - Path traversal
22
- - **Fail-safe modes**: Choose between fail-open (default) or fail-closed behavior
18
+ - **Fail-safe modes**: Defaults to fail-closed (Zero Trust posture); opt into fail-open explicitly when availability outranks enforcement
23
19
  - **CJS + ESM**: Dual build via tsup, works everywhere
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 9-layer cognitive pipeline includes sensitive-data detection aligned to 5 major regulatory 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
+
35
+ Detection coverage is not a certification claim — see [shrikesecurity.com/compliance](https://shrikesecurity.com/compliance) for our current certification status. Plus built-in detection for prompt injection, jailbreaks, social engineering, and dangerous requests.
36
+
37
+ ### Tiers
38
+
39
+ Detection depth depends on your tier. All tiers get the same SDK wrappers — tiers control which backend layers run.
40
+
41
+ | | Anonymous | Community | Pro | Enterprise |
42
+ |---|---|---|---|---|
43
+ | Detection Layers | L1-L5 | L1-L7 | L1-L9 (full) | L1-L9 (full) |
44
+ | API Key | Not needed | Free signup | Paid | Paid |
45
+ | Rate Limit | — | 10/min | 100/min | 1,000/min |
46
+ | Scans/month | — | 1,000 | 25,000 | 1,000,000 |
47
+
48
+ **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.
49
+
27
50
  ## Installation
28
51
 
29
52
  ```bash
@@ -103,20 +126,22 @@ Choose how the SDK behaves when the security scan fails (timeout, network error,
103
126
  ```typescript
104
127
  import { ShrikeOpenAI } from 'shrike-guard/openai';
105
128
 
106
- // Fail-open (default): Allow requests if scan fails
107
- // Best for: Most applications where availability is important
129
+ // Fail-closed (default): Block requests if scan fails
130
+ // Best for: Production security workloads. If the Shrike backend is down,
131
+ // the SDK raises ShrikeScanError instead of allowing traffic through unguarded.
108
132
  const client = new ShrikeOpenAI({
109
133
  apiKey: 'sk-...',
110
134
  shrikeApiKey: 'shrike-...',
111
- failMode: 'open',
135
+ failMode: 'closed', // This is the default
112
136
  });
113
137
 
114
- // Fail-closed: Block requests if scan fails
115
- // Best for: Security-critical applications
116
- const strictClient = new ShrikeOpenAI({
138
+ // Fail-open: Allow requests if scan fails
139
+ // Best for: Non-production experiments, internal tools where availability must
140
+ // outrank enforcement. Trades the guard's enforcement promise for uptime.
141
+ const permissiveClient = new ShrikeOpenAI({
117
142
  apiKey: 'sk-...',
118
143
  shrikeApiKey: 'shrike-...',
119
- failMode: 'closed',
144
+ failMode: 'open',
120
145
  });
121
146
  ```
122
147
 
@@ -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
+ | 9-layer cognitive pipeline | Yes | Limited | Limited |
306
+ | PII detection + redaction | Yes | Partial | No |
307
+ | Session correlation | Yes (Pro+) | 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-Bz7j8OSD.mjs';
3
+ import { F as FailMode, S as ScanResult } from '../scanner-RWQR_jcB.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-Bz7j8OSD.js';
3
+ import { F as FailMode, S as ScanResult } from '../scanner-RWQR_jcB.js';
4
4
 
5
5
  /**
6
6
  * ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
@@ -74,9 +74,11 @@ var THREAT_TYPE_MAP = {
74
74
  completion_baiting: "jailbreak",
75
75
  override: "jailbreak",
76
76
  manipulate: "jailbreak",
77
- tonality_drift_profanity: "jailbreak",
77
+ // L8 tonality drift — backend normalizes profanity + hostile as toxic_content;
78
+ // casual stays as jailbreak (matches platform/common/models/response.go:309-313).
79
+ tonality_drift_profanity: "toxic_content",
80
+ tonality_drift_hostile: "toxic_content",
78
81
  tonality_drift_casual: "jailbreak",
79
- tonality_drift_hostile: "jailbreak",
80
82
  // System prompt leak
81
83
  system_prompt_leak: "system_prompt_leak",
82
84
  system_prompt_extraction: "system_prompt_leak",
@@ -132,9 +134,11 @@ var THREAT_TYPE_MAP = {
132
134
  suspicious_tld: "blocked_domain",
133
135
  suspicious_domain: "blocked_domain",
134
136
  malicious_url: "blocked_domain",
135
- // Toxicity
136
- toxicity: "toxicity",
137
- harmful_content: "toxicity",
137
+ // Toxic content (canonical name as of the L7 rewrite). `toxicity` kept as a
138
+ // legacy alias so older backend builds + customer code don't break.
139
+ toxic_content: "toxic_content",
140
+ toxicity: "toxic_content",
141
+ harmful_content: "toxic_content",
138
142
  // Malicious code
139
143
  malicious_content: "malicious_code",
140
144
  malicious_code: "malicious_code",
@@ -155,6 +159,12 @@ var THREAT_TYPE_MAP = {
155
159
  privilege_escalation: "privilege_escalation",
156
160
  // Destructive operation
157
161
  destructive_operation: "destructive_operation",
162
+ // L9 multi-turn correlation — the pseudo-category emitted when any
163
+ // multi_turn_* pattern fires (crescendo, blocked_retry, trust-building,
164
+ // memory_poisoning, etc.). The normalizeThreatType prefix check below
165
+ // handles the specific pattern names; this entry catches the pseudo
166
+ // value if the backend pre-normalizes.
167
+ multi_turn_attack: "multi_turn_attack",
158
168
  // Errors
159
169
  scan_error: "scan_error",
160
170
  size_limit_exceeded: "size_limit_exceeded",
@@ -171,7 +181,9 @@ var THREAT_GUIDANCE = {
171
181
  secrets_exposure: "This content contains patterns matching API keys, tokens, or credentials.",
172
182
  pii_exposure: "This content contains personally identifiable information.",
173
183
  blocked_domain: "This web search targets a restricted domain.",
184
+ toxic_content: "This content contains potentially harmful or inappropriate language.",
174
185
  toxicity: "This content contains potentially harmful or inappropriate language.",
186
+ multi_turn_attack: "A pattern was detected across multiple turns of this session that suggests a coordinated attempt to bypass safety controls.",
175
187
  malicious_code: "This content contains patterns associated with malicious code.",
176
188
  harmful_intent: "This request contains content associated with harmful intent.",
177
189
  social_engineering: "This prompt contains social engineering patterns.",
@@ -191,7 +203,9 @@ var THREAT_SEVERITY = {
191
203
  secrets_exposure: "critical",
192
204
  pii_exposure: "high",
193
205
  blocked_domain: "medium",
206
+ toxic_content: "medium",
194
207
  toxicity: "medium",
208
+ multi_turn_attack: "high",
195
209
  malicious_code: "critical",
196
210
  harmful_intent: "high",
197
211
  social_engineering: "medium",
@@ -201,12 +215,31 @@ var THREAT_SEVERITY = {
201
215
  size_limit_exceeded: "low",
202
216
  unknown: "medium"
203
217
  };
218
+ var INTERNAL_FIELDS = /* @__PURE__ */ new Set([
219
+ "detected_by",
220
+ "policy_id",
221
+ "policy_name",
222
+ "matched_pattern",
223
+ "matched_text",
224
+ "pattern",
225
+ "scan_stage",
226
+ "ai_reasoning",
227
+ "llm_analysis",
228
+ "performance_metrics",
229
+ "performance"
230
+ ]);
204
231
  function normalizeThreatType(rawType) {
205
232
  if (!rawType) {
206
233
  return "unknown";
207
234
  }
208
235
  const normalized = rawType.toLowerCase().replace(/-/g, "_");
209
- return THREAT_TYPE_MAP[normalized] || "unknown";
236
+ if (THREAT_TYPE_MAP[normalized]) {
237
+ return THREAT_TYPE_MAP[normalized];
238
+ }
239
+ if (normalized.startsWith("multi_turn_")) {
240
+ return "multi_turn_attack";
241
+ }
242
+ return "unknown";
210
243
  }
211
244
  function deriveSeverity(threatType, rawSeverity) {
212
245
  const validSeverities = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
@@ -227,20 +260,59 @@ function bucketConfidence(score) {
227
260
  }
228
261
  return "low";
229
262
  }
263
+ var PRESERVED_GOVERNANCE_FIELDS = [
264
+ "action",
265
+ "refuse_tier",
266
+ "recovery",
267
+ "session_state",
268
+ "content_type",
269
+ "approval_info",
270
+ "client_session_rotation"
271
+ ];
272
+ function sanitizeViolation(raw) {
273
+ if (!raw || typeof raw !== "object") {
274
+ return null;
275
+ }
276
+ const source = raw;
277
+ const out = {};
278
+ for (const [k, v] of Object.entries(source)) {
279
+ if (!INTERNAL_FIELDS.has(k)) {
280
+ out[k] = v;
281
+ }
282
+ }
283
+ return out;
284
+ }
230
285
  function sanitizeScanResponse(raw) {
231
286
  const safe = raw.safe !== false;
232
- const approvalInfo = raw.approval_info;
287
+ const result = { safe };
288
+ for (const field of PRESERVED_GOVERNANCE_FIELDS) {
289
+ const value = raw[field];
290
+ if (value !== void 0 && value !== null) {
291
+ result[field] = value;
292
+ }
293
+ }
294
+ const rawViolations = raw.violations;
295
+ if (Array.isArray(rawViolations) && rawViolations.length > 0) {
296
+ const cleaned = rawViolations.map((v) => sanitizeViolation(v)).filter((v) => v !== null);
297
+ if (cleaned.length > 0) {
298
+ result.violations = cleaned;
299
+ }
300
+ }
233
301
  if (safe) {
234
- const result2 = {
235
- safe: true,
236
- reason: raw.reason || ""
237
- };
238
- if (approvalInfo) {
239
- result2.approval_info = approvalInfo;
302
+ result.reason = raw.reason || "";
303
+ return result;
304
+ }
305
+ let rawThreatType = raw.threat_type;
306
+ if (!rawThreatType && Array.isArray(rawViolations) && rawViolations.length > 0) {
307
+ const first = rawViolations[0];
308
+ if (first && typeof first === "object" && !Array.isArray(first)) {
309
+ const firstThreat = first.threat_type;
310
+ if (typeof firstThreat === "string") {
311
+ rawThreatType = firstThreat;
312
+ }
240
313
  }
241
- return result2;
242
314
  }
243
- const threatType = normalizeThreatType(raw.threat_type);
315
+ const threatType = normalizeThreatType(rawThreatType);
244
316
  const confidence = bucketConfidence(
245
317
  typeof raw.confidence === "number" ? raw.confidence : void 0
246
318
  );
@@ -249,22 +321,16 @@ function sanitizeScanResponse(raw) {
249
321
  typeof raw.severity === "string" ? raw.severity : void 0
250
322
  );
251
323
  const guidance = THREAT_GUIDANCE[threatType] || THREAT_GUIDANCE["unknown"];
252
- const result = {
253
- safe: false,
254
- threat_type: threatType,
255
- severity,
256
- confidence,
257
- reason: raw.reason || guidance,
258
- guidance
259
- };
260
- if (approvalInfo) {
261
- result.approval_info = approvalInfo;
262
- }
324
+ result.threat_type = threatType;
325
+ result.severity = severity;
326
+ result.confidence = confidence;
327
+ result.reason = raw.reason || guidance;
328
+ result.guidance = guidance;
263
329
  return result;
264
330
  }
265
331
 
266
332
  // src/version.ts
267
- var VERSION = "1.3.1";
333
+ var VERSION = "1.5.0";
268
334
  function generateUUID() {
269
335
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
270
336
  const r = Math.random() * 16 | 0;
@@ -281,11 +347,24 @@ function getScanHeaders(shrikeApiKey, requestId) {
281
347
  "X-Shrike-Request-ID": generateUUID()
282
348
  };
283
349
  }
350
+ function isBlocked(verdict) {
351
+ const action = verdict.action;
352
+ if (typeof action === "string" && action.length > 0) {
353
+ if (action === "allow" || action === "warn") return false;
354
+ if (action === "block" || action === "require_approval") return true;
355
+ }
356
+ const safe = verdict.safe;
357
+ if (typeof safe === "boolean") {
358
+ return !safe;
359
+ }
360
+ return false;
361
+ }
284
362
  function maybeAddSignupHint(result, apiKey) {
285
363
  if (apiKey) return result;
364
+ if (result.upgrade_hint) return result;
286
365
  return {
287
366
  ...result,
288
- _note: "Running without API key (regex-only). For LLM-powered analysis and session correlation, run: npx shrike-mcp --signup"
367
+ _note: "Running without API key (L1-L5 only). Register free for cognitive threat detection: npx shrike-mcp --signup"
289
368
  };
290
369
  }
291
370
 
@@ -352,7 +431,7 @@ var ShrikeAnthropic = class {
352
431
  const controller = new AbortController();
353
432
  const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);
354
433
  try {
355
- const response = await fetch(`${this._shrikeEndpoint}/scan`, {
434
+ const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {
356
435
  method: "POST",
357
436
  headers: getScanHeaders(this._shrikeApiKey),
358
437
  body: JSON.stringify({
@@ -411,7 +490,7 @@ var MessagesNamespace = class {
411
490
  const scanResult = await this._client._scanMessages(
412
491
  params.messages
413
492
  );
414
- if (!scanResult.safe) {
493
+ if (isBlocked(scanResult)) {
415
494
  throw new ShrikeBlockedError(
416
495
  `Request blocked: ${scanResult.reason || "Security threat detected"}`,
417
496
  scanResult.threat_type,