shrike-guard 1.3.3 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/README.md +16 -16
- package/dist/anthropic/index.d.mts +1 -1
- package/dist/anthropic/index.d.ts +1 -1
- package/dist/anthropic/index.js +113 -32
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs +113 -32
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.d.mts +4 -98
- package/dist/api/index.d.ts +4 -98
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +1 -1
- package/dist/api/index.mjs.map +1 -1
- package/dist/auth-DotJtJHE.d.mts +97 -0
- package/dist/auth-DotJtJHE.d.ts +97 -0
- package/dist/gemini/index.d.mts +36 -20
- package/dist/gemini/index.d.ts +36 -20
- package/dist/gemini/index.js +130 -46
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs +130 -46
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.d.mts +535 -8
- package/dist/index.d.ts +535 -8
- package/dist/index.js +946 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +930 -33
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.d.mts +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.js +119 -38
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/index.mjs +119 -38
- package/dist/openai/index.mjs.map +1 -1
- package/dist/scanner-cxA3LnQR.d.mts +313 -0
- package/dist/scanner-cxA3LnQR.d.ts +313 -0
- package/package.json +11 -9
- package/dist/scanner-CkCXFY3H.d.mts +0 -166
- package/dist/scanner-CkCXFY3H.d.ts +0 -166
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [4.0.2] - 2026-07-16
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
- **Fail-open verdicts now carry `degraded: true` on every provider wrapper.** Previously the OpenAI, Anthropic, and Gemini wrappers returned a plain allow verdict when `failMode: 'open'` was set and the backend was unreachable, so a caller could not distinguish "scanned and clean" from "not scanned, enforcement skipped." All fail-open returns now route through a single `failOpenResult()` constructor, and `degraded?: boolean` is part of the exported `ScanResult` type. The default remains fail-**closed**; this affects only callers who explicitly opt into fail-open.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **Gemini wrapper now works against `@google/genai`.** It previously required `@google/genai` but instantiated the legacy `@google/generative-ai` class (`GoogleGenerativeAI`) and called `getGenerativeModel()`, so it threw at construction against the package it declared. Migrated to the current v2 SDK surface (`GoogleGenAI` + `ai.models.generateContent(...)` / `ai.chats.create(...)`); the public `getGenerativeModel().generateContent()` facade is unchanged. Dependency raised to `@google/genai >=1.0.0` (tested against 2.12). Added a real-package shape test so an SDK rename can't silently ship broken again.
|
|
10
|
+
- Corrected the `ShrikeScanError` docstring, which incorrectly described `fail_mode='open'` as the default. The default is `fail_mode='closed'` (secure by default across the 4.x line) — behavior unchanged; documentation-only. (Supersedes the unreleased 4.0.1.)
|
|
11
|
+
|
|
12
|
+
## [4.0.0] - 2026-07-13
|
|
13
|
+
|
|
14
|
+
### Version alignment (no breaking changes)
|
|
15
|
+
All Shrike client surfaces — MCP server (`shrike-mcp`), TypeScript SDK, and Python SDK — now share a single version line starting at 4.0.0. The major-version jump from 1.5.0 signals the alignment, not an API break: every 1.5.0 program runs unchanged on 4.0.0.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Client-side auto-chunk for large inputs.** Prompts over 20KB are automatically split on natural boundaries (paragraph → line → hard cut) into ~8KB chunks and scanned sequentially with fail-fast on the first blocking verdict. Applies to `ScanClient.scan()`. New `chunker` module exports `AUTO_CHUNK_THRESHOLD`, `CHUNK_TARGET_SIZE`, `chunkContent()`, and `aggregateChunkResults()` for callers who want to drive chunking directly.
|
|
19
|
+
- **Aggregation contract:** worst-action wins across chunks; violations dedup by (threat_type, severity, action); `session_state` from the last scanned chunk; `recovery` from the first blocking chunk.
|
|
20
|
+
|
|
21
|
+
### Why
|
|
22
|
+
Large single-shot scan inputs (agent transcripts, RAG context dumps, file contents) previously rode through the cascade as one oversized request — slower verdicts and more LLM-layer tokens burned per scan. Chunked inputs land in the backend cascade's small-content band, which allows earlier early-exit on clean content and cheaper verdicts on both sides of the wire. Fail-fast on the first blocked chunk means a threat at the top of a large document blocks without paying to scan the rest.
|
|
23
|
+
|
|
24
|
+
## [1.5.0] - 2026-07-06
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Resilience primitives — `CircuitBreaker`, `CircuitOpenError`, `CircuitState`, `retryWithBackoff`** (mirrors Python SDK). Three-state breaker with configurable `failureThreshold` (5), `successThreshold` (2), `timeout` (30s), and `maxHalfOpenRequests` (3). Under a partial backend outage the breaker short-circuits repeated failures fast rather than letting `fail_mode: "closed"` produce a blocked-response storm. `retryWithBackoff` treats `CircuitOpenError` as non-retryable by default.
|
|
28
|
+
- **`AuthClient` re-exported at package root** (previously only reachable via `shrike-guard/api`). Matches the Python SDK's placement — `import { AuthClient } from 'shrike-guard'` now works.
|
|
29
|
+
|
|
30
|
+
### Why
|
|
31
|
+
Cross-language SDK parity. The Python SDK ships `CircuitBreaker` + `retry_with_backoff`; this brings the TypeScript SDK to feature-equivalence so customers flipping between languages see identical failure semantics under real outage conditions.
|
|
32
|
+
|
|
33
|
+
## [1.4.0] - 2026-07-02
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- **Client-side PII redaction.** `redactPII()`, `rehydratePII()`, `getRedactionSummary()`, `updatePIIPatterns()`, and `getPIIPatternCount()` exported from the root entry point. Detects and tokenizes 20+ PII types (SSN, credit card, email, phone, address, medical record, wallet, etc.) BEFORE the prompt leaves customer environment — Shrike backend never sees the raw PII.
|
|
37
|
+
- **`syncPIIPatterns({ endpoint, apiKey })`** — one-shot startup call fetches the canonical Presidio-derived pattern set from the Shrike backend so client-side detection stays uniform with the server-side scan. Fails safe: on any error (network / timeout / malformed) the bootstrap patterns stay in place; sync never blocks scans.
|
|
38
|
+
- **Backend-owned prefix contract.** The recognizer ships its client-side redaction tag (e.g. `[IP_1]`) as part of the pattern payload; the client uses it verbatim. When the backend omits the field (pre-2026-07-02 releases), the SDK derives the prefix from the threat_type. No pattern is ever silently dropped for an unmapped prefix. Adding a new backend pattern requires zero SDK changes.
|
|
39
|
+
|
|
40
|
+
### Why
|
|
41
|
+
Client-side redaction removes PII before it crosses the network to Shrike's backend — defense-in-depth for HIPAA / PCI / GLBA / CMMC workloads on top of the BAA/DPA that already covers transmission. The `syncPIIPatterns` step keeps client patterns in step with backend patterns without shipping SDK updates every time Presidio adds a recognizer.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- gemini-client test mock import matches the runtime `@google/genai` package (upstream renamed from `@google/generative-ai`).
|
|
45
|
+
|
|
46
|
+
## [1.1.0] - 2026-02-19
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- "What Shrike Detects" section in README: 86+ rules across 6 compliance frameworks
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- Updated backend tier description: all tiers now get full 9-layer cascade (L1-L8)
|
|
54
|
+
|
|
55
|
+
## [1.0.1] - 2026-01-20
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- Minor bug fixes and documentation updates
|
|
59
|
+
|
|
60
|
+
## [1.0.0] - 2026-01-15
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- Initial release
|
|
64
|
+
- Drop-in OpenAI, Anthropic, and Gemini client wrappers
|
|
65
|
+
- Automatic prompt scanning via Shrike backend
|
|
66
|
+
- Fail-open and fail-closed modes
|
|
67
|
+
- Subpath imports (shrike-guard/openai, /anthropic, /gemini)
|
|
68
|
+
- SQL injection and file scanning
|
|
69
|
+
- Response sanitization (IP protection)
|
|
70
|
+
- API management clients (auth, policies, agents, sandbox)
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](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
|
|
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**:
|
|
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
|
|
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-
|
|
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-
|
|
132
|
-
// Best for:
|
|
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: '
|
|
135
|
+
failMode: 'closed', // This is the default
|
|
137
136
|
});
|
|
138
137
|
|
|
139
|
-
// Fail-
|
|
140
|
-
// Best for:
|
|
141
|
-
|
|
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: '
|
|
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
|
-
|
|
|
305
|
+
| 9-layer cognitive pipeline | Yes | Limited | Limited |
|
|
306
306
|
| PII detection + redaction | Yes | Partial | No |
|
|
307
|
-
| Session correlation | Yes (
|
|
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-
|
|
3
|
+
import { F as FailMode, S as ScanResult } from '../scanner-cxA3LnQR.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import { MessageCreateParams, Message } from '@anthropic-ai/sdk/resources/messages';
|
|
3
|
-
import { F as FailMode, S as ScanResult } from '../scanner-
|
|
3
|
+
import { F as FailMode, S as ScanResult } from '../scanner-cxA3LnQR.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
package/dist/anthropic/index.js
CHANGED
|
@@ -74,9 +74,11 @@ var THREAT_TYPE_MAP = {
|
|
|
74
74
|
completion_baiting: "jailbreak",
|
|
75
75
|
override: "jailbreak",
|
|
76
76
|
manipulate: "jailbreak",
|
|
77
|
-
|
|
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
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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(
|
|
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,19 @@ 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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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.
|
|
333
|
+
var VERSION = "1.5.0";
|
|
334
|
+
function failOpenResult(reason) {
|
|
335
|
+
return { safe: true, reason, degraded: true };
|
|
336
|
+
}
|
|
268
337
|
function generateUUID() {
|
|
269
338
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
270
339
|
const r = Math.random() * 16 | 0;
|
|
@@ -281,6 +350,18 @@ function getScanHeaders(shrikeApiKey, requestId) {
|
|
|
281
350
|
"X-Shrike-Request-ID": generateUUID()
|
|
282
351
|
};
|
|
283
352
|
}
|
|
353
|
+
function isBlocked(verdict) {
|
|
354
|
+
const action = verdict.action;
|
|
355
|
+
if (typeof action === "string" && action.length > 0) {
|
|
356
|
+
if (action === "allow" || action === "warn") return false;
|
|
357
|
+
if (action === "block" || action === "require_approval") return true;
|
|
358
|
+
}
|
|
359
|
+
const safe = verdict.safe;
|
|
360
|
+
if (typeof safe === "boolean") {
|
|
361
|
+
return !safe;
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
284
365
|
function maybeAddSignupHint(result, apiKey) {
|
|
285
366
|
if (apiKey) return result;
|
|
286
367
|
if (result.upgrade_hint) return result;
|
|
@@ -353,7 +434,7 @@ var ShrikeAnthropic = class {
|
|
|
353
434
|
const controller = new AbortController();
|
|
354
435
|
const timeoutId = setTimeout(() => controller.abort(), this._scanTimeout);
|
|
355
436
|
try {
|
|
356
|
-
const response = await fetch(`${this._shrikeEndpoint}/scan`, {
|
|
437
|
+
const response = await fetch(`${this._shrikeEndpoint}/api/scan/enforce`, {
|
|
357
438
|
method: "POST",
|
|
358
439
|
headers: getScanHeaders(this._shrikeApiKey),
|
|
359
440
|
body: JSON.stringify({
|
|
@@ -368,7 +449,7 @@ var ShrikeAnthropic = class {
|
|
|
368
449
|
});
|
|
369
450
|
if (!response.ok) {
|
|
370
451
|
if (this._failMode === "open" /* OPEN */) {
|
|
371
|
-
return
|
|
452
|
+
return failOpenResult(`Scan API error: ${response.status}`);
|
|
372
453
|
}
|
|
373
454
|
throw new ShrikeScanError(`Scan API returned error: ${response.status}`);
|
|
374
455
|
}
|
|
@@ -381,12 +462,12 @@ var ShrikeAnthropic = class {
|
|
|
381
462
|
if (errorMessage.includes("abort")) {
|
|
382
463
|
if (this._failMode === "open" /* OPEN */) {
|
|
383
464
|
logWarning("Scan request timed out, failing open (allowing request)");
|
|
384
|
-
return
|
|
465
|
+
return failOpenResult("Scan timeout, failing open");
|
|
385
466
|
}
|
|
386
467
|
throw new ShrikeScanError("Scan request timed out and fail_mode is 'closed'");
|
|
387
468
|
}
|
|
388
469
|
if (this._failMode === "open" /* OPEN */) {
|
|
389
|
-
return
|
|
470
|
+
return failOpenResult(`Scan error: ${errorMessage}`);
|
|
390
471
|
}
|
|
391
472
|
throw new ShrikeScanError(`Scan failed: ${errorMessage}`);
|
|
392
473
|
} finally {
|
|
@@ -412,7 +493,7 @@ var MessagesNamespace = class {
|
|
|
412
493
|
const scanResult = await this._client._scanMessages(
|
|
413
494
|
params.messages
|
|
414
495
|
);
|
|
415
|
-
if (
|
|
496
|
+
if (isBlocked(scanResult)) {
|
|
416
497
|
throw new ShrikeBlockedError(
|
|
417
498
|
`Request blocked: ${scanResult.reason || "Security threat detected"}`,
|
|
418
499
|
scanResult.threat_type,
|