shrike-guard 4.1.0 → 4.3.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +92 -2
  2. package/README.md +66 -0
  3. package/dist/ai/index.d.mts +59 -0
  4. package/dist/ai/index.d.ts +59 -0
  5. package/dist/ai/index.js +500 -0
  6. package/dist/ai/index.js.map +1 -0
  7. package/dist/ai/index.mjs +496 -0
  8. package/dist/ai/index.mjs.map +1 -0
  9. package/dist/anthropic/index.d.mts +1 -1
  10. package/dist/anthropic/index.d.ts +1 -1
  11. package/dist/anthropic/index.js +4 -1
  12. package/dist/anthropic/index.js.map +1 -1
  13. package/dist/anthropic/index.mjs +4 -1
  14. package/dist/anthropic/index.mjs.map +1 -1
  15. package/dist/api/index.d.mts +1 -1
  16. package/dist/api/index.d.ts +1 -1
  17. package/dist/api/index.js +1 -1
  18. package/dist/api/index.js.map +1 -1
  19. package/dist/api/index.mjs +1 -1
  20. package/dist/api/index.mjs.map +1 -1
  21. package/dist/claude-agent/index.d.mts +98 -0
  22. package/dist/claude-agent/index.d.ts +98 -0
  23. package/dist/claude-agent/index.js +531 -0
  24. package/dist/claude-agent/index.js.map +1 -0
  25. package/dist/claude-agent/index.mjs +524 -0
  26. package/dist/claude-agent/index.mjs.map +1 -0
  27. package/dist/gemini/index.d.mts +1 -1
  28. package/dist/gemini/index.d.ts +1 -1
  29. package/dist/gemini/index.js +4 -1
  30. package/dist/gemini/index.js.map +1 -1
  31. package/dist/gemini/index.mjs +4 -1
  32. package/dist/gemini/index.mjs.map +1 -1
  33. package/dist/{scanner-z9r8BJvO.d.mts → govern-olQtsipX.d.mts} +282 -2
  34. package/dist/{scanner-z9r8BJvO.d.ts → govern-olQtsipX.d.ts} +282 -2
  35. package/dist/index.d.mts +42 -16
  36. package/dist/index.d.ts +42 -16
  37. package/dist/index.js +107 -12
  38. package/dist/index.js.map +1 -1
  39. package/dist/index.mjs +107 -12
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/langchain/index.d.mts +74 -0
  42. package/dist/langchain/index.d.ts +74 -0
  43. package/dist/langchain/index.js +515 -0
  44. package/dist/langchain/index.js.map +1 -0
  45. package/dist/langchain/index.mjs +510 -0
  46. package/dist/langchain/index.mjs.map +1 -0
  47. package/dist/openai/index.d.mts +1 -1
  48. package/dist/openai/index.d.ts +1 -1
  49. package/dist/openai/index.js +4 -1
  50. package/dist/openai/index.js.map +1 -1
  51. package/dist/openai/index.mjs +4 -1
  52. package/dist/openai/index.mjs.map +1 -1
  53. package/dist/openai-agents/index.d.mts +65 -0
  54. package/dist/openai-agents/index.d.ts +65 -0
  55. package/dist/openai-agents/index.js +516 -0
  56. package/dist/openai-agents/index.js.map +1 -0
  57. package/dist/openai-agents/index.mjs +509 -0
  58. package/dist/openai-agents/index.mjs.map +1 -0
  59. package/package.json +63 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,95 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.3.0] - 2026-10-01
4
+
5
+ ### Added
6
+ - **The adapters report what became of a governed action.** A scan proves
7
+ an action was authorized, never that it ran. The LangGraph and Vercel AI
8
+ wrappers now report `executed` when the tool returns and `failed` when it
9
+ throws, and the Claude Agent SDK adapter registers `PostToolUse` and
10
+ `PostToolUseFailure` and pairs the outcome with the call by `tool_use_id`.
11
+ The report names the scan by the `scan_id` the verdict carries and
12
+ carries a status only, never the tool's result or error text. A refusal
13
+ never runs the handler and reports nothing. An unreported action reads as
14
+ unconfirmed on the Shrike Agents screen, which is what it is.
15
+ - `ScanClient.reportOutcome(scanId, outcome, { exitStatus, source })` and
16
+ `ScanClient.reportHostOutcome({ host, outcome, tool, callId, contentHash,
17
+ reason, scanId })` for integrations that hold the outcome themselves.
18
+ Both are best effort and never throw. `Governance.reportOutcome(out,
19
+ outcome)` reports once per recorded decision. `ScanResult.scan_id` and
20
+ `Decision.scanId` are new fields.
21
+ - `SessionLockedNotice` is exported as the third member of the
22
+ `ClientSessionRotation` union, so a locked session has a type of its own
23
+ instead of a rotation record with its rotation fields left unset.
24
+
25
+ ### Fixed
26
+ - **A locked session is no longer offered a new session id.** A lock means the
27
+ session is finished, so the record returned on a `session_locked` verdict now
28
+ carries no id to adopt. The two rotating records narrow `reason` to
29
+ `'risk_threshold_exceeded'`, the only value either can still carry, so a lock
30
+ and a rotation are distinguishable by type rather than by reading a string.
31
+ Recovery from a lock is self-release under a live declared scope, or an
32
+ operator.
33
+
34
+ ## [4.2.0] - 2026-09-17
35
+
36
+ ### Added
37
+ - **An unmapped tool can still be judged by its name.** A tool with no
38
+ mapping has no readable surface, so the content plane has nothing to say
39
+ about it. The authorization plane still does: the operator's declared scope
40
+ judges a tool by NAME, which is the one thing every tool call has.
41
+ `onUnmapped: 'authorize'` sends the tool's name (and nothing else) to the
42
+ backend, so a tool outside the allowlist is refused even though nothing
43
+ read what it was carrying, and an expired or exhausted scope holds it. The
44
+ permit is narrower than a mapped tool's and the record says so: the
45
+ decision's surface is `authorization`, never a scanned surface. A client
46
+ too old to ask fails according to `failMode` rather than assuming.
47
+ `ShrikeGuard.authorizeTool` is the call underneath.
48
+ - **Observe-plane scans say so.** `ShrikeGuard.scan` takes an options argument
49
+ carrying `plane`, and `observePrompt` sends `plane: 'observe'`. A verdict on a prompt nobody is
50
+ gated on is advice: it is recorded and returned, and the model still reads
51
+ the note, but it is no longer filed as an action that was stopped.
52
+
53
+ - **Framework starters over one core.** `govern.ts` is the framework-free
54
+ core of a governed agent: a tool mapping table (`mapTool`, `exempt`),
55
+ `evaluate` (every scan a tool call needs, stopping at the first refusal,
56
+ into one `Outcome`: allow, warn, hold or deny, with the message written
57
+ for the model), `observePrompt` (never blocks), `requestScope` (the
58
+ backend decides; a widening is refused unless an operator grants it), and
59
+ the record (`decisions`, `onDecision`). Each starter is a subpath and a
60
+ thin translation of one framework's hooks:
61
+ - `shrike-guard/claude-agent` (Claude Agent SDK): `PreToolUse` and
62
+ `UserPromptSubmit` hooks, an in-process MCP tool. Ships mappings for the
63
+ SDK's built-in tools. ESM, as the SDK is.
64
+ - `shrike-guard/openai-agents` (OpenAI Agents SDK): a tool input guardrail
65
+ attached to every function tool (a refused call is answered with
66
+ `rejectContent`, so the model reads the reason as the tool's output), a
67
+ non-tripping input guardrail for the observe plane, and `request_scope`
68
+ as a function tool.
69
+ - `shrike-guard/ai` (Vercel AI SDK): `governTools` wraps each tool's
70
+ `execute` so a refused call returns the reason as the tool's result, a
71
+ language-model middleware whose `transformParams` scans a new user
72
+ message and prepends a note, and `request_scope` as an AI SDK tool.
73
+ - `shrike-guard/langchain` (LangChain `createAgent` and LangGraph): an
74
+ agent middleware whose `wrapToolCall` answers a refused call with a
75
+ `ToolMessage`, or `governTools` for a hand-built `ToolNode`, and
76
+ `request_scope` as a LangChain tool.
77
+ - **Tool mappings.** A tool is a surface (`command`, `file`, `file_path`,
78
+ `sql`, `web_search`, `rag_context`, `a2a_message`, `agent_card`, or
79
+ `none`) and the argument that carries its payload. A tool with no mapping
80
+ is refused with a message that says how to map it (`onUnmapped: 'deny'`),
81
+ or allowed and recorded (`'allow'`), or has its arguments scanned as text
82
+ (`'scan'`), or judged by name alone (`'authorize'`, see below). The Claude
83
+ Agent SDK starter defaults to `'authorize'`, and its hook now sees every
84
+ tool the agent can call rather than only the mapped ones, because a tool
85
+ with no reader is exactly the one whose authorization nobody has checked.
86
+ Pass an explicit tool list to `hooksFor` to narrow the matcher.
87
+ - **A conformance suite.** `tests/unit/frameworks.test.ts` drives every
88
+ starter through the same table (allow, warn, block, hold, backend down,
89
+ unmapped, a refused widening); each starter must answer it the same way.
90
+ The frameworks are optional peer dependencies; the base install is
91
+ unchanged.
92
+
3
93
  ## [4.1.0] - 2026-09-09
4
94
 
5
95
  ### Added
@@ -126,11 +216,11 @@ Cross-language SDK parity. The Python SDK ships `CircuitBreaker` + `retry_with_b
126
216
 
127
217
  ### Added
128
218
  - **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.
129
- - **`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.
219
+ - **`syncPIIPatterns({ endpoint, apiKey })`** — one-shot startup call fetches the canonical 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.
130
220
  - **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.
131
221
 
132
222
  ### Why
133
- 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.
223
+ 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 the backend adds a recognizer.
134
224
 
135
225
  ### Fixed
136
226
  - gemini-client test mock import matches the runtime `@google/genai` package (upstream renamed from `@google/generative-ai`).
package/README.md CHANGED
@@ -347,6 +347,72 @@ for (const tool of tools) {
347
347
 
348
348
  Screening happens once per tool at registration, not on every call.
349
349
 
350
+ ## Governing an agent: framework starters
351
+
352
+ Shrike governs an agent from inside its tool loop: every tool call is judged
353
+ before it executes, the person's prompt is scanned on the way in and never
354
+ blocked, and the agent gets one tool, `request_scope`, to ask for more than
355
+ it holds. One framework-free core carries that behaviour; each starter is a
356
+ thin translation of one framework's hooks. Every starter is a subpath of this
357
+ package, and each framework is an optional peer dependency (`zod` too).
358
+
359
+ | Framework | Import | What it wires |
360
+ |---|---|---|
361
+ | Claude Agent SDK | `shrike-guard/claude-agent` | `PreToolUse` and `UserPromptSubmit` hooks, an in-process MCP tool |
362
+ | OpenAI Agents SDK | `shrike-guard/openai-agents` | a tool input guardrail on every function tool, a non-tripping input guardrail, a function tool |
363
+ | Vercel AI SDK | `shrike-guard/ai` | wrapped `execute` on every tool, a language-model middleware for the observe plane, an AI SDK tool |
364
+ | LangChain `createAgent` / LangGraph | `shrike-guard/langchain` | an agent middleware (`wrapToolCall`, `beforeAgent`) or wrapped tools for a `ToolNode` |
365
+
366
+ Every starter has the same shape. Build it, map your tools to Shrike's
367
+ surfaces, hand it to the framework:
368
+
369
+ ```ts
370
+ import { Agent } from '@openai/agents';
371
+ import { ScanClient } from 'shrike-guard';
372
+ import { govern } from 'shrike-guard/openai-agents'; // or claude-agent, ai, langchain
373
+
374
+ const guard = new ScanClient({ apiKey: 'shrike-...' });
375
+ const gov = govern(guard, { agentId: 'invoice-agent' });
376
+ await gov.declare(['file_path', 'file_content', 'sql'], 'Reconcile Q3 vendor invoices');
377
+
378
+ gov.mapTool('run_query', { surface: 'sql', arg: 'query' });
379
+ gov.mapTool('save_report', { surface: 'file', pathArg: 'path', contentArg: 'text' });
380
+ gov.exempt('get_time');
381
+
382
+ const agent = gov.governAgent(new Agent({ name: 'invoices', tools: [runQuery, saveReport, getTime] }));
383
+ ```
384
+
385
+ A tool mapping says which surface a tool is and which argument carries the
386
+ payload: `command` (with an optional `cwdArg`), `file` (a path and the
387
+ content written to it), `file_path`, `sql`, `web_search`, `rag_context`,
388
+ `a2a_message`, `agent_card`, or `none` for a tool allowed without a scan.
389
+ The Claude Agent SDK starter ships mappings for the SDK's built-in tools;
390
+ the others govern the tools you write, so they need your mappings.
391
+
392
+ A tool with no mapping is refused, with a message that says how to map it,
393
+ until you map it or choose what an unmapped tool gets. `onUnmapped: 'authorize'` asks the
394
+ backend whether the agent may call it at all: the arguments stay put, and the
395
+ tool's NAME goes up, where the operator's declared scope answers. A tool
396
+ outside the allowlist is refused by name even though nothing read what it was
397
+ carrying, and an expired or exhausted scope holds it. That permit is narrower
398
+ than a mapped tool's and the record says so, with the decision's surface
399
+ recorded as `authorization`. `'allow'` records the call and moves on without
400
+ asking; `'scan'` sends the arguments to be read as text.
401
+
402
+ What the model sees is the same everywhere: an allowed call runs; a warned
403
+ call runs with the advisory; a blocked call does not run and the model gets
404
+ the reason; a held call (an action outside the declared scope) does not run
405
+ and the model gets the reason, the recovery, and the instruction to ask with
406
+ `request_scope` and then stop. An action Shrike could not check is refused
407
+ (`failMode: 'closed'`). Every decision lands on `gov.decisions` and on your
408
+ `onDecision` callback, with the axis that objected.
409
+
410
+ Where a framework can route a hold to a person, `onHold: 'ask'` does that
411
+ (the Claude Agent SDK's `canUseTool`). Where it cannot, a hold is answered
412
+ like a refusal and the model is told an operator can grant it on the Shrike
413
+ Agents screen. The `claude-agent` subpath is ESM, because the Claude Agent
414
+ SDK is.
415
+
350
416
  ## Who is answerable: `content_origin`
351
417
 
352
418
  Every verdict carries `content_origin`, which says where the scanned content
@@ -0,0 +1,59 @@
1
+ import { ToolSet, Tool, LanguageModelMiddleware } from 'ai';
2
+ import { G as Governance$1, a as Guard, S as ScanClient, b as GovernanceOptions } from '../govern-olQtsipX.mjs';
3
+ export { D as Decision, O as Outcome, T as ToolMapping } from '../govern-olQtsipX.mjs';
4
+
5
+ /**
6
+ * Govern an agent built with the Vercel AI SDK (`ai`).
7
+ *
8
+ * ```ts
9
+ * import { generateText, wrapLanguageModel } from 'ai';
10
+ * import { ScanClient } from 'shrike-guard';
11
+ * import { govern } from 'shrike-guard/ai';
12
+ *
13
+ * const guard = new ScanClient({ apiKey: KEY });
14
+ * const gov = govern(guard, { agentId: 'invoice-agent' }).mapTool('runQuery', { surface: 'sql', arg: 'query' });
15
+ * const result = await generateText({
16
+ * model: wrapLanguageModel({ model, middleware: gov.middleware }),
17
+ * tools: gov.governTools({ runQuery, saveReport, request_scope: gov.tool }),
18
+ * prompt,
19
+ * });
20
+ * ```
21
+ *
22
+ * A thin adapter over `shrike-guard`'s framework-free core (`govern.ts`).
23
+ *
24
+ * The act plane wraps each tool's `execute`: Shrike judges the call through
25
+ * the tool's mapping before the inner `execute` runs. A refused or held call
26
+ * never runs; the wrapper returns Shrike's reason and the recovery as the
27
+ * tool's result, so the model reads why and what to do next. Tools with no
28
+ * mapping are refused by default (`onUnmapped: 'deny'`).
29
+ *
30
+ * The observe plane is a language-model middleware whose `transformParams`
31
+ * scans the latest user message on the way in, once per message, and never
32
+ * blocks. A finding is prepended to the prompt as a system message.
33
+ *
34
+ * `request_scope` is available as `gov.tool`; add it to the tool set. A held
35
+ * action is answered like a refusal whatever `onHold` says; use the SDK's
36
+ * `needsApproval` if the run itself should pause.
37
+ *
38
+ * Requires `ai` and `zod`.
39
+ */
40
+
41
+ /** The latest user message's text in a language-model prompt. */
42
+ declare function lastUserText(prompt: unknown): string;
43
+ declare class Governance extends Governance$1 {
44
+ private toolCache?;
45
+ private middlewareCache?;
46
+ private lastObserved?;
47
+ /** Wrap each tool's `execute` so Shrike judges its calls. Returns a new tool set. */
48
+ governTools<T extends ToolSet>(tools: T): T;
49
+ /** `request_scope` as an AI SDK tool; add it to your tool set under that name. */
50
+ get tool(): Tool;
51
+ /** Scan a new user message in a prompt, once. Returns the note to prepend, or empty. */
52
+ observePromptMessages(prompt: unknown): Promise<string>;
53
+ /** The observe-plane middleware for `wrapLanguageModel({ model, middleware })`. */
54
+ get middleware(): LanguageModelMiddleware;
55
+ }
56
+ /** Build the governance for one agent. */
57
+ declare function govern(guard: Guard | ScanClient, options: GovernanceOptions): Governance;
58
+
59
+ export { Governance, GovernanceOptions, govern, lastUserText };
@@ -0,0 +1,59 @@
1
+ import { ToolSet, Tool, LanguageModelMiddleware } from 'ai';
2
+ import { G as Governance$1, a as Guard, S as ScanClient, b as GovernanceOptions } from '../govern-olQtsipX.js';
3
+ export { D as Decision, O as Outcome, T as ToolMapping } from '../govern-olQtsipX.js';
4
+
5
+ /**
6
+ * Govern an agent built with the Vercel AI SDK (`ai`).
7
+ *
8
+ * ```ts
9
+ * import { generateText, wrapLanguageModel } from 'ai';
10
+ * import { ScanClient } from 'shrike-guard';
11
+ * import { govern } from 'shrike-guard/ai';
12
+ *
13
+ * const guard = new ScanClient({ apiKey: KEY });
14
+ * const gov = govern(guard, { agentId: 'invoice-agent' }).mapTool('runQuery', { surface: 'sql', arg: 'query' });
15
+ * const result = await generateText({
16
+ * model: wrapLanguageModel({ model, middleware: gov.middleware }),
17
+ * tools: gov.governTools({ runQuery, saveReport, request_scope: gov.tool }),
18
+ * prompt,
19
+ * });
20
+ * ```
21
+ *
22
+ * A thin adapter over `shrike-guard`'s framework-free core (`govern.ts`).
23
+ *
24
+ * The act plane wraps each tool's `execute`: Shrike judges the call through
25
+ * the tool's mapping before the inner `execute` runs. A refused or held call
26
+ * never runs; the wrapper returns Shrike's reason and the recovery as the
27
+ * tool's result, so the model reads why and what to do next. Tools with no
28
+ * mapping are refused by default (`onUnmapped: 'deny'`).
29
+ *
30
+ * The observe plane is a language-model middleware whose `transformParams`
31
+ * scans the latest user message on the way in, once per message, and never
32
+ * blocks. A finding is prepended to the prompt as a system message.
33
+ *
34
+ * `request_scope` is available as `gov.tool`; add it to the tool set. A held
35
+ * action is answered like a refusal whatever `onHold` says; use the SDK's
36
+ * `needsApproval` if the run itself should pause.
37
+ *
38
+ * Requires `ai` and `zod`.
39
+ */
40
+
41
+ /** The latest user message's text in a language-model prompt. */
42
+ declare function lastUserText(prompt: unknown): string;
43
+ declare class Governance extends Governance$1 {
44
+ private toolCache?;
45
+ private middlewareCache?;
46
+ private lastObserved?;
47
+ /** Wrap each tool's `execute` so Shrike judges its calls. Returns a new tool set. */
48
+ governTools<T extends ToolSet>(tools: T): T;
49
+ /** `request_scope` as an AI SDK tool; add it to your tool set under that name. */
50
+ get tool(): Tool;
51
+ /** Scan a new user message in a prompt, once. Returns the note to prepend, or empty. */
52
+ observePromptMessages(prompt: unknown): Promise<string>;
53
+ /** The observe-plane middleware for `wrapLanguageModel({ model, middleware })`. */
54
+ get middleware(): LanguageModelMiddleware;
55
+ }
56
+ /** Build the governance for one agent. */
57
+ declare function govern(guard: Guard | ScanClient, options: GovernanceOptions): Governance;
58
+
59
+ export { Governance, GovernanceOptions, govern, lastUserText };