shrike-guard 4.0.3 → 4.1.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/CHANGELOG.md +86 -0
- package/README.md +248 -30
- package/dist/anthropic/index.d.mts +3 -3
- package/dist/anthropic/index.d.ts +3 -3
- package/dist/anthropic/index.js +12 -5
- package/dist/anthropic/index.js.map +1 -1
- package/dist/anthropic/index.mjs +12 -5
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/api/index.d.mts +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +2 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +2 -2
- package/dist/api/index.mjs.map +1 -1
- package/dist/gemini/index.d.mts +17 -3
- package/dist/gemini/index.d.ts +17 -3
- package/dist/gemini/index.js +18 -6
- package/dist/gemini/index.js.map +1 -1
- package/dist/gemini/index.mjs +18 -6
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +218 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +218 -19
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.d.mts +3 -3
- package/dist/openai/index.d.ts +3 -3
- package/dist/openai/index.js +12 -5
- package/dist/openai/index.js.map +1 -1
- package/dist/openai/index.mjs +12 -5
- package/dist/openai/index.mjs.map +1 -1
- package/dist/{scanner-cxA3LnQR.d.mts → scanner-z9r8BJvO.d.mts} +178 -7
- package/dist/{scanner-cxA3LnQR.d.ts → scanner-z9r8BJvO.d.ts} +178 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,91 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.1.0] - 2026-09-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Act-plane scanning: every channel the backend scans is now reachable from
|
|
7
|
+
the SDK.** The backend has scanned eight specialized content types since the
|
|
8
|
+
act plane shipped; this SDK exposed four. `scanCommand` — the highest-volume
|
|
9
|
+
surface, the one an agent hits before every shell-out — had no method at all,
|
|
10
|
+
so the only way to reach it was to hand-roll an HTTP call or route through the
|
|
11
|
+
MCP server. New on `ScanClient`:
|
|
12
|
+
- `scanCommand(command, cwd?)` — shell commands, screened before `exec`.
|
|
13
|
+
Commands are decomposed, so SQL passed to `psql -c`, `mysql -e`, or a
|
|
14
|
+
heredoc is scanned as SQL rather than as opaque shell text.
|
|
15
|
+
- `scanWebSearch(query)` — search queries that acquire attack tooling,
|
|
16
|
+
credentials, or evasion tradecraft.
|
|
17
|
+
- `scanRagContext(chunks, query?)` — retrieved context, the standard carrier
|
|
18
|
+
for indirect prompt injection. Accepts a string or a list of strings.
|
|
19
|
+
- `scanMcpSchema(name, description, inputSchema?)` — a single MCP tool
|
|
20
|
+
definition, for tool poisoning in a `tools/list` response. Screened once at
|
|
21
|
+
registration, not per call.
|
|
22
|
+
- **`content_origin` on `ScanResult`.** Every verdict now says where the scanned
|
|
23
|
+
content came from: `human_prompt`, `agent_output`, `agent_action`, or
|
|
24
|
+
`third_party`. This answers the question a verdict alone cannot — was that my
|
|
25
|
+
prompt, or the agent acting on its own — which decides who a refusal message
|
|
26
|
+
is addressed to. Exported alongside it: the `ContentOrigin` type and
|
|
27
|
+
`attributableToOperator(origin)`, true only for `human_prompt`. Unknown content
|
|
28
|
+
types resolve to `agent_action`, never to `human_prompt`.
|
|
29
|
+
- **Act-plane parity test** (`tests/unit/actplane-parity.test.ts`). Iterates the
|
|
30
|
+
canonical channel list and fails when a channel has no SDK method. The gap
|
|
31
|
+
above went unnoticed because "the backend supports it" and "a customer can
|
|
32
|
+
call it" were two facts with nothing comparing them. This is the comparison.
|
|
33
|
+
|
|
34
|
+
- **Per-request session identity — `sessionId`/`agentId` on `ScanClientOptions`,
|
|
35
|
+
and `forSession()`.** Session identity is the key the backend accumulates
|
|
36
|
+
multi-turn risk against, so it has to mean one unit of work: one agent run,
|
|
37
|
+
one conversation, one user's request. It defaults to a process-wide id, which
|
|
38
|
+
suits a CLI or a worker but not a server serving many end users, where every
|
|
39
|
+
user would share one risk score and one user's refusal would count against
|
|
40
|
+
the next user's action. `forSession()` returns a view that shares the parent's
|
|
41
|
+
rate limiter, so deriving one per request is cheap and cannot multiply your
|
|
42
|
+
rate budget:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const guard = new ScanClient({ apiKey: KEY }); // once, at startup
|
|
46
|
+
|
|
47
|
+
app.post('/act', async (req, res) => { // per request
|
|
48
|
+
const scoped = guard.forSession(req.session.id);
|
|
49
|
+
const verdict = await scoped.scanCommand(req.body.command);
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The process-wide default is unchanged when nothing is supplied, so
|
|
54
|
+
single-agent callers keep multi-turn correlation; the SDK now warns once when
|
|
55
|
+
it is in force. Silence that with `SHRIKE_SUPPRESS_SESSION_WARNING=1`.
|
|
56
|
+
|
|
57
|
+
`evaluateRotation`'s caller-owned branch now applies: a caller that supplies
|
|
58
|
+
its own session id receives a rotation recommendation rather than a
|
|
59
|
+
module-owned rotation.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
- **`content_origin` was computed by the backend and dropped before the caller.**
|
|
63
|
+
The response sanitizer is an allow-list, and the field was never added to
|
|
64
|
+
`PRESERVED_GOVERNANCE_FIELDS`, so it was serialized by the server and stripped
|
|
65
|
+
one layer before the application. It now survives sanitization.
|
|
66
|
+
- **Session identity could not be configured.** Every scan in a process used one
|
|
67
|
+
generated session id, so a server scanning on behalf of more than one person
|
|
68
|
+
placed all of them in one session. See `sessionId` / `forSession()` above.
|
|
69
|
+
|
|
70
|
+
## [4.0.5] - 2026-08-31
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- **Custom endpoint for the Gemini wrapper (`baseUrl`).** `ShrikeGemini` now
|
|
74
|
+
accepts `baseUrl` (applied as `httpOptions.baseUrl` on the underlying
|
|
75
|
+
`@google/genai` client) and a `geminiOptions` passthrough for advanced
|
|
76
|
+
constructor options — parity with the OpenAI and Anthropic wrappers, which
|
|
77
|
+
already forwarded a custom endpoint via `openaiOptions.baseURL` /
|
|
78
|
+
`anthropicOptions.baseURL`. An explicit `geminiOptions.httpOptions` takes
|
|
79
|
+
precedence over `baseUrl`.
|
|
80
|
+
- **Documented local / self-hosted LLM governance.** New README section shows
|
|
81
|
+
governing an OpenAI-compatible local runtime (Ollama, vLLM, LM Studio) by
|
|
82
|
+
forwarding `openaiOptions.baseURL`, plus the Gemini and Anthropic equivalents.
|
|
83
|
+
The plumbing already worked for OpenAI/Anthropic; this documents it and closes
|
|
84
|
+
the Gemini gap.
|
|
85
|
+
|
|
86
|
+
_(Version aligned to 4.0.5 with the Python SDK for this coordinated local-LLM
|
|
87
|
+
parity release; 4.0.4 was not published for the TypeScript SDK.)_
|
|
88
|
+
|
|
3
89
|
## [4.0.3] - 2026-07-17
|
|
4
90
|
|
|
5
91
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
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
|
+
It scans two different things. **Prompts and responses**, which is what a guardrail library normally means. And **agent actions** — the shell command, the SQL query, the web search, the retrieved document, the MCP tool definition — screened before they run, which is where an autonomous agent actually causes harm. See [Scanning agent actions](#scanning-agent-actions-shell-commands-sql-web-search-rag-mcp-tools).
|
|
10
|
+
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
13
|
- **Drop-in replacement** for OpenAI, Anthropic, and Gemini clients
|
|
@@ -15,6 +17,8 @@
|
|
|
15
17
|
- Jailbreak attempts
|
|
16
18
|
- SQL injection
|
|
17
19
|
- Path traversal
|
|
20
|
+
- **Pre-execution scanning for agent actions**: shell commands, SQL, file writes, web searches, RAG context, agent-to-agent messages, agent cards, and MCP tool schemas
|
|
21
|
+
- **Content provenance** (`content_origin`): every verdict says whether a human typed it, the model wrote it, the agent is about to do it, or it arrived from outside
|
|
18
22
|
- **Fail-safe modes**: Defaults to fail-closed (Zero Trust posture); opt into fail-open explicitly when availability outranks enforcement
|
|
19
23
|
- **CJS + ESM**: Dual build via tsup, works everywhere
|
|
20
24
|
- **Subpath imports**: `shrike-guard/openai`, `shrike-guard/anthropic`, `shrike-guard/gemini`
|
|
@@ -167,31 +171,216 @@ const client = new ShrikeOpenAI({
|
|
|
167
171
|
});
|
|
168
172
|
```
|
|
169
173
|
|
|
170
|
-
|
|
174
|
+
### Sessions: one per unit of work, not one per process
|
|
175
|
+
|
|
176
|
+
Shrike correlates risk across a session. After a refusal, later actions in the
|
|
177
|
+
same session are held until the session recovers. That is the multi-turn
|
|
178
|
+
defence, and it means the session id has to mean one unit of work: one agent
|
|
179
|
+
run, one conversation, one user's request.
|
|
180
|
+
|
|
181
|
+
By default the SDK scans under one id for the whole process. That suits a CLI,
|
|
182
|
+
a worker, or a single agent. It does not suit a server that scans on behalf of
|
|
183
|
+
many end users, because every user then shares one risk score, and one user's
|
|
184
|
+
refusal counts against the next user's action.
|
|
185
|
+
|
|
186
|
+
Build one client at startup and derive a per-request view from it. The view
|
|
187
|
+
shares the rate limiter, so it costs nothing to make one per request and it
|
|
188
|
+
cannot multiply your rate budget:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { ScanClient } from 'shrike-guard';
|
|
192
|
+
|
|
193
|
+
const guard = new ScanClient({ apiKey: 'shrike-...' }); // once, at startup
|
|
194
|
+
|
|
195
|
+
app.post('/act', async (req, res) => { // per request
|
|
196
|
+
const scoped = guard.forSession(req.session.id);
|
|
197
|
+
const verdict = await scoped.scanCommand(req.body.command);
|
|
198
|
+
if (verdict.refuse_tier !== 'allow') {
|
|
199
|
+
...
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
```
|
|
171
203
|
|
|
172
|
-
|
|
204
|
+
Or pin the identity at construction when one client serves one unit of work:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const client = new ScanClient({ apiKey: 'shrike-...', sessionId: 'job-42', agentId: 'ingest' });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`agentId` is separate on purpose: it names *which agent* a scope is enforced
|
|
211
|
+
against and who an incident is attributed to. Set it when one process drives
|
|
212
|
+
several distinct agents.
|
|
213
|
+
|
|
214
|
+
The SDK warns once per process when it is scanning under the shared default.
|
|
215
|
+
Set `SHRIKE_SUPPRESS_SESSION_WARNING=1` to silence it once you have decided
|
|
216
|
+
the default is what you want.
|
|
217
|
+
|
|
218
|
+
### Local and self-hosted LLMs
|
|
219
|
+
|
|
220
|
+
Shrike governs the model you point it at — it does not have to be a hosted
|
|
221
|
+
frontier API. Local runtimes like [Ollama](https://ollama.com),
|
|
222
|
+
[vLLM](https://docs.vllm.ai), and LM Studio expose an OpenAI-compatible
|
|
223
|
+
endpoint, so `ShrikeOpenAI` guards them by forwarding a `baseURL` through
|
|
224
|
+
`openaiOptions`:
|
|
173
225
|
|
|
174
226
|
```typescript
|
|
175
227
|
import { ShrikeOpenAI } from 'shrike-guard/openai';
|
|
176
228
|
|
|
229
|
+
// Ollama serving llama3 locally, governed by Shrike before every call
|
|
177
230
|
const client = new ShrikeOpenAI({
|
|
178
|
-
apiKey: '
|
|
231
|
+
apiKey: 'ollama', // local servers ignore the value
|
|
232
|
+
shrikeApiKey: 'shrike-...', // governance still runs server-side
|
|
233
|
+
openaiOptions: { baseURL: 'http://localhost:11434/v1' },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const response = await client.chat.completions.create({
|
|
237
|
+
model: 'llama3',
|
|
238
|
+
messages: [{ role: 'user', content: 'Summarize this ticket…' }],
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The same pattern works for Anthropic-compatible gateways via
|
|
243
|
+
`anthropicOptions.baseURL`. For Gemini, use the `baseUrl` option (added in
|
|
244
|
+
4.0.5), applied as `httpOptions.baseUrl` on the `@google/genai` client:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { ShrikeGemini } from 'shrike-guard/gemini';
|
|
248
|
+
|
|
249
|
+
const client = new ShrikeGemini({
|
|
250
|
+
apiKey: '…',
|
|
179
251
|
shrikeApiKey: 'shrike-...',
|
|
252
|
+
baseUrl: 'https://your-gemini-gateway.example',
|
|
180
253
|
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The prompt still leaves your process to reach the Shrike backend for scanning;
|
|
257
|
+
the *model call* stays on your local/self-hosted endpoint.
|
|
258
|
+
|
|
259
|
+
## Scanning agent actions (shell commands, SQL, web search, RAG, MCP tools)
|
|
260
|
+
|
|
261
|
+
Scanning the prompt protects the model. It does not protect the shell. An agent
|
|
262
|
+
that was never told anything malicious can still be talked into running
|
|
263
|
+
`curl … | sh` by a poisoned README, and the prompt scan has no view of that.
|
|
181
264
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
265
|
+
`ScanClient` exposes a method per action channel. Call the one that matches
|
|
266
|
+
what the agent is about to do, before it does it:
|
|
267
|
+
|
|
268
|
+
| Channel | Method | Screens for |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| Shell command | `scanCommand(command, cwd?)` | destructive commands, data exfiltration, credential dumps, embedded SQL injection |
|
|
271
|
+
| SQL query | `scanSql(query, database?, allowDestructive?)` | SQL injection, unauthorized destructive statements |
|
|
272
|
+
| File path | `scanFile(path)` | path traversal, writes outside the working tree |
|
|
273
|
+
| File content | `scanFile(path, content)` | secrets, credentials, PII before they land on disk |
|
|
274
|
+
| Web search | `scanWebSearch(query)` | searches that acquire attack tooling, credentials, or evasion tradecraft |
|
|
275
|
+
| RAG context | `scanRagContext(chunks, query?)` | indirect prompt injection in retrieved documents |
|
|
276
|
+
| Agent message | `scanA2AMessage(message, options?)` | instructions smuggled between agents |
|
|
277
|
+
| Agent card | `scanAgentCard(card, verifySignature?)` | capability misrepresentation in A2A discovery |
|
|
278
|
+
| MCP tool schema | `scanMcpSchema(name, description, inputSchema?)` | tool poisoning in `tools/list` responses |
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
import { ScanClient } from 'shrike-guard';
|
|
282
|
+
|
|
283
|
+
const scanner = new ScanClient({ apiKey: 'shrike-...' });
|
|
284
|
+
|
|
285
|
+
// Before shelling out
|
|
286
|
+
const cmd = await scanner.scanCommand('psql -c "SELECT * FROM users"', '/srv/app');
|
|
287
|
+
if (!cmd.safe) throw new Error(`Refused: ${cmd.reason}`);
|
|
288
|
+
|
|
289
|
+
// Before querying
|
|
290
|
+
const sql = await scanner.scanSql('SELECT * FROM users WHERE id = $1', 'postgres');
|
|
291
|
+
|
|
292
|
+
// Before writing
|
|
293
|
+
const write = await scanner.scanFile('/tmp/config.py', 'api_key = "sk-..."');
|
|
294
|
+
|
|
295
|
+
// Before searching the web
|
|
296
|
+
const search = await scanner.scanWebSearch('sql injection prevention owasp');
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
A shell command is not one thing. `scanCommand` decomposes it, so SQL passed to
|
|
300
|
+
`psql -c`, `mysql -e`, or a heredoc is scanned as SQL rather than as an opaque
|
|
301
|
+
string of shell text.
|
|
302
|
+
|
|
303
|
+
### Indirect prompt injection in RAG pipelines
|
|
304
|
+
|
|
305
|
+
Retrieved chunks are text somebody else wrote. They are the standard carrier for
|
|
306
|
+
indirect prompt injection: the user asks nothing unusual, the document tells the
|
|
307
|
+
model what to do, and the model complies. Scan on the way in, not after the
|
|
308
|
+
model has acted:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const chunks = await vectorStore.similaritySearch(userQuery, 5);
|
|
312
|
+
|
|
313
|
+
const verdict = await scanner.scanRagContext(
|
|
314
|
+
chunks.map((c) => c.pageContent),
|
|
315
|
+
userQuery,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
if (!verdict.safe) {
|
|
319
|
+
// The retrieved context is hostile, not the user's question.
|
|
320
|
+
console.warn(`Poisoned context: ${verdict.reason}`);
|
|
186
321
|
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### MCP tool poisoning
|
|
187
325
|
|
|
188
|
-
|
|
189
|
-
|
|
326
|
+
An MCP tool description is read by the model as guidance. A hostile server can
|
|
327
|
+
put instructions in the `description` field of a tool that never executes, and
|
|
328
|
+
the agent will act on them at registration time. Screen every entry of a
|
|
329
|
+
`tools/list` response from a server you do not control:
|
|
190
330
|
|
|
191
|
-
|
|
192
|
-
const
|
|
331
|
+
```typescript
|
|
332
|
+
const { tools } = await mcpClient.listTools();
|
|
333
|
+
|
|
334
|
+
for (const tool of tools) {
|
|
335
|
+
const verdict = await scanner.scanMcpSchema(
|
|
336
|
+
tool.name,
|
|
337
|
+
tool.description ?? '',
|
|
338
|
+
tool.inputSchema,
|
|
339
|
+
);
|
|
340
|
+
if (!verdict.safe) {
|
|
341
|
+
console.warn(`Not registering ${tool.name}: ${verdict.reason}`);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
register(tool);
|
|
345
|
+
}
|
|
193
346
|
```
|
|
194
347
|
|
|
348
|
+
Screening happens once per tool at registration, not on every call.
|
|
349
|
+
|
|
350
|
+
## Who is answerable: `content_origin`
|
|
351
|
+
|
|
352
|
+
Every verdict carries `content_origin`, which says where the scanned content
|
|
353
|
+
came from. It answers the question a verdict alone cannot: *was that my prompt,
|
|
354
|
+
or the agent acting on its own?*
|
|
355
|
+
|
|
356
|
+
| Value | Meaning |
|
|
357
|
+
|---|---|
|
|
358
|
+
| `human_prompt` | the operator typed it |
|
|
359
|
+
| `agent_output` | the model generated it |
|
|
360
|
+
| `agent_action` | the agent is about to do it (every act-plane channel) |
|
|
361
|
+
| `third_party` | it arrived from outside: a tool result, a retrieved document, a peer agent |
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { attributableToOperator } from 'shrike-guard';
|
|
365
|
+
|
|
366
|
+
const verdict = await scanner.scanRagContext(chunks);
|
|
367
|
+
|
|
368
|
+
if (!verdict.safe) {
|
|
369
|
+
if (attributableToOperator(verdict.content_origin)) {
|
|
370
|
+
showUser(`Your request was blocked: ${verdict.reason}`);
|
|
371
|
+
} else {
|
|
372
|
+
// The agent poisoned its own context. Telling the user "your request
|
|
373
|
+
// was blocked" would be both wrong and unhelpful.
|
|
374
|
+
log.warn('agent-side refusal', verdict.reason);
|
|
375
|
+
retryWithCleanContext();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Unknown content types resolve to `agent_action`, never to `human_prompt`:
|
|
381
|
+
attributing an unattributable action to the operator is the one error that is
|
|
382
|
+
never safe to make by default.
|
|
383
|
+
|
|
195
384
|
## Error Handling
|
|
196
385
|
|
|
197
386
|
```typescript
|
|
@@ -257,6 +446,8 @@ export OPENAI_API_KEY="sk-..."
|
|
|
257
446
|
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
258
447
|
export SHRIKE_API_KEY="shrike-..."
|
|
259
448
|
export SHRIKE_ENDPOINT="https://your-shrike-instance.com"
|
|
449
|
+
export SHRIKE_AGENT_ID="ingest" # names this process's agent; see Sessions
|
|
450
|
+
export SHRIKE_SUPPRESS_SESSION_WARNING=1 # once you have decided the shared session is right
|
|
260
451
|
```
|
|
261
452
|
|
|
262
453
|
## Scope and Limitations
|
|
@@ -267,13 +458,23 @@ export SHRIKE_ENDPOINT="https://your-shrike-instance.com"
|
|
|
267
458
|
| Multi-modal text content | Image/audio content |
|
|
268
459
|
| SQL queries | Non-chat API calls |
|
|
269
460
|
| File paths and content | |
|
|
461
|
+
| Shell commands | |
|
|
462
|
+
| Web search queries | |
|
|
463
|
+
| Retrieved RAG context | |
|
|
464
|
+
| Agent-to-agent messages and agent cards | |
|
|
465
|
+
| MCP tool schemas | |
|
|
270
466
|
|
|
271
|
-
### Why
|
|
467
|
+
### Why Pre-Execution Scanning?
|
|
272
468
|
|
|
273
|
-
Shrike Guard focuses on **pre-flight protection** —
|
|
469
|
+
Shrike Guard focuses on **pre-flight protection** — evaluating a prompt before it
|
|
470
|
+
reaches the LLM, and an action before it runs. This:
|
|
274
471
|
- Prevents prompt injection attacks at the source
|
|
275
472
|
- Has zero latency impact on LLM responses
|
|
276
|
-
-
|
|
473
|
+
- Puts the decision point before the side effect, where refusing still costs nothing
|
|
474
|
+
|
|
475
|
+
An action already taken cannot be un-taken by detecting it afterwards. That is the
|
|
476
|
+
distinction between this and after-the-fact monitoring: the verdict arrives while
|
|
477
|
+
refusing is still free.
|
|
277
478
|
|
|
278
479
|
## Other Integration Surfaces
|
|
279
480
|
|
|
@@ -291,22 +492,39 @@ Shrike Guard is one of several ways to integrate with the Shrike platform:
|
|
|
291
492
|
| Scenario | How Shrike Guard Helps |
|
|
292
493
|
|---|---|
|
|
293
494
|
| **Customer chatbot** | Wrap your OpenAI/Anthropic client. Every user message scanned for injection before it reaches the model. |
|
|
294
|
-
| **
|
|
295
|
-
| **
|
|
296
|
-
| **
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
495
|
+
| **Coding agents that shell out** | `scanCommand` before every `exec`. Destructive commands, exfiltration, and SQL smuggled through `psql -c` are caught before the process starts. |
|
|
496
|
+
| **Internal RAG pipeline** | `scanRagContext` on retrieved chunks for indirect prompt injection, plus PII leakage on the query. |
|
|
497
|
+
| **MCP clients** | `scanMcpSchema` on every `tools/list` entry from a server you do not control, to catch tool poisoning at registration. |
|
|
498
|
+
| **Multi-agent systems** | `scanA2AMessage` and `scanAgentCard` for instructions smuggled between agents. |
|
|
499
|
+
| **Agent orchestration** | Scan every tool call and LLM request in your LangChain/LangGraph/CrewAI/AutoGen pipeline. |
|
|
500
|
+
|
|
501
|
+
## How This Differs From a Prompt Scanner
|
|
502
|
+
|
|
503
|
+
If you are evaluating TypeScript or JavaScript AI security SDKs, this is the
|
|
504
|
+
distinction worth testing against your own workload.
|
|
505
|
+
|
|
506
|
+
Most guardrail libraries answer one question: **is this text hostile?** They read
|
|
507
|
+
the prompt, and sometimes the response. That is necessary, and Shrike Guard does
|
|
508
|
+
it through a 9-layer cascade with PII redaction and multi-turn session
|
|
509
|
+
correlation.
|
|
510
|
+
|
|
511
|
+
But an autonomous agent does not cause harm by saying something. It causes harm
|
|
512
|
+
by *doing* something: running a command, writing a file, querying a database,
|
|
513
|
+
calling a tool. Shrike Guard scans those too, before they execute:
|
|
514
|
+
|
|
515
|
+
- **A verdict per action channel** — shell commands, SQL, file writes, web
|
|
516
|
+
searches, RAG context, agent-to-agent messages, agent cards, MCP tool schemas.
|
|
517
|
+
See [Scanning agent actions](#scanning-agent-actions-shell-commands-sql-web-search-rag-mcp-tools).
|
|
518
|
+
- **Pre-execution, not after the fact.** The verdict arrives while refusing is
|
|
519
|
+
still free. An action already taken cannot be un-taken by detecting it.
|
|
520
|
+
- **Provenance on every verdict** (`content_origin`) — whether a human typed it,
|
|
521
|
+
the model wrote it, the agent is about to do it, or it arrived from outside.
|
|
522
|
+
- **A governance contract, not just a boolean** — `refuse_tier`
|
|
523
|
+
(allow / warn / require_approval / block), a `recovery` block telling the agent
|
|
524
|
+
how to proceed legitimately, and `session_state`. Present on safe verdicts too.
|
|
525
|
+
- **Drop-in wrappers** for OpenAI, Anthropic, and Gemini, so the prompt-scanning
|
|
526
|
+
half needs no code changes.
|
|
527
|
+
- **Free tier with no API key**, and an Apache 2.0 client you can read.
|
|
310
528
|
|
|
311
529
|
## License
|
|
312
530
|
|
|
@@ -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-z9r8BJvO.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
|
@@ -78,8 +78,8 @@ declare class ShrikeAnthropic {
|
|
|
78
78
|
* Scan user messages for security threats via backend API.
|
|
79
79
|
*
|
|
80
80
|
* Always calls backend - backend handles tier-based scanning:
|
|
81
|
-
* - Community tier (no API key): L1-
|
|
82
|
-
* - Pro tier: L1-
|
|
81
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
82
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
83
83
|
*/
|
|
84
84
|
_scanMessages(messages: AnthropicMessage[]): Promise<ScanResult>;
|
|
85
85
|
/**
|
|
@@ -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-z9r8BJvO.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ShrikeAnthropic - Drop-in replacement for Anthropic client with security scanning.
|
|
@@ -78,8 +78,8 @@ declare class ShrikeAnthropic {
|
|
|
78
78
|
* Scan user messages for security threats via backend API.
|
|
79
79
|
*
|
|
80
80
|
* Always calls backend - backend handles tier-based scanning:
|
|
81
|
-
* - Community tier (no API key): L1-
|
|
82
|
-
* - Pro tier: L1-
|
|
81
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
82
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
83
83
|
*/
|
|
84
84
|
_scanMessages(messages: AnthropicMessage[]): Promise<ScanResult>;
|
|
85
85
|
/**
|
package/dist/anthropic/index.js
CHANGED
|
@@ -13,12 +13,12 @@ var DEFAULT_FAIL_MODE = "closed" /* CLOSED */;
|
|
|
13
13
|
var DEFAULT_ENDPOINT = "https://api.shrikesecurity.com/agent";
|
|
14
14
|
var SDK_NAME = "typescript";
|
|
15
15
|
var SESSION_ID = crypto.randomUUID();
|
|
16
|
-
var AGENT_ID =
|
|
16
|
+
var AGENT_ID = `sdk-ts-${crypto.randomUUID().slice(0, 8)}`;
|
|
17
17
|
function getSessionId() {
|
|
18
18
|
return SESSION_ID;
|
|
19
19
|
}
|
|
20
20
|
function getAgentId() {
|
|
21
|
-
return AGENT_ID;
|
|
21
|
+
return process.env.SHRIKE_AGENT_ID || AGENT_ID;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// src/errors.ts
|
|
@@ -266,6 +266,13 @@ var PRESERVED_GOVERNANCE_FIELDS = [
|
|
|
266
266
|
"recovery",
|
|
267
267
|
"session_state",
|
|
268
268
|
"content_type",
|
|
269
|
+
// Provenance: human_prompt / agent_output / agent_action / third_party.
|
|
270
|
+
// An OUTCOME field, not detection attribution — it says who is answerable
|
|
271
|
+
// for the scanned content, which the caller needs in order to know who a
|
|
272
|
+
// refusal is even addressed to. It shipped on the backend in 2cfaa5f and was
|
|
273
|
+
// silently dropped here, because this is an allow-list and nobody added it:
|
|
274
|
+
// a new backend field reaches no caller until it appears in this array.
|
|
275
|
+
"content_origin",
|
|
269
276
|
"approval_info",
|
|
270
277
|
"client_session_rotation"
|
|
271
278
|
];
|
|
@@ -330,7 +337,7 @@ function sanitizeScanResponse(raw) {
|
|
|
330
337
|
}
|
|
331
338
|
|
|
332
339
|
// package.json
|
|
333
|
-
var version = "4.0
|
|
340
|
+
var version = "4.1.0";
|
|
334
341
|
|
|
335
342
|
// src/version.ts
|
|
336
343
|
var VERSION = version;
|
|
@@ -419,8 +426,8 @@ var ShrikeAnthropic = class {
|
|
|
419
426
|
* Scan user messages for security threats via backend API.
|
|
420
427
|
*
|
|
421
428
|
* Always calls backend - backend handles tier-based scanning:
|
|
422
|
-
* - Community tier (no API key): L1-
|
|
423
|
-
* - Pro tier: L1-
|
|
429
|
+
* - Community tier (no API key): L1-L5 (regex, unicode, malformed, encoding, token/semantic)
|
|
430
|
+
* - Pro tier and above: L1-L9 (adds visual, LLM semantic, response intel, session correlation)
|
|
424
431
|
*/
|
|
425
432
|
async _scanMessages(messages) {
|
|
426
433
|
const userContent = this._extractUserContent(messages);
|