llm-firewall-js
A lightweight, framework-agnostic JavaScript firewall for protecting LLM API routes from prompt attacks, secret leakage, spam abuse, and runaway usage costs.
npm install llm-firewall-jsimport { createLLMFirewall } from "llm-firewall-js"
const firewall = createLLMFirewall()What Is LLM Firewall?
llm-firewall-js is a server-side guardrail layer for applications that use large language models.
It sits between your frontend and your LLM provider:
Frontend -> Your API Route -> llm-firewall-js -> LLM Provider
It does not call OpenAI, Anthropic, Gemini, Groq, or any other provider for you. Instead, it protects your own API route before a request reaches any LLM and inspects the response before it returns to users.
The package is designed for developers shipping public AI features, AI widgets, prototypes, portfolios, SaaS assistants, internal tools, and no-code/low-code AI integrations.
Features
- Provider-agnostic: works with OpenAI, Anthropic, Gemini, Groq, local models, or any other LLM.
- Framework-agnostic: works with Next.js, Express, Node.js, Vercel API Routes, and generic backend APIs.
- Prompt attack detection for common jailbreak and prompt-extraction attempts.
- Secret extraction blocking for API keys, environment variables, hidden prompts, and owner tokens.
- Output leak detection before model responses reach users.
- Input size limits to reduce high-token abuse.
- Per-IP burst rate limiting.
- Per-IP daily request limits.
- Per-IP daily token-estimate limits.
- Repeated payload detection for spam loops.
- CORS/origin handling with exact domains, wildcards, or
*. - Owner bypass token for private testing.
- Kill switch for quickly disabling all AI calls.
- Optional Upstash Redis support for persistent serverless limits.
- Zero runtime dependencies.
Installation
npm install llm-firewall-jsRequirements:
- Node.js
>=18 - Server-side runtime
Do not use this package directly in public browser code. It is built for API routes and backend servers.
Quick Start
import { createLLMFirewall } from "llm-firewall-js"
const firewall = createLLMFirewall()
export default async function handler(req, res) {
firewall.setCors(req, res)
if (req.method === "OPTIONS") {
res.status(204).end()
return
}
const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body || {}
const inputCheck = await firewall.inspectInput({
request: req,
body,
text: body.question || body.prompt || "",
})
if (!inputCheck.ok) {
firewall.reject(res, inputCheck)
return
}
const modelData = await callYourLLMProvider(body)
const outputCheck = firewall.inspectOutput(modelData)
if (!outputCheck.ok) {
firewall.reject(res, outputCheck)
return
}
res.status(200).json(outputCheck.data)
}API Reference
createLLMFirewall(options?)
Creates a firewall instance.
const firewall = createLLMFirewall({
allowedOrigins: ["https://example.com"],
maxInputChars: 4000,
rateLimitMax: 8,
})Returns:
{
config,
setCors,
inspectInput,
inspectOutput,
reject,
ownerBypass,
getClientIp
}firewall.setCors(request, response)
Sets CORS headers on Node, Express, Vercel, or Fetch Response objects.
Use this for:
OPTIONSpreflight requests- normal API responses
- blocked firewall responses
firewall.inspectInput({ request, body, text })
Inspects user input before it reaches your LLM provider.
const inputCheck = await firewall.inspectInput({
request: req,
body: req.body,
text: req.body.question,
})Returns:
{ ok: true }or:
{
ok: false,
status: 400,
reason: "This request looks like a prompt attack or secret-extraction attempt."
}firewall.inspectOutput(data)
Inspects the model output before returning it to users.
const outputCheck = firewall.inspectOutput(modelData)Returns:
{ ok: true, data: modelData }or:
{
ok: false,
status: 500,
reason: "The model response was blocked because it looked like it could expose hidden instructions or secrets."
}firewall.reject(response, inspection)
Sends or creates a JSON blocked response.
For Express/Vercel:
firewall.reject(res, inputCheck)For Fetch/Next.js route handlers:
const blocked = firewall.reject(null, inputCheck)
firewall.setCors(request, blocked)
return blockedBlocked response shape:
{
"error": "Request blocked by LLM firewall.",
"firewall": {
"blocked": true,
"reason": "blocked"
}
}firewall.ownerBypass(request)
Returns true when a request includes the configured owner bypass token.
firewall.getClientIp(request)
Reads client IP from common proxy and serverless headers.
Configuration
All options can be passed directly:
const firewall = createLLMFirewall({
enabled: true,
killSwitch: false,
ownerBypassToken: process.env.OWNER_BYPASS_TOKEN,
allowedOrigins: ["https://example.com", "https://*.example.com"],
maxInputChars: 8000,
maxOutputTokens: 650,
rateLimitWindowMs: 60_000,
rateLimitMax: 12,
dailyRequestLimit: 250,
dailyTokenEstimateLimit: 180_000,
repeatedPayloadWindowMs: 10 * 60_000,
repeatedPayloadMax: 8,
})Custom Text Extraction
By default, the firewall checks common body fields:
input, prompt, question, message, messages, jobDescription, text
For custom payloads:
const firewall = createLLMFirewall({
textExtractor(body) {
return [
body.userMessage,
body.context,
body.uploadedText,
].join("\n")
},
})Custom Rules
const firewall = createLLMFirewall({
suspiciousPatterns: [
/ignore\s+previous\s+instructions/i,
/reveal\s+(the\s+)?system\s+prompt/i,
/my-private-internal-term/i,
],
outputLeakPatterns: [
/OPENAI_API_KEY/i,
/DATABASE_URL/i,
/\b(sk|sk-proj)-[A-Za-z0-9_-]{20,}\b/i,
],
})Environment Variables
Every option can also be configured through environment variables.
| Variable | Default | Purpose |
|---|---|---|
LLM_FIREWALL_ENABLED |
true |
Turn the firewall on/off. Set false to disable. |
AI_KILL_SWITCH |
false |
Stop all AI calls immediately. |
OWNER_BYPASS_TOKEN |
empty | Private token for owner-only testing. |
ALLOWED_ORIGINS |
* |
Comma-separated origin allowlist. Supports wildcards. |
MAX_INPUT_CHARS |
8000 |
Maximum extracted input length. |
MAX_OUTPUT_TOKENS |
650 |
Estimated output token allowance for usage limits. |
RATE_LIMIT_WINDOW_MS |
60000 |
Burst rate-limit window. |
RATE_LIMIT_MAX |
12 |
Requests allowed per window per IP. |
DAILY_REQUEST_LIMIT |
250 |
Daily request cap per IP. |
DAILY_TOKEN_ESTIMATE_LIMIT |
180000 |
Daily estimated token cap per IP. |
REPEATED_PAYLOAD_WINDOW_MS |
600000 |
Window for repeated payload detection. |
REPEATED_PAYLOAD_MAX |
8 |
Repeated identical payloads allowed per window. |
UPSTASH_REDIS_REST_URL |
empty | Optional Upstash Redis REST URL. |
UPSTASH_REDIS_REST_TOKEN |
empty | Optional Upstash Redis REST token. |
Recommended starter settings for public demos:
MAX_INPUT_CHARS=4000
MAX_OUTPUT_TOKENS=500
RATE_LIMIT_MAX=8
DAILY_REQUEST_LIMIT=120
DAILY_TOKEN_ESTIMATE_LIMIT=90000
Examples
Examples are included in the examples folder.
Express
import express from "express"
import { createLLMFirewall } from "llm-firewall-js"
const app = express()
const firewall = createLLMFirewall()
app.use(express.json({ limit: "32kb" }))
app.post("/api/chat", async (req, res) => {
firewall.setCors(req, res)
const inputCheck = await firewall.inspectInput({
request: req,
body: req.body,
text: req.body.question || req.body.prompt || "",
})
if (!inputCheck.ok) {
firewall.reject(res, inputCheck)
return
}
const modelData = await callYourLLMProvider(req.body)
const outputCheck = firewall.inspectOutput(modelData)
if (!outputCheck.ok) {
firewall.reject(res, outputCheck)
return
}
res.json(outputCheck.data)
})Next.js App Router
Use in app/api/chat/route.js.
import { createLLMFirewall } from "llm-firewall-js"
const firewall = createLLMFirewall()
export async function POST(request) {
const body = await request.json()
const inputCheck = await firewall.inspectInput({
request,
body,
text: body.question || body.prompt || "",
})
if (!inputCheck.ok) {
const blocked = firewall.reject(null, inputCheck)
firewall.setCors(request, blocked)
return blocked
}
const modelData = await callYourLLMProvider(body)
const outputCheck = firewall.inspectOutput(modelData)
const response = Response.json(outputCheck.ok ? outputCheck.data : { error: outputCheck.reason })
firewall.setCors(request, response)
return response
}Vercel API Route
See examples/vercel-api/chat.js.
Generic Node
See examples/generic-node/server.js.
Security Features
Input Protection
Blocks common attempts to:
- ignore previous instructions
- reveal system prompts
- show hidden instructions
- leak API keys or secrets
- access environment variables
- jailbreak or enable developer mode
- encode hidden instructions
- spam repeated payloads
Output Protection
Inspects model responses for:
- system prompt leakage
- developer message leakage
- hidden instruction leakage
- common secret environment variable names
- OpenAI-style secret key strings
Cost Protection
Controls:
- maximum input size
- per-IP burst rate
- per-IP daily request count
- per-IP daily token estimate
- repeated identical payloads
Operational Controls
Supports:
- kill switch
- owner bypass
- CORS helper
- environment-based configuration
- optional persistent Redis limits
Supported Frameworks
- Express
- Next.js App Router
- Vercel API Routes
- Node.js HTTP servers
- Generic backend APIs
- Serverless functions that support JavaScript ESM
Supported LLM Providers
llm-firewall-js is provider-agnostic. It can be used before any server-side provider call, including:
- OpenAI
- Anthropic
- Google Gemini
- Groq
- Mistral
- Cohere
- Azure OpenAI
- local/self-hosted models
- any custom LLM API
Limitations
This package is a practical first layer of defense. It does not make AI apps unhackable.
Use it alongside:
- provider billing alerts
- provider spend limits
- authentication for private products
- product-level user quotas
- request logging and monitoring
- abuse detection
- secret scanning
- regular key rotation
- human review for high-risk workflows
Roadmap
- TypeScript declarations.
- First-class middleware helpers.
- Structured logging hooks.
- Custom storage adapter interface.
- Per-user limits for authenticated apps.
- Optional rule packs for stricter enterprise use cases.
- Test suite with fixture-based prompt attack cases.
Created By
Created by Darshan Sawant
Disclaimer
This project is provided for educational and experimental use. Review, test, and adapt it before using it in production. It reduces common LLM API risks, but it does not guarantee complete protection against abuse, prompt injection, data leakage, or unexpected billing.