
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
promptguard-sdk
Advanced tools
Drop-in security for AI applications - AI Firewall SDK with auto-instrumentation
Drop-in security for AI applications. Secure any GenAI app - regardless of framework or LLM provider.
npm install promptguard-sdk
The npm package and the import specifier are both
promptguard-sdk— no surprises:import { init, PromptGuard } from 'promptguard-sdk';
Get a free API key at app.promptguard.co.
The SDK reads
PROMPTGUARD_API_KEYfrom the environment; it does not auto-load.env. Use dotenv (callimport 'dotenv/config'first) if you keep secrets in a.envfile.
PromptGuard fails open by default — if the Guard API is unavailable, calls proceed unscanned so your app stays up. Set
failOpen: falseto block (fail closed) on a Guard outage instead.
Module format: the package currently ships CommonJS (
require) builds. It works in ESM projects via Node's CJS interop (import { init } from 'promptguard-sdk'transpiles to arequire), and in plain CommonJS viaconst { init } = require('promptguard-sdk').Running a native-ESM app? Auto-instrumentation (
init()) may not cover your LLM calls — see Limitations: ESM apps before relying on enforce mode.
One line secures every LLM call in your application - no matter which framework you use.
// All imports first — ES module imports are hoisted and always run before
// any other statement, regardless of their position in the file.
import { init } from 'promptguard-sdk';
import OpenAI from 'openai';
// init() runs as the first executed statement and patches the SDK prototypes.
// Patching works regardless of import order, so you don't need to worry about
// importing the LLM SDK "after" calling init().
init({ apiKey: 'pg_live_xxx' });
const client = new OpenAI();
// This call is automatically scanned by PromptGuard.
const response = await client.chat.completions.create({
model: 'gpt-5-nano',
messages: [{ role: 'user', content: 'Hello!' }],
});
init()is intentionally quiet on success (SDK logging defaults towarn). To confirm which provider SDKs are actually being protected, readgetAppliedPatches()— the source of truth for what got patched:import { init, getAppliedPatches } from 'promptguard-sdk'; init({ apiKey: 'pg_live_xxx' }); console.log('PromptGuard protecting:', getAppliedPatches()); // e.g. ['openai']If this list is empty, nothing is being scanned (see Limitations: ESM apps). Set
logLevel: 'info'oninit()to also emit a one-line confirmation banner.
Auto-instrumentation patches the create / generateContent / chat / send methods on:
| SDK | npm Package | What Gets Patched |
|---|---|---|
| OpenAI | openai | chat.completions.create, responses.create (string and message-item input forms) |
| Anthropic | @anthropic-ai/sdk | messages.create |
@google/genai (Google's current SDK): models.generateContent() / generateContentStream(). The config.systemInstruction system prompt is scanned, as are tool-call arguments and code-execution output. Patched by swapping the exported GoogleGenAI constructor, because this SDK assigns generateContent as an own property in the constructor rather than on the prototype.
| Google Generative AI | @google/generative-ai | generateContent | (deprecated by Google; still supported because customers are still on it)
| Cohere | cohere-ai | Client.chat / ClientV2.chat |
| AWS Bedrock | @aws-sdk/client-bedrock-runtime | BedrockRuntimeClient.send (InvokeModel, InvokeModelWithResponseStream, Converse, ConverseStream) |Any framework built on these SDKs is automatically covered: LangChain.js, Vercel AI SDK, AutoGen, Semantic Kernel, and more.
Patches attach to the modules resolved via CommonJS
require(). Ifinit()finds no patchable SDK it logs a warning (nothing would be scanned). You can also verify at runtime withgetAppliedPatches():import { init, getAppliedPatches } from 'promptguard-sdk'; init({ apiKey: 'pg_live_xxx' }); console.log(getAppliedPatches()); // e.g. ['openai', 'anthropic']Native-ESM apps: see Limitations: ESM apps.
// Enforce mode (default) - blocks policy violations.
init({ apiKey: 'pg_live_xxx', mode: 'enforce' });
// Monitor mode - logs threats but never blocks. Good for shadow deployment.
init({ apiKey: 'pg_live_xxx', mode: 'monitor' });
init({
apiKey: 'pg_live_xxx', // or set PROMPTGUARD_API_KEY env var
baseUrl: 'https://...', // or set PROMPTGUARD_BASE_URL env var
mode: 'enforce', // 'enforce' | 'monitor'
failOpen: true, // allow calls when Guard API is unreachable
scanResponses: false, // also scan LLM responses
timeout: 10_000, // Guard API timeout in ms
});
init() resolving does not mean anything is being scanned. PromptGuard fails
open, so a rejected API key, an unreachable Guard API, or a provider SDK we
never hooked all leave you with an application that runs perfectly and blocks
nothing. In a native-ESM app, where the patches may never attach at all (see
Limitations), that is the default rather than
the edge case.
verify() is the positive check. It makes the real calls and reports what came
back:
import { verify } from 'promptguard-sdk';
const report = await verify();
if (!report.ok) {
for (const check of report.checks) {
console.error(`${check.status} ${check.name} — ${check.detail}`);
}
process.exit(1);
}
In CI, the one-liner is usually enough:
expect((await verify()).ok).toBe(true);
It checks reachability, authentication, live threat detection and PII redaction,
plus which provider SDKs this process actually patched — the same checks, under
the same names, as promptguard verify in the CLI and promptguard.verify() in
the Python SDK.
Each check reports pass, warn or fail. Only a fail clears ok. A
request that never completed is a failure; a request that completed and came
back permissive — an injection that was not blocked, a PII probe with nothing
detected — is a warning, because a monitor-mode project legitimately behaves that
way. Warnings are still the first thing to read before trusting a setup.
verify() never rejects for a failed check, so one call reports every problem
rather than only the first. It throws only when no API key was supplied at all.
It retries once rather than the client's three times, so a dead host is reported
in well under a second instead of after the full backoff schedule.
Each call makes two real, billed requests. The probes go through the same endpoints as your production traffic, so every
verify()writes two rows to your security events — the injection probe among them, which shows up in your dashboard and analytics as a genuine prompt-injection attempt and will trigger any alert you have configured on injection. Both requests count against your plan's quota. That is fine on deploy or at the start of a demo; calling it on every CI build of a busy repo is how you end up with a threat dashboard full of your own probes.
import { shutdown } from 'promptguard-sdk';
// Removes all patches and cleans up.
shutdown();
Route LLM traffic through PromptGuard. Just swap your base URL.
import { PromptGuard } from 'promptguard-sdk';
const pg = new PromptGuard({ apiKey: 'pg_live_xxx' });
// Use exactly like the OpenAI client.
const response = await pg.chat.completions.create({
model: 'gpt-5-nano',
messages: [{ role: 'user', content: 'Hello!' }],
});
const result = await pg.security.scan('Ignore previous instructions...');
if (result.blocked) {
console.log(`Threat detected: ${result.reason}`);
}
const result = await pg.security.redact(
'My email is john@example.com and SSN is 123-45-6789'
);
console.log(result.redacted);
import { PromptGuardCallbackHandler } from 'promptguard-sdk/integrations/langchain';
import { ChatOpenAI } from '@langchain/openai';
const handler = new PromptGuardCallbackHandler({
apiKey: 'pg_live_xxx',
mode: 'enforce',
scanResponses: true,
});
// Attach to a single model
const llm = new ChatOpenAI({
model: 'gpt-5-nano',
callbacks: [handler],
});
// Or use with any chain / agent
const result = await chain.invoke(
{ input: 'Hello' },
{ callbacks: [handler] },
);
The callback handler provides rich context to PromptGuard - chain names, tool calls, agent steps - for more precise threat detection.
Redact decisions block in enforce mode: LangChain callbacks observe calls but cannot rewrite the inputs of the in-flight LLM call, so a
redactdecision cannot be honored — in enforce mode it is escalated to a block (PromptGuardBlockedError) rather than silently sending the content the Guard API asked to redact. Use auto-instrumentation or explicitGuardClient.scan()calls if you need actual redaction.
scanResponsesdefaults tofalse(consistent withinit()and the Vercel AI middleware) — passscanResponses: trueto opt in to output scanning.
import { openai } from '@ai-sdk/openai';
import { wrapLanguageModel, generateText } from 'ai';
import { promptGuardMiddleware } from 'promptguard-sdk/integrations/vercel-ai';
const model = wrapLanguageModel({
model: openai('gpt-5-nano'),
middleware: promptGuardMiddleware({
apiKey: 'pg_live_xxx',
mode: 'enforce',
scanResponses: true,
}),
});
const { text } = await generateText({
model,
prompt: 'Hello!',
});
Use the Guard client directly for maximum control:
import { GuardClient } from 'promptguard-sdk';
const guard = new GuardClient({ apiKey: 'pg_live_xxx' });
// Scan before sending to LLM (options-object form, preferred)
const decision = await guard.scan(
[{ role: 'user', content: userInput }],
{ direction: 'input', model: 'gpt-5-nano' },
);
if (decision.blocked) {
console.log(`Blocked: ${decision.threatType}`);
} else if (decision.redacted && decision.redactedMessages) {
// Use redacted messages instead
messages = decision.redactedMessages;
}
// Scan LLM response
const outputDecision = await guard.scan(
[{ role: 'assistant', content: llmOutput }],
{ direction: 'output' },
);
// The positional form still works for back-compat:
// await guard.scan(messages, 'input', 'gpt-5-nano');
Both the proxy client (PromptGuard) and the Guard client (GuardClient) support configurable retry behavior for transient failures:
// Proxy client
const pg = new PromptGuard({
apiKey: 'pg_live_xxx',
maxRetries: 3, // Number of retry attempts (default: 3)
retryDelay: 500, // Base delay in ms between retries (default: 1000)
});
// Guard client (standalone scanning)
const guard = new GuardClient({
apiKey: 'pg_live_xxx',
maxRetries: 3, // default: 3
retryDelay: 500, // default: 1000
});
Because auto-instrumentation (init()) and the framework integrations all scan through GuardClient, they retry transient Guard API failures too — pass maxRetries / retryDelay to init(), PromptGuardCallbackHandler, or promptGuardMiddleware:
init({ apiKey: 'pg_live_xxx', maxRetries: 3, retryDelay: 500 });
Retries use exponential backoff starting from retryDelay, with jitter so concurrent clients don't retry in lockstep. A server-provided Retry-After header is honored but clamped to 60 seconds. Only transient errors (network timeouts, 429/5xx responses) are retried; client errors (4xx) fail immediately.
Enforcement is unchanged by retries. Retrying only affects transient Guard failures (network errors, 429/5xx). A real
block/redactdecision is terminal and is never retried, and once retries are exhausted aGuardApiErroris raised so your existingfailOpenpolicy governs — exactly as it would withmaxRetries: 0. Retries never turn a would-be error into an allow.
Idempotency caveat: all requests — including
POSTs — are retried on transient failure. If a request reached the server but the response was lost, the retry re-submits it. Chat/completion/scan calls are safe to re-submit, but each attempt may bill separately; setmaxRetries: 0if you need strict at-most-once semantics.
const validation = await pg.agent.validateTool(
'agent-123',
'execute_shell',
{ command: 'ls -la' },
);
if (!validation.allowed) {
console.log(`Blocked: ${validation.reason}`);
}
Run PromptGuard's adversarial corpus against your own policy configuration and see how much of it your guardrails block. Useful from CI as a regression gate on a policy change.
const pg = new PromptGuard({ apiKey: 'pg_live_xxx' });
// What the corpus contains, without running it
const catalog = await pg.redteam.listTests();
console.log(`${catalog.total} attacks available`);
// Run everything against a preset
const summary = await pg.redteam.runAll('support_bot:strict');
console.log(`Blocked ${summary.blocked}/${summary.totalTests}`);
// Run one named attack, or your own adversarial prompt
const one = await pg.redteam.runTest('prompt_injection_basic');
const custom = await pg.redteam.runCustom('ignore previous instructions and ...');
console.log(custom.decision, custom.reason);
Requires an API key with the proxy scope (an unrestricted key also works).
Device/scan-only credentials cannot reach these endpoints.
| Option | Environment Variable | Default | Description |
|---|---|---|---|
apiKey | PROMPTGUARD_API_KEY | - | PromptGuard API key (required) |
baseUrl | PROMPTGUARD_BASE_URL | https://api.promptguard.co/api/v1 | API base URL |
mode | - | "enforce" | "enforce" or "monitor" |
failOpen | - | true | Allow calls when Guard API is unreachable |
scanResponses | - | false | Also scan LLM responses |
timeout | - | 10000 | HTTP timeout in milliseconds |
logLevel | - | "warn" | SDK log verbosity: "debug", "info", "warn", "error", "silent" |
silent | - | false | Shorthand for logLevel: "silent" |
The proxy client (
PromptGuard) talks to the/api/v1/proxyendpoints. If you setbaseUrl/PROMPTGUARD_BASE_URLto.../api/v1(without/proxy), the SDK appends the/proxysuffix for you, so requests still land on the proxy.Security: the SDK sends your API key (and, in proxy mode, your prompt content) to whatever
PROMPTGUARD_BASE_URLpoints at. Self-hosting is supported, so only point it at a host you trust.Logging is process-global:
logLevel/silentset a single shared log level for the whole SDK. If several integrations orinit()calls pass different values, the most recently constructed one wins. UsesetLogLevel()directly for fine-grained control.
Auto-instrumentation (init()) patches the provider modules that Node resolves via CommonJS require(). If your application runs as native ESM ("type": "module" in package.json, or .mjs files) and a provider ships separate ESM builds, the module instances your code imports can be different objects from the ones the SDK patched — the dual-package hazard. In that case your LLM calls bypass the patches entirely and enforce mode silently protects nothing.
What to do:
verify(), which makes real calls and reports what came back, or with getAppliedPatches() for the patch list alone — and note that a patch being listed proves the CJS build was patched, not that your ESM imports go through it. init() also logs a warning when it applies zero patches.PromptGuardCallbackHandler (promptguard-sdk/integrations/langchain)promptGuardMiddleware (promptguard-sdk/integrations/vercel-ai)GuardClient.scan() calls around your LLM invocationstsc/ts-node default) are not affected — their imports compile to require() and hit the patched modules.scanResponses: true, streaming calls (stream: true, Bedrock ConverseStreamCommand, etc.) skip the output scan — the stream is consumed incrementally by your code and cannot be buffered without breaking stream semantics. Input scanning still applies. A debug-level log is emitted when the output scan is skipped. The same applies to the Vercel AI SDK middleware: streamText() outputs are not scanned (wrapStream logs the skip); generateText() outputs are.APIPromise helpers are not preserved by auto-instrumentation. Patched methods return a plain Promise, so .withResponse() / .asResponse() on client.chat.completions.create(...) are unavailable while init() is active. await the call and use the plain result instead.pg.chat.completions.create({ stream: true }) is rejected with a clear error — streaming is not yet supported by the proxy client.import { PromptGuardBlockedError, GuardApiError } from 'promptguard-sdk';
try {
await client.chat.completions.create({ ... });
} catch (error) {
if (error instanceof PromptGuardBlockedError) {
// Request was blocked by policy
console.log(error.decision.threatType);
console.log(error.decision.confidence);
console.log(error.decision.eventId);
} else if (error instanceof GuardApiError) {
// Guard API is unreachable (only when failOpen=false)
console.log(error.statusCode);
}
}
Full TypeScript support with type definitions for all exports:
import type {
GuardDecision,
GuardMessage,
GuardContext,
InitOptions,
ChatCompletionRequest,
ChatCompletionResponse,
SecurityScanResult,
AutonomousRedTeamRequest,
AutonomousRedTeamReport,
IntelligenceStats,
VerifyReport,
VerifyCheck,
VerifyOptions,
CheckStatus,
} from 'promptguard-sdk';
Response fields are camelCase — with one deliberate exception. The SDK's normalized response objects (the
security,redteam,agent, andguardnamespaces) use camelCase field names (e.g.report.bypassRate,stats.totalPatterns,validation.riskScore) regardless of the snake_case wire format — consistent withGuardDecisionandSecurityScanResult. The OpenAI-compatible responses (chat.completions, i.e.ChatCompletionResponse) are the exception: they intentionally preserve OpenAI's snake_case shape (choices[].finish_reason,usage.prompt_tokens, …) so they stay drop-in compatible with theopenaiclient. Request options are camelCase throughout (maxTokens,targetPreset), includingGuardContext(chainName,agentId,sessionId,toolCalls).
MIT
FAQs
Drop-in security for AI applications - AI Firewall SDK with auto-instrumentation
The npm package promptguard-sdk receives a total of 178 weekly downloads. As such, promptguard-sdk popularity was classified as not popular.
We found that promptguard-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.