Skip to main content

prompt sanitizer

PII and secret sanitization for Python LLM pipelines.

prompt sanitizer provides a typed API for detecting, redacting, anonymizing, and restoring sensitive values before they reach a model, tool, middleware layer, log sink, or SDK wrapper. FAST mode has zero required dependencies. SMART and FULL add optional NLP, synthetic replacement, and audit logging.

Install

Python 3.10+.

pip install ai-prompt-sanitizer
pip install "ai-prompt-sanitizer[nlp]"
pip install "ai-prompt-sanitizer[synthetic]"
pip install "ai-prompt-sanitizer[integrations]"
pip install "ai-prompt-sanitizer[all]"

Optional extras

Extra Adds Typical use
nlp transformers + torch NER in SMART/FULL mode
synthetic faker realistic fake replacements
integrations framework / SDK adapters LangChain, LlamaIndex, OpenAI, FastAPI, Django
all all extras full feature set

Quick start

from prompt_sanitizer import Sanitizer, Mode

s = Sanitizer(mode=Mode.FAST)
result = s.sanitize("Contact Jane Doe at jane@example.com or 415-555-0112.")

print(result.text)
print(result.has_pii)
print(result.risk_score)
print(result.tokens)
for entity in result.entities:
    print(entity.entity_type, entity.value, entity.replacement)

Modes

Mode Pipeline Dependencies Notes
Mode.FAST regex + secret detectors none sub-ms, stdlib only
Mode.SMART FAST + Piiranha NER prompt-sanitizer[nlp] lazy-loads on first call
Mode.FULL SMART + synthetic replacement + audit log usually nlp + synthetic best for compliance-oriented flows

FAST mode

from prompt_sanitizer import Sanitizer, Mode

s = Sanitizer(mode=Mode.FAST)
text = "SSN 078-05-1120, card 4111 1111 1111 1111, token sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx"
result = s.sanitize(text)

print(result.text)
print(result.entities)
print(result.tokens)

Use FAST for prompt pre-processing, log scrubbing, middleware guards, CI checks, and zero-dependency CLI tooling.

SMART mode

from prompt_sanitizer import Sanitizer, Mode

s = Sanitizer(mode=Mode.SMART)
result = s.sanitize(
    "Alice from Acme Corp met us in Berlin on 2025-02-14. Email alice@acme.example."
)

print(result.text)
for entity in result.entities:
    print(entity.entity_type, entity.value, entity.confidence)

Use SMART when prompts contain free-form prose with names, organizations, dates, or locations that regexes alone may miss.

FULL mode

from prompt_sanitizer import Sanitizer, Mode, SQLiteAuditLog

audit = SQLiteAuditLog("prompt_sanitizer_audit.db")
s = Sanitizer(mode=Mode.FULL, locale="en_US", on_detect="redact", audit_log=audit)

result = s.sanitize("Customer Jane Doe uses jane@example.com and 415-555-0112.")
print(result.text)
print(result.tokens)
print(s.audit.export(format="json"))

Use FULL when you want synthetic replacement plus an audit trail.

Public API

Sanitizer

Sanitizer(
    mode: Mode = Mode.FAST,
    locale: str = "en_US",
    entities: list[EntityType] | None = None,
    on_detect: str = "redact",
    audit_log: BaseAuditLog | None = None,
)
Parameter Type Description
mode Mode detection pipeline
locale str locale for synthetic replacement generation
entities list[EntityType] | None optional allowlist of entity types
on_detect str "redact", "warn", or "block"
audit_log BaseAuditLog | None optional audit backend

| Method | Signature | Description | | ---------------- | ----------------------------------------------------------------------- | ------------------------------------------- | --------------------------------------- | | sanitize | sanitize(text: str, session_id: str | None = None) -> SanitizeResult | sanitize one string | | sanitize_batch | sanitize_batch(texts: list[str]) -> list[SanitizeResult] | sanitize multiple inputs | | session | session(session_id: str | None = None) -> Session | create a reusable anonymization session | | add_entity | add_entity(name: str, pattern: str, confidence: float = 0.85) -> None | register a custom entity | | stream | stream(source: AsyncIterable, session: Session | None) -> AsyncGenerator[str, None] | restore streamed chunks | | guard | guard(on_detect: str) -> decorator | decorate a function with sanitization logic | | audit | .audit -> BaseAuditLog | None | access the configured audit log |

Detection policy

on_detect value Behavior
"redact" rewrite the returned text
"warn" return original text, but populate entities and scores
"block" raise instead of returning sanitized text
results = s.sanitize_batch(["Email a@example.com", "No sensitive data here"])

@s.guard(on_detect="redact")
def call_model(prompt: str) -> str:
    return prompt

Mode, SanitizeResult, and DetectedEntity

Mode value Meaning
Mode.FAST regex + secrets, zero deps, sub-ms
Mode.SMART FAST + Piiranha NER, lazy loads on first call
Mode.FULL SMART + synthetic replacement + audit log
SanitizeResult attribute Type Description
text str sanitized text
entities list[DetectedEntity] detected spans
tokens dict[str, str] {original_value: replacement} map
risk_score float composite score from 0.0 to 1.0
has_pii bool whether sensitive data was found
DetectedEntity attribute Type Description
entity_type EntityType entity classification
value str original matched value
start int inclusive start offset
end int exclusive end offset
confidence float detection confidence
replacement str | None replacement value, if generated
result = s.sanitize("Contact me at sam@example.com")
assert result.has_pii is True
assert 0.0 <= result.risk_score <= 1.0
for entity in result.entities:
    print(entity.entity_type, entity.value, entity.replacement)

Sessions and vaults

Use sessions when the model should never see raw values, but the final response should restore them.

from prompt_sanitizer import Sanitizer

s = Sanitizer()
session = s.session(session_id="support-chat-001")
clean_prompt = session.anonymize("My name is Elena Ruiz and my email is elena@company.com")
llm_reply = "Confirmed. I will email [EMAIL_1] shortly."
final_reply = session.deanonymize(llm_reply)

print(clean_prompt)
print(final_reply)
Session API Description
session.anonymize(text: str) -> str replace PII with vault tokens
session.deanonymize(text: str) -> str restore originals from the vault
session.vault: Vault access the underlying vault
Vault API Description
vault.store(value: str, replacement: str) -> None store a mapping
vault.lookup(replacement: str) -> str | None resolve token to original
vault.reverse(value: str) -> str | None resolve original to replacement
vault.clear() -> None clear all mappings
vault = session.vault
vault.store("alice@example.com", "[EMAIL_1]")
print(vault.lookup("[EMAIL_1]"))
print(vault.reverse("alice@example.com"))
vault.clear()

Persisting sessions across restarts

By default a session's vault lives only in process memory. Pass store= to reattach to the same mapping later by session_id — e.g. after a worker restart:

from prompt_sanitizer import SQLiteVaultStore

store = SQLiteVaultStore("./vault.db")
session = s.session(session_id="support-chat-001", store=store)
clean_prompt = session.anonymize("My name is Elena Ruiz and my email is elena@company.com")
session.persist()

# ...later, possibly in a new process:
resumed = s.session(session_id="support-chat-001", store=store)
final_reply = resumed.deanonymize(llm_reply)

MemoryVaultStore is the zero-dependency, same-process reference store; SQLiteVaultStore persists to a local SQLite file (stdlib only). Pass auto_persist=True to persist automatically after every anonymize() call instead of calling persist() yourself. No store is active unless you pass one.

Custom entities

Use add_entity() for internal identifiers, tenant-specific secrets, or domain-specific formats.

from prompt_sanitizer import Sanitizer

s = Sanitizer()
s.add_entity(name="customer_id", pattern=r"\bCUS-\d{8}\b", confidence=0.90)
s.add_entity(name="invoice_no", pattern=r"\bINV-\d{6}-[A-Z]{2}\b", confidence=0.88)

result = s.sanitize("Customer CUS-12345678 opened invoice INV-882211-US")
print(result.text)
print(result.entities)

Filtering by entity type

from prompt_sanitizer import Sanitizer, EntityType

s = Sanitizer(entities=[EntityType.EMAIL, EntityType.API_KEY])
result = s.sanitize("Email a@b.com and SSN 123-45-6789")
print(result.text)

Audit logging

Audit backends are optional. Use them when you want structured records of detections.

MemoryAuditLog

from prompt_sanitizer import MemoryAuditLog, Mode, Sanitizer

audit = MemoryAuditLog()
s = Sanitizer(mode=Mode.FULL, audit_log=audit)
s.sanitize("Email finance@example.com")

print(audit.events())
print(audit.export(format="json"))

SQLiteAuditLog

from prompt_sanitizer import SQLiteAuditLog, Mode, Sanitizer

audit = SQLiteAuditLog("audit.db")
s = Sanitizer(mode=Mode.FULL, audit_log=audit)
s.sanitize("Call +1 415 555 0112", session_id="request-17")

print(audit.events())
print(audit.export(format="csv"))

Audit API

API Description
MemoryAuditLog() in-memory list of AuditEvent
SQLiteAuditLog(path: str) SQLite-backed persisted log
.events() -> list[AuditEvent] return recorded events
.export(format: "json" | "csv") -> str export audit records

Integrations

Install integration dependencies first:

pip install "ai-prompt-sanitizer[integrations]"

LangChain

from prompt_sanitizer import Sanitizer
from prompt_sanitizer.integrations.langchain import PromptSanitizerRunnable, SanitizedLLM

s = Sanitizer()
# As a runnable step in a chain
chain = PromptSanitizerRunnable(sanitizer=s) | llm | OutputParser()
result = chain.invoke("My email is dev@example.com")

# Or wrap the LLM directly
safe_llm = SanitizedLLM(llm, s)
reply = safe_llm.invoke("Contact alice@example.com with the summary.")

LlamaIndex

from prompt_sanitizer import Sanitizer
from prompt_sanitizer.integrations.llamaindex import PromptSanitizerPostprocessor

s = Sanitizer()
postprocessor = PromptSanitizerPostprocessor(sanitizer=s)
query_engine = index.as_query_engine(node_postprocessors=[postprocessor])
response = query_engine.query("Summarize the contract for jane@example.com")

OpenAI SDK wrapper

import openai
from prompt_sanitizer import Sanitizer
from prompt_sanitizer.integrations.openai import wrap

s = Sanitizer()
client = wrap(openai.OpenAI(), sanitizer=s)
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "My card is 4111 1111 1111 1111"}],
)

FastAPI middleware

from fastapi import FastAPI
from prompt_sanitizer import Sanitizer
from prompt_sanitizer.integrations.fastapi import SanitizerMiddleware

s = Sanitizer()
app = FastAPI()
app.add_middleware(SanitizerMiddleware, sanitizer=s, fields=["prompt", "message"])

Django middleware

MIDDLEWARE = ["prompt_sanitizer.integrations.django.SanitizerMiddleware"]
from prompt_sanitizer import Sanitizer

PROMPT_SANITIZER = {
    "sanitizer": Sanitizer(),
    "fields": ["prompt", "message"],
}

Entity types

Group Values
core PII EMAIL, PHONE, SSN, CREDIT_CARD, IBAN, IP_ADDRESS, URL, DATE
identity / org PERSON_NAME, ORGANIZATION, LOCATION
secrets API_KEY, JWT_TOKEN, SECRET_KEY, AWS_KEY, GITHUB_TOKEN, OPENAI_KEY, ANTHROPIC_KEY
extension CUSTOM

Operational notes

  • FAST mode is stdlib-only.
  • SMART lazy-loads NER on first use.
  • FULL is the best fit for synthetic replacement plus audit.
  • sanitize() is for one-shot calls.
  • session() is for reversible multi-turn workflows.
  • sanitize_batch() treats each input independently.

License

MIT.

Metadata

Release files for ai-prompt-sanitizer 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ai-prompt-sanitizer 1.1.0
File Size Uploaded
ai_prompt_sanitizer-1.1.0.tar.gz 44.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-prompt-sanitizer 1.1.0
File Interpreter ABI Platform
ai_prompt_sanitizer-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 88.8 kB

Release files / ai_prompt_sanitizer-1.1.0.tar.gz

Download URL ai_prompt_sanitizer-1.1.0.tar.gz
Size 44.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f14bd6eef57bb7f50a620463ee6d9ca312ec257bbdf0e307505ba0d40b481ee2
BLAKE2b-256 checksum
How to use checksums
8a1c21093bf8cf8a921bc3917d6e6f0eb34f4d22b1907662b3fc5b3091c30dc7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Release files / ai_prompt_sanitizer-1.1.0-py3-none-any.whl

Download URL ai_prompt_sanitizer-1.1.0-py3-none-any.whl
Size 44.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
52d82fa28427a00cbd9492c8b0d166827f35cb08c54a5b93ab3ae099b7ffb10d
BLAKE2b-256 checksum
How to use checksums
a1138dfe97ab2b785ae6ed994118e17425c5febc1eb53fc6e35193f485f7b3f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.4

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page