shrike-guard 1.3.3 → 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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Node.js 18+](https://img.shields.io/badge/node-18+-green.svg)](https://nodejs.org/)
5
5
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
6
 
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.
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.
8
8
 
9
9
  ## Features
10
10
 
@@ -15,14 +15,14 @@
15
15
  - Jailbreak attempts
16
16
  - SQL injection
17
17
  - Path traversal
18
- - **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
19
19
  - **CJS + ESM**: Dual build via tsup, works everywhere
20
20
  - **Subpath imports**: `shrike-guard/openai`, `shrike-guard/anthropic`, `shrike-guard/gemini`
21
21
  - **Zero code changes**: Just replace your import
22
22
 
23
23
  ## What Shrike Detects
24
24
 
25
- Shrike's backend runs a multi-stage detection pipeline with security rules across **7 compliance frameworks**:
25
+ Shrike's 9-layer cognitive pipeline includes sensitive-data detection aligned to 5 major regulatory frameworks:
26
26
 
27
27
  | Framework | Coverage |
28
28
  |-----------|----------|
@@ -31,10 +31,8 @@ Shrike's backend runs a multi-stage detection pipeline with security rules acros
31
31
  | **ISO 27001** | Information security — passwords, tokens, certificates |
32
32
  | **SOC 2** | Secrets, credentials, API keys, cloud tokens |
33
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
34
 
37
- Plus built-in detection for prompt injection, jailbreaks, social engineering, and dangerous requests.
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.
38
36
 
39
37
  ### Tiers
40
38
 
@@ -42,7 +40,7 @@ Detection depth depends on your tier. All tiers get the same SDK wrappers — ti
42
40
 
43
41
  | | Anonymous | Community | Pro | Enterprise |
44
42
  |---|---|---|---|---|
45
- | Detection Layers | L1-L5 | L1-L7 | L1-L8 | L1-L9 |
43
+ | Detection Layers | L1-L5 | L1-L7 | L1-L9 (full) | L1-L9 (full) |
46
44
  | API Key | Not needed | Free signup | Paid | Paid |
47
45
  | Rate Limit | — | 10/min | 100/min | 1,000/min |
48
46
  | Scans/month | — | 1,000 | 25,000 | 1,000,000 |
@@ -128,20 +126,22 @@ Choose how the SDK behaves when the security scan fails (timeout, network error,
128
126
  ```typescript
129
127
  import { ShrikeOpenAI } from 'shrike-guard/openai';
130
128
 
131
- // Fail-open (default): Allow requests if scan fails
132
- // 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.
133
132
  const client = new ShrikeOpenAI({
134
133
  apiKey: 'sk-...',
135
134
  shrikeApiKey: 'shrike-...',
136
- failMode: 'open',
135
+ failMode: 'closed', // This is the default
137
136
  });
138
137
 
139
- // Fail-closed: Block requests if scan fails
140
- // Best for: Security-critical applications
141
- 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({
142
142
  apiKey: 'sk-...',
143
143
  shrikeApiKey: 'shrike-...',
144
- failMode: 'closed',
144
+ failMode: 'open',
145
145
  });
146
146
  ```
147
147
 
@@ -302,9 +302,9 @@ Looking for an AI security SDK? Here's how Shrike Guard compares:
302
302
  | Feature | Shrike Guard | Lakera | Prompt Armor |
303
303
  |---|---|---|---|
304
304
  | Drop-in OpenAI/Anthropic/Gemini wrapper | Yes | No | No |
305
- | Multi-layered evaluation pipeline | Yes | Limited | Limited |
305
+ | 9-layer cognitive pipeline | Yes | Limited | Limited |
306
306
  | PII detection + redaction | Yes | Partial | No |
307
- | Session correlation | Yes (Enterprise) | No | No |
307
+ | Session correlation | Yes (Pro+) | No | No |
308
308
  | Free tier (no API key) | Yes | No | No |
309
309
  | Open source client | Yes (Apache 2.0) | No | No |
310
310
 
@@ -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-CkCXFY3H.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-CkCXFY3H.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,6 +347,18 @@ 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;
286
364
  if (result.upgrade_hint) return result;
@@ -353,7 +431,7 @@ var ShrikeAnthropic = class {
353
431
  const controller = new AbortController();
354
432
  const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);
355
433
  try {
356
- const response = await fetch(`${this._shrikeEndpoint}/scan`, {
434
+ const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {
357
435
  method: "POST",
358
436
  headers: getScanHeaders(this._shrikeApiKey),
359
437
  body: JSON.stringify({
@@ -412,7 +490,7 @@ var MessagesNamespace = class {
412
490
  const scanResult = await this._client._scanMessages(
413
491
  params.messages
414
492
  );
415
- if (!scanResult.safe) {
493
+ if (isBlocked(scanResult)) {
416
494
  throw new ShrikeBlockedError(
417
495
  `Request blocked: ${scanResult.reason || "Security threat detected"}`,
418
496
  scanResult.threat_type,