TL;DR — AgentMesh 0.3.2 ships a PII/PHI/PCI scanner that runs on every AI prompt before it reaches the model. 17 entity types. Under 2ms. No external API. No cloud service. Pure Python regex with Luhn validation and overlap deduplication. Three enforcement modes: mask, redact, block.
pip install agentmesh-proxy
The problem
Your AI agents and tools are sending raw sensitive data to the LLM vendor.
Medical record numbers in clinical AI prompts. Credit card numbers in finance team workflows. AWS access keys in developer debug pastes. Social security numbers in HR automation.
The people doing this are not making bad decisions. They are using the tools available to them. The problem is that there is no layer between the prompt and the model that catches sensitive data first.
I built that layer into AgentMesh. Here is how it works.
What it catches
![17 entity types caught before the LLM]
17 entity types across four categories:
PII — personal identity
- SSN:
567-89-0123→[SSN] - Date of birth:
07/22/1985→[DOB] - Email:
sarah.johnson@gmail.com→[EMAIL] - Phone:
(415) 867-5309→[PHONE_US] - Passport:
Passport no: US123456789→[PASSPORT]
PCI — payment card and financial data
- Visa:
4532 1234 5678 9012→[PCI_CARD] - Amex:
3714 496353 98431→[PCI_CARD] - Mastercard:
5500 0055 0000 0004→[PCI_CARD] - CVV:
CVV 394→[PCI_CVV] - Routing:
Routing: 021000021→[PCI_ROUTING] - Account:
Account: 000123456789→[PCI_ACCOUNT]
PHI — HIPAA-protected medical data
- Medical record:
MRN: P-987654→[PHI_MRN] - ICD-10 diagnosis:
E11.9→[PHI_ICD10] - Medication dosage:
10mg lisinopril→[PHI_DOSAGE] - Provider ID:
NPI: 1234567890→[PHI_NPI]
CII — cloud credentials and infrastructure
- AWS key:
AKIAIOSFODNN7EXAMPLE→[CII_AWS_KEY] - JWT token:
eyJhbGci...→[CII_JWT]
Quick start
pip install agentmesh-proxy
from agentmesh.security.pii_scanner import PIIScanner, ScanMode
scanner = PIIScanner(mode=ScanMode.MASK)
result = scanner.scan(
"Patient MRN: P-987654, email: sarah@example.com, "
"card: 4532 1234 5678 9012, key: AKIAIOSFODNN7EXAMPLE"
)
print(result.cleaned)
# Patient MRN: [PHI_MRN], email: [EMAIL],
# card: [PCI_CARD], key: [CII_AWS_KEY]
print(result.finding_types)
# ['CII_AWS_KEY', 'EMAIL', 'PCI_CARD', 'PHI_MRN']
For scanning a list of {role, content} messages (OpenAI format):
messages = [
{"role": "user", "content": "SSN 123-45-6789, card 4532 1234 5678 9012"}
]
cleaned_messages, findings = scanner.scan_messages(messages)
print(cleaned_messages[0]["content"])
# SSN [SSN], card [PCI_CARD]
Three enforcement modes
from agentmesh.security.pii_scanner import PIIScanner, ScanMode, PIIDetectedError
# MASK: replace with labeled placeholder — model still gets a useful prompt
scanner = PIIScanner(mode=ScanMode.MASK)
result = scanner.scan("SSN 123-45-6789")
print(result.cleaned) # "SSN [SSN]"
# REDACT: replace with *** — when even the label is too much context
scanner = PIIScanner(mode=ScanMode.REDACT)
result = scanner.scan("SSN 123-45-6789")
print(result.cleaned) # "SSN ***"
# BLOCK: raise PIIDetectedError — zero tolerance, reject the request
scanner = PIIScanner(mode=ScanMode.BLOCK)
try:
scanner.scan("SSN 123-45-6789")
except PIIDetectedError as e:
print(e.findings) # [Finding(entity_type='SSN', ...)]
# Return HTTP 400 to the caller
Engineering decisions worth explaining
Why regex over an NLP model?
Speed. The scan runs in under 2ms. An NLP-based entity recognizer adds 50ms to 200ms per call and requires a model download. For a proxy that sits in the path of every LLM call, 2ms is acceptable and 200ms is not.
The tradeoff is recall. Regex will miss creative obfuscation. For governance purposes — where the goal is catching accidental leakage, not adversarial attacks — regex is the right tool.
The credit card validation decision
Standard implementations run Luhn validation on card numbers and only mask numbers that pass. We run in strict_pci=True mode by default:
# In PIIScanner.__init__:
# strict_pci=True (default): mask any card-shaped number (13-19 digits)
# even if it fails the Luhn check.
# Rationale: governance proxies should over-mask rather than under-mask.
# A false positive costs one masked token.
# A false negative sends a real card number to the vendor.
self.strict_pci = strict_pci
If you prefer Luhn validation only:
scanner = PIIScanner(mode=ScanMode.MASK, strict_pci=False)
The overlap deduplication problem
This one took a few iterations to get right.
Consider a prompt containing MRN: A1234567. The PHI_MRN pattern matches the whole span. The PASSPORT pattern (before it required a passport: prefix) would also match the A1234567 part.
If you apply replacements in reverse order by start position — which is the standard approach to keep earlier offsets valid — and the inner match gets processed first, it replaces 8 characters with 10 ([PASSPORT]). The outer match then tries to cut at the original end offset, which now points into the middle of [PASSPORT], producing [PHI_MRN]T].
The fix:
def _dedup_overlapping(findings: List[Finding]) -> List[Finding]:
# Sort by start position, then by length descending (outermost first).
# Walk forward and drop any finding whose start is inside the
# previous kept finding's range.
sorted_f = sorted(findings, key=lambda f: (f.start, -(f.end - f.start)))
result: List[Finding] = []
last_end = -1
for f in sorted_f:
if f.start >= last_end:
result.append(f)
last_end = f.end
return result
Keep the outermost match. Drop everything whose start position falls inside it. Apply replacements in reverse order on the deduplicated list. No artifacts.
Wiring it into the proxy
If you are running AgentMesh as a proxy rather than calling the scanner directly, activate it in config:
# agentmesh.yaml
pii_mode: mask # mask | redact | block
block_injections: true # prompt injection detection (14 rules)
anomaly_detection: true # runaway loop + burn rate monitoring
slack_webhook: "" # optional: alert destination
agentmesh serve --config agentmesh.yaml --port 8080
Point your agents at it:
export OPENAI_BASE_URL=http://localhost:8080/v1
export ANTHROPIC_BASE_URL=http://localhost:8080
Every call going through the proxy now gets scanned. The response includes a header showing what was found:
X-AgentMesh-PII-Findings: 4
X-AgentMesh-Cache: miss
X-AgentMesh-Cost-USD: 0.000420
The Chrome extension
A server-side proxy cannot intercept prompts typed directly into the ChatGPT or Claude.ai browser tab. For that there is a Chrome extension — same scanner, running locally in the browser process before the request leaves the tab.
Google approved it last weekend.
Install from the Chrome Web Store (link in the repo readme) or build from source. Works with ChatGPT, Claude.ai, Gemini, Perplexity, and Cursor. No server required for standalone use.
HIPAA in production
If your team uses AI in a clinical setting, the PHI scanner is the piece that matters most. ICD-10 codes are two to five characters but identify specific diagnoses. Combined with a medical record number and a provider NPI, they reconstruct a patient record from a prompt.
AgentMesh also generates HIPAA readiness reports:
from agentmesh.compliance.pdf_report import ComplianceReporter, Framework
reporter = ComplianceReporter()
markdown = reporter.generate_markdown(Framework.HIPAA)
# or
reporter.generate_pdf(Framework.HIPAA, output_path="hipaa_report.pdf")
Outputs a structured report listing which controls are active, which are not, and what gaps remain. Useful for security reviews before a compliance audit.
Try it
pip install agentmesh-proxy
from agentmesh.security.pii_scanner import PIIScanner, ScanMode
scanner = PIIScanner(mode=ScanMode.MASK)
result = scanner.scan("your prompt here")
print(result.cleaned)
print(result.finding_types)
The scanner is in agentmesh/security/pii_scanner.py. About 270 lines. No external dependencies beyond the Python standard library.
Repo: https://github.com/anilatambharii/agentmesh
PyPI: agentmesh-proxy
Docker: docker pull anilsprasad/agentmesh:latest
Apache 2.0.
What entity types would you add? What patterns are you seeing in your team's prompts that are not covered here?
Find me: anilsprasad.com · X @anilsprasad · LinkedIn


Top comments (0)