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.
The shieldOpenAI wrapper adds Shield to your existing OpenAI client. It intercepts every chat.completions.create and responses.create call to harden system prompts, detect injections in user messages and tool results, and redact prompt leaks, credentials, and exfiltration links from responses.
Usage
import OpenAI from "openai";
import { shieldOpenAI } from "@zeroleaks/shield/openai";
const client = shieldOpenAI(new OpenAI(), {
systemPrompt: "You are a financial advisor...",
onDetection: "block",
});
const response = await client.chat.completions.create({
model: "gpt-5.5",
messages: [
{ role: "system", content: "You are a financial advisor..." },
{ role: "user", content: userInput },
],
});How it works
On every call to chat.completions.create, Shield:
- Copies the messages array so your objects are not modified.
- Hardens every message with
role: "system"orrole: "developer"(unlessharden: false), adding the canary if you set one. Array content is replaced with a single text part. - Runs detection on every
usermessage (unlessdetect: false) and on everytoolandfunctionmessage (unlessscanToolResults: false), including earlier turns. Text parts are joined; image parts are ignored. - Calls the original OpenAI method.
- For every choice, checks
message.contentfor prompt leaks (unlesssanitize: falseor there is no system prompt) and runs the output guard on it (unlessoutput: false). It does the same for each tool call'sfunction.argumentsor custom toolinput, and the legacyfunction_call.arguments. When text is redacted, the choice'slogprobsis set tonull. Streaming responses are handled as described in Streaming.
For prompt leak checks, Shield uses systemPrompt if provided. Otherwise, it joins the text of every system and developer message before hardening.
Responses API
The wrapper also guards client.responses.create when your client provides it (openai 4.87 and later):
const response = await client.responses.create({
model: "gpt-5.5",
instructions: "You are a financial advisor...",
input: userInput,
});
console.log(response.output_text); // guardedOn every call to responses.create, Shield:
- Copies the
inputitems so your objects are not modified. - Hardens
instructionswhen it is a string, and every input message withrole: "system"orrole: "developer"(unlessharden: false). String content stays a string; an array of parts becomes a singleinput_textpart. - Runs detection on
inputwhen it is a string, and on every input message withrole: "user", joining itsinput_textparts and ignoring images and files. Tool outputs ininputare scanned as listed under Tool results. - Calls the original OpenAI method.
- Guards the text of every
output_textpart in the response'smessageitems, theargumentsoffunction_callitems, and theinputofcustom_tool_callitems, and rebuildsresponse.output_textfrom the guarded parts. A redacted part gets an emptylogprobslist.
For prompt leak checks, Shield uses systemPrompt if provided. Otherwise, it joins instructions and the text of every system and developer message in input before hardening. Detection sees only what you send: with previous_response_id or conversation, the earlier turns OpenAI stores are not rescanned.
Streaming
With stream: true, create() resolves immediately to a plain async iterable of the API's events. "buffer" and "chunked" behave the same way here:
- Each text's
response.output_text.deltaevents are held until itsresponse.output_text.doneevent, each function call'sresponse.function_call_arguments.deltaevents until itsresponse.function_call_arguments.done, and each custom tool call'sresponse.custom_tool_call_input.deltaevents until itsresponse.custom_tool_call_input.done. The full text is guarded then. - If nothing was redacted, the buffered deltas are emitted unchanged. Otherwise, the guarded text is emitted as new delta events of 64 characters each, shaped like the originals: the same
item_id,output_index, andcontent_index, the buffered deltas'sequence_numbers in order (extra pieces repeat the last one), and emptylogprobs. - The
doneevent carries the guarded text, and so doresponse.content_part.done,response.output_item.done, and the finalresponse.completed,response.incomplete, orresponse.failedevent. - Every other event passes through as it arrives, in order.
- A leak with
throwOnLeak, or a finding withblockOnOutputFindings, throws from yourfor awaitloop at thedoneevent, after all preceding events.
"passthrough" returns the SDK's stream unchanged. Annotation offsets, such as a url_citation's start_index and end_index, are not adjusted after a redaction. response.refusal.delta and response.mcp_call_arguments.delta events are not held or guarded.
Options
| Option | Type | Default | Description |
|---|---|---|---|
systemPrompt | string | system and developer messages | The system prompt to check output against. Defaults to the text of every system and developer message (Responses API: instructions too), 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. 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 (Chat Completions only) |
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 |
Blocking vs warning
By default, onDetection is "block": if an injection is detected in a user message or a tool result, Shield throws InjectionDetectedError with the risk level, the matched categories, and source, and the request is never sent. To log instead of blocking:
const client = shieldOpenAI(new OpenAI(), {
onDetection: "warn",
onInjectionDetected: (result, source) => {
console.warn(`Injection in ${source} input: ${result.risk}`, result.matches);
},
});What is not covered
Only chat.completions.create and responses.create are guarded. Every other method on the wrapped client still works, such as chat.completions.retrieve(), responses.stream(), responses.parse(), responses.retrieve(), and client.beta, but its requests and responses are not checked or guarded. To stream a guarded response, call create() with 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 and What is not scanned.
Provider Wrappers
Wrappers for OpenAI, Anthropic, Groq, Google Gen AI, Mistral, LangChain.js, MCP clients, and the Vercel AI SDK that harden system prompts, check user messages and tool results for injection, and redact prompt leaks, credentials, exfiltration links, and canaries from responses, and guardrails for the OpenAI Agents SDK.
Anthropic Provider
Wrap your Anthropic client with Shield for prompt hardening, injection detection in user messages, tool results, and documents, and output guarding.