ZeroLeaksDocs
Shield SDKProvider Wrappers

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:

  1. Copies the request parameters and messages so your objects are not modified.
  2. Hardens the system field, whether it is a string or an array of text blocks (unless harden: false), adding the canary if you set one. Each text block is hardened on its own.
  3. Runs detection on every user message, including earlier turns, for both string content and arrays of text blocks (unless detect: false). It also runs detection on every tool_result, document, search_result, and web_fetch_tool_result block in any message (unless scanToolResults: false). See Tool results.
  4. Calls the original Anthropic method.
  5. Checks every text block in the response for prompt leaks (unless sanitize: false or there is no system prompt) and runs the output guard on it (unless output: false). It does the same for every key and string inside each tool_use block's input, and reports each block's findings in one onOutputFindings call. 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

OptionTypeDefaultDescription
systemPromptstringthe system paramThe system prompt to check output against. Defaults to the request's system text, before hardening.
hardenHardenOptions | false{}Hardening options, or false to disable
detectLocalDetectOptions | false{}Detection options for user messages, or false to disable
scanToolResultsboolean | LocalDetectOptionstrueDetection on tool results and documents. true uses the detect options, an object replaces them, and false disables it. detect: false does not disable it.
sanitizeSanitizeOptions | false{}Prompt leak sanitization options, or false to disable
outputScanOutputOptions | false{}Output scanning options, or false to disable. By default credentials and exfiltration links are redacted.
onOutputFindings(findings: OutputFinding[]) => voidnoneCalled with the findings each time output scanning finds something
blockOnOutputFindingsbooleanfalseWhen true, throw OutputBlockedError instead of redacting if a finding is high or critical severity
canarystring | booleannonePlant 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.
streamingChunkSizenumber8192Characters per chunk in "chunked" mode
throwOnLeakbooleanfalseWhen 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") => voidnoneCalled when injection is detected, in both modes, with where it was found
onLeakDetected(result: SanitizeResult) => voidnoneCalled 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.

On this page