Anthropic Provider
Wrap your Anthropic client with Shield for prompt hardening, injection detection in user messages, tool results, and documents, and output guarding.
The shieldAnthropic wrapper adds Shield to your existing Anthropic client. It intercepts every messages.create call to harden the system prompt, detect injections in user messages, tool results, and documents, and redact prompt leaks, credentials, and exfiltration links from responses.
Usage
import Anthropic from "@anthropic-ai/sdk";
import { shieldAnthropic } from "@zeroleaks/shield/anthropic";
const client = shieldAnthropic(new Anthropic(), {
systemPrompt: "You are a support agent...",
});
const response = await client.messages.create({
model: "claude-sonnet-5",
system: "You are a support agent...",
messages: [{ role: "user", content: userInput }],
max_tokens: 1024,
});How it works
On every call to messages.create, Shield:
- Copies the request parameters and messages so your objects are not modified.
- Hardens the
systemfield, whether it is a string or an array of text blocks (unlessharden: false), adding the canary if you set one. Each text block is hardened on its own. - Runs detection on every
usermessage, including earlier turns, for both string content and arrays of text blocks (unlessdetect: false). It also runs detection on everytool_result,document,search_result, andweb_fetch_tool_resultblock in any message (unlessscanToolResults: false). See Tool results. - Calls the original Anthropic method.
- Checks every
textblock in the response for prompt leaks (unlesssanitize: falseor there is no system prompt) and runs the output guard on it (unlessoutput: false). It does the same for every key and string inside eachtool_useblock'sinput, and reports each block's findings in oneonOutputFindingscall. Streaming responses are handled as described in Streaming.
For prompt leak checks, Shield uses systemPrompt if provided. Otherwise, it uses the request's system text before hardening, with its text blocks joined.
Options
| Option | Type | Default | Description |
|---|---|---|---|
systemPrompt | string | the system param | The system prompt to check output against. Defaults to the request's system text, before hardening. |
harden | HardenOptions | false | {} | Hardening options, or false to disable |
detect | LocalDetectOptions | false | {} | Detection options for user messages, or false to disable |
scanToolResults | boolean | LocalDetectOptions | true | Detection on tool results and documents. true uses the detect options, an object replaces them, and false disables it. detect: false does not disable it. |
sanitize | SanitizeOptions | false | {} | Prompt leak sanitization options, or false to disable |
output | ScanOutputOptions | false | {} | Output scanning options, or false to disable. By default credentials and exfiltration links are redacted. |
onOutputFindings | (findings: OutputFinding[]) => void | none | Called with the findings each time output scanning finds something |
blockOnOutputFindings | boolean | false | When true, throw OutputBlockedError instead of redacting if a finding is high or critical severity |
canary | string | boolean | none | Plant a canary in the hardened system prompt and treat its appearance in output as a leak. true creates one. See Canary. |
streamingSanitize | "buffer" | "chunked" | "passthrough" | "buffer" | How streamed output is guarded. See Streaming. |
streamingChunkSize | number | 8192 | Characters per chunk in "chunked" mode |
throwOnLeak | boolean | false | When true, throw LeakDetectedError instead of redacting a prompt leak or a canary |
onDetection | "block" | "warn" | "block" | "block" throws InjectionDetectedError; "warn" calls onInjectionDetected and sends the request anyway |
onInjectionDetected | (result: LocalDetectResult, source: "user" | "tool") => void | none | Called when injection is detected, in both modes, with where it was found |
onLeakDetected | (result: SanitizeResult) => void | none | Called when a prompt leak or a canary is found in the output |
Content blocks
Anthropic messages can contain arrays of content blocks instead of plain strings. Shield joins the text blocks of each user message for detection:
const response = await client.messages.create({
model: "claude-sonnet-5",
system: "You are a support agent...",
messages: [
{
role: "user",
content: [
{ type: "text", text: "Help me with this document:" },
{ type: "text", text: documentContent },
],
},
],
max_tokens: 1024,
});Both text blocks are scanned as user input. Content from a tool or retrieval step is also scanned in the tool_result or document block where it usually arrives. Each block is checked separately with the scanToolResults options:
const response = await client.messages.create({
model: "claude-sonnet-5",
system: "You are a support agent...",
messages: [
{ role: "user", content: "Summarize the page I linked." },
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_1", name: "fetch_page", input: { url } }],
},
{
role: "user",
content: [{ type: "tool_result", tool_use_id: "toolu_1", content: pageText }],
},
],
max_tokens: 1024,
});If pageText carries an injection, the call throws InjectionDetectedError with source: "tool" before the request is sent. Inside a tool_result, the text, document, and search result blocks are read; image blocks are not. Documents are read when their source is plain text or content blocks; PDF, URL, and file sources are not.
Streaming
See Streaming for the three streamingSanitize modes. In "buffer" and "chunked" mode, Shield guards the text of each content block and the tool input JSON of each tool_use block (its input_json_delta events). Every other event, including thinking_delta, signature_delta, and citations_delta, is replayed unchanged. In "chunked" mode, each block is scanned separately. A delta event is omitted if all its text is buffered, and any remaining buffered text is emitted in one more delta event just before the block's content_block_stop.
What is not covered
Only messages.create is guarded. messages.stream(), messages.countTokens(), client.beta.messages, and every other method still work on the wrapped client, but their requests and responses are not checked or guarded. For a guarded stream, use messages.create({ stream: true }). create() returns a plain Promise, and a guarded stream is a plain async iterable, so the SDK's withResponse() and toReadableStream() are unavailable at runtime, although TypeScript accepts them. See Shared behavior. Thinking blocks and citations in the response are not scanned; see What is not scanned.
OpenAI Provider
Wrap your OpenAI client with Shield for prompt hardening, injection detection in user messages and tool results, and output guarding, on Chat Completions and the Responses API.
Groq Provider
Wrap your Groq client with Shield for prompt hardening, injection detection in user messages and tool results, and output guarding.