Users paste sensitive data into AI prompts — card numbers, phone numbers, home addresses, and whole résumés — often without realizing the risk. Once that data reaches your AI provider it can end up in logs, training pipelines, or model outputs, well outside your control.
Arcjet sensitive info detection scans prompt content inside your application, before it reaches the AI provider. Detection runs locally in your own environment, so the raw text never leaves your app: only the decision — whether sensitive data was found — is reported to Arcjet. When something is detected you choose what happens next: block the request, strip the data, or warn the user.
Get started
Section titled “Get started”In this example we use the Vercel AI SDK to create a simple AI chat endpoint with Next.js, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
We assume you already have a Next.js app set up.
Install the dependencies:
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."
npm install @arcjet/next ai @ai-sdk/openaiCreate an AI chat endpoint:
import { openai } from "@ai-sdk/openai";import arcjet, { sensitiveInfo } from "@arcjet/next";import type { UIMessage } from "ai";import { convertToModelMessages, isTextUIPart, streamText } from "ai";
const aj = arcjet({ key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com rules: [ sensitiveInfo({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only // Block PII types that should never appear in AI prompts. // Remove types your app legitimately handles (e.g. EMAIL for a support bot). deny: ["CREDIT_CARD_NUMBER", "EMAIL"], }), ],});
export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json();
// Check the most recent user message for sensitive information. // Pass the full conversation if you want to scan all messages. const lastMessage: string = (messages.at(-1)?.parts ?? []) .filter(isTextUIPart) .map((p) => p.text) .join(" ");
const decision = await aj.protect(req, { sensitiveInfoValue: lastMessage });
if (decision.isDenied() && decision.reason.isSensitiveInfo()) { console.warn("Request blocked due to sensitive information"); return new Response( "Sensitive information detected — please remove it from your prompt", { status: 400 }, ); }
// Arcjet approved — call your AI provider const result = await streamText({ model: openai("gpt-4o"), messages: await convertToModelMessages(messages), });
return result.toUIMessageStreamResponse();}And hook it up to a chat UI:
"use client";
import { useChat } from "@ai-sdk/react";import { useState } from "react";
export default function Chat() { const [input, setInput] = useState(""); const [errorMessage, setErrorMessage] = useState<string | null>(null); const { messages, sendMessage } = useChat({ onError: async (e) => setErrorMessage(e.message), }); return ( <div className="flex flex-col w-full max-w-md py-24 mx-auto stretch"> {messages.map((message) => ( <div key={message.id} className="whitespace-pre-wrap"> {message.role === "user" ? "User: " : "AI: "} {message.parts.map((part, i) => { switch (part.type) { case "text": return <div key={`${message.id}-${i}`}>{part.text}</div>; } })} </div> ))}
{errorMessage && ( <div className="text-red-500 text-sm mb-4">{errorMessage}</div> )}
<form onSubmit={(e) => { e.preventDefault(); sendMessage({ text: input }); setInput(""); setErrorMessage(null); }} > <input className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl" value={input} placeholder="Say something..." onChange={(e) => setInput(e.currentTarget.value)} /> </form> </div> );}Then run the server:
npm run devYou will see requests being processed in your Arcjet dashboard in real time.
In this example we use LangChain to create a simple AI chat server with FastAPI, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
Set up the environment and install dependencies (uses uv, but you can also use pip to install the Arcjet Python SDK):
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)export OPENAI_API_KEY="sk-..."
# Install dependenciesuv add arcjet fastapi uvicorn langchain langchain-openaiCreate the chat server:
import loggingimport os
from arcjet import Mode, SensitiveInfoEntityType, arcjet, detect_sensitive_infofrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponsefrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAIfrom pydantic import BaseModel
app = FastAPI()
logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
arcjet_key = os.getenv("ARCJET_KEY")if not arcjet_key: raise RuntimeError("ARCJET_KEY is required. Get one at https://app.arcjet.com")
openai_api_key = os.getenv("OPENAI_API_KEY")if not openai_api_key: raise RuntimeError( "OPENAI_API_KEY is required. Get one at https://platform.openai.com" )
llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
prompt = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful assistant."), ("human", "{message}"), ])
chain = prompt | llm | StrOutputParser()
class ChatRequest(BaseModel): message: str
# Create a single Arcjet client at startup and reuse it across requestsaj = arcjet( key=arcjet_key, # Get your key from https://app.arcjet.com rules=[ detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only # Block PII types that should never appear in AI prompts. # Remove types your app legitimately handles (e.g. EMAIL for a # support bot). deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ], ), ],)
@app.post("/chat")async def chat(request: Request, body: ChatRequest): # Scan the user message for sensitive information before it reaches the # AI model. Pass the full conversation if you want to scan all messages. decision = await aj.protect(request, sensitive_info_value=body.message)
if decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO": logger.warning("Request blocked due to sensitive information") return JSONResponse( {"error": "Sensitive information detected — please remove it from your prompt"}, status_code=400, )
# Arcjet approved — call the AI model reply = await chain.ainvoke({"message": body.message})
return {"reply": reply}Then run the server:
uv run uvicorn main:app --reloadAnd send a message to the API endpoint:
curl -X POST http://localhost:8000/chat \ -H "Content-Type: application/json" \ -d '{"message": "My email is test@example.com"}'You will see requests being processed in your Arcjet dashboard in real time.
In this example we use LangChain to create a simple AI chat server with Flask, and Arcjet to prevent sensitive information from being sent to the AI model. The same principles can be applied to any AI application, including those built with other frameworks.
Set up the environment and install dependencies (uses uv, but you can also use pip to install the Arcjet Python SDK):
# Export your Arcjet API key from https://app.arcjet.comexport ARCJET_KEY="ajkey_..."export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)export OPENAI_API_KEY="sk-..."
# Install dependenciesuv add arcjet flask langchain langchain-openaiCreate the chat server:
import loggingimport os
from arcjet import ( Mode, SensitiveInfoEntityType, arcjet_sync, detect_sensitive_info,)from flask import Flask, jsonify, requestfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_openai import ChatOpenAI
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)
arcjet_key = os.getenv("ARCJET_KEY")if not arcjet_key: raise RuntimeError("ARCJET_KEY is required. Get one at https://app.arcjet.com")
openai_api_key = os.getenv("OPENAI_API_KEY")if not openai_api_key: raise RuntimeError( "OPENAI_API_KEY is required. Get one at https://platform.openai.com" )
llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
prompt = ChatPromptTemplate.from_messages( [ ("system", "You are a helpful assistant."), ("human", "{message}"), ])
chain = prompt | llm | StrOutputParser()
# Create a single Arcjet client at startup and reuse it across requestsaj = arcjet_sync( key=arcjet_key, # Get your key from https://app.arcjet.com rules=[ detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only # Block PII types that should never appear in AI prompts. # Remove types your app legitimately handles (e.g. EMAIL for a # support bot). deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ], ), ],)
@app.post("/chat")def chat(): body = request.get_json() message = body.get("message", "") if body else ""
# Scan the user message for sensitive information before it reaches the # AI model. Pass the full conversation if you want to scan all messages. decision = aj.protect(request, sensitive_info_value=message)
if decision.is_denied() and decision.reason_v2.type == "SENSITIVE_INFO": logger.warning("Request blocked due to sensitive information") return jsonify( error="Sensitive information detected — please remove it from your prompt" ), 400
# Arcjet approved — call the AI model reply = chain.invoke({"message": message})
return jsonify(reply=reply)
if __name__ == "__main__": app.run(debug=True)Then run the server:
uv run python app.pyAnd send a message to the API endpoint:
curl -X POST http://localhost:5000/chat \ -H "Content-Type: application/json" \ -d '{"message": "My email is test@example.com"}'You will see requests being processed in your Arcjet dashboard in real time.
How detection works
Section titled “How detection works”Sensitive info detection runs through a detection backend — the engine that scans the text and identifies entities. There are two:
- Built-in engine (default). A WebAssembly engine bundled with the SDK. It detects four structured types — card numbers, email addresses, phone numbers, and IP addresses — runs anywhere the SDK runs (including edge runtimes), and needs no extra dependencies.
- Rampart backend (optional, JavaScript SDKs). An on-device named-entity-recognition (NER) model that adds the free-form PII people actually paste into prompts — names, street addresses, and government or financial identifiers. This is often the more valuable engine for AI data loss prevention, because that is exactly the data a structured-pattern matcher can’t catch.
Both engines run entirely on your own infrastructure. Nothing is sent to a third party for analysis, which is what makes this safe to put in front of an AI provider in the first place.
Configuring detection
Section titled “Configuring detection”Choose which PII to block
Section titled “Choose which PII to block”Use deny to list the entity types to block, or allow to block everything
except the types you list (the two are mutually exclusive). Tune the list to
your app — a support bot that legitimately collects phone numbers should leave
PHONE_NUMBER out of deny:
sensitiveInfo({ mode: "LIVE", // Blocks requests. Use "DRY_RUN" to log only deny: ["CREDIT_CARD_NUMBER", "EMAIL"],});detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ],)detect_sensitive_info( mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only deny=[ SensitiveInfoEntityType.CREDIT_CARD_NUMBER, SensitiveInfoEntityType.EMAIL, ],)The built-in engine detects CREDIT_CARD_NUMBER, PHONE_NUMBER, EMAIL, and
IP_ADDRESS. See the
entity detection
table for every type each backend supports, and for defining your
own custom detectors.
Detect names, addresses, and IDs
Section titled “Detect names, addresses, and IDs”The built-in types cover structured data, but AI prompts are full of free-form PII — a pasted résumé, a shipping address, someone’s full name. The optional Rampart backend runs an on-device NER model that detects these, plus government and financial identifiers (SSNs, tax IDs, passports, driver’s licences, bank and routing numbers).
Install the package and pass rampart() as the rule’s backend:
npm install @arcjet/sensitive-info-rampartimport arcjet, { sensitiveInfo } from "@arcjet/next";import { rampart } from "@arcjet/sensitive-info-rampart";
// The Rampart backend loads a native ONNX runtime, so this route must run on// the Node.js runtime, not the Edge runtime.export const runtime = "nodejs";
const aj = arcjet({ key: process.env.ARCJET_KEY!, rules: [ sensitiveInfo({ mode: "LIVE", // Names and addresses are common in pasted résumés and personal messages. deny: ["CREDIT_CARD_NUMBER", "EMAIL", "GIVEN_NAME", "SURNAME", "STREET_NAME"], backend: rampart(), }), ],});The model is bundled with the package (~14.7 MB, quantized) so nothing is fetched at runtime, and inference is fast enough to run inline on each request (~6.6 ms median on Node.js). It recalls ~98% of private terms across seven Latin-script languages. Because it loads a native ONNX runtime it needs a server runtime — Node.js, Bun, or Deno, not edge — and on Next.js you must mark it as a server external package. See the Rampart reference for the bundler configuration, options, and the full accuracy and latency breakdown.
Choose what text to scan
Section titled “Choose what text to scan”Pass the text to scan as sensitiveInfoValue (JS) / sensitive_info_value
(Python). For a chat endpoint this is usually the user’s most recent message.
Pass the full conversation history instead if you want to scan every message,
not just the latest one — useful when PII may have been introduced earlier in
the exchange.
Test before you block
Section titled “Test before you block”Set mode to "DRY_RUN" (JS) / Mode.DRY_RUN (Python) to log detections
without blocking any requests. Run this in production for a while to audit what
PII actually shows up in your prompts, then switch to "LIVE" once you’re
confident in the entity list.
Combine with other protections
Section titled “Combine with other protections”Sensitive info detection controls what data reaches your AI provider. Pair it with the other AI protection layers for full coverage:
- Prompt injection detection blocks hostile instructions and jailbreak attempts.
- AI abuse protection and AI budget control block automated clients and enforce per-user token budgets.
- Arcjet Guards applies the same PII detection — including the on-device Rampart backend — directly inside agent tool handlers and pipelines that don’t route through HTTP.