Skip to content

AI data loss prevention for Python + Flask

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.

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):

Terminal window
# Export your Arcjet API key from https://console.arcjet.com
export ARCJET_KEY="ajkey_..."
export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)
export OPENAI_API_KEY="sk-..."
# Install dependencies
uv add arcjet flask langchain langchain-openai

Create the chat server:

app.py
import logging
import os
from arcjet import (
Mode,
SensitiveInfoEntityType,
arcjet_sync,
detect_sensitive_info,
)
from flask import Flask, jsonify, request
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from 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://console.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 requests
aj = arcjet_sync(
key=arcjet_key, # Get your key from https://console.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:

Terminal window
uv run python app.py

And send a message to the API endpoint:

Terminal window
curl -X POST http://localhost:5000/chat \
-H "Content-Type: application/json" \
-d '{"message": "My email is test@example.com"}'

Requests appear in your Arcjet dashboard in real time.

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). 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.

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 – for a support bot that legitimately collects phone numbers, leave PHONE_NUMBER out of deny:

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.

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 licenses, bank and routing numbers).

Install the optional extra and pass rampart() as the rule’s backend:

Terminal window
pip install "arcjet[sensitive-info-rampart]"
import os
from arcjet import Mode, SensitiveInfoEntityType, arcjet_sync, detect_sensitive_info
from arcjet_sensitive_info_rampart import rampart
aj = arcjet_sync(
key=os.environ["ARCJET_KEY"],
rules=[
detect_sensitive_info(
mode=Mode.LIVE,
# Names and addresses are common in pasted résumés and messages.
deny=[
SensitiveInfoEntityType.CREDIT_CARD_NUMBER,
SensitiveInfoEntityType.EMAIL,
SensitiveInfoEntityType.GIVEN_NAME,
SensitiveInfoEntityType.SURNAME,
SensitiveInfoEntityType.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. It recalls ~98% of private terms across seven Latin-script languages. It loads a native ONNX runtime (ONNX Runtime), which the extra installs for you. For the options and the full accuracy and latency breakdown, see the Rampart reference.

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.

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.

Sensitive info detection controls what data reaches your AI provider. Pair it with the other AI protection layers for full coverage: