shadow-vault
A bi-directional, format-preserving reversible PII proxy for AI agents, LangChain/LangGraph, and Model Context Protocol (MCP) tools.
Stop breaking your AI Agent tools with [REDACTED]
Most PII masking libraries were designed for static document redaction, not autonomous AI agents. Replacing sensitive data with [REDACTED] or <EMAIL_1> completely breaks tool-calling workflows:
- Schema & Validator Rejection: When your AI agent attempts to invoke a Stripe API, PostgreSQL tool, or CRM integration, strict downstream validators (Pydantic, regex schemas, database constraints) immediately crash when handed
[REDACTED_CARD]instead of a 16-digit numeric card string. - One-Way Irreversibility: Once data is destroyed by one-way redaction, local tools cannot execute real actions. You cannot process a refund, send an email, or dispatch an SMS with
[EMAIL]. Bi-directional pseudonymization securely restores real PII at the tool boundary and re-masks outputs before returning to the model. - SSE Streaming Token Tearing: Naive regex replacements leak torn fragments when modern LLMs stream tokens across Server-Sent Events (SSE) chunks (e.g. streaming
"u_8f", then"9a1@shadow", then".vault").shadow-vaultprovides a lookahead holdback buffer that reassembles split tokens seamlessly without token tearing.
Architecture Flow
+---------------------------------------+
| END USER / CLIENT |
+---------------------------------------+
| ^
1. Raw User PII | | 8. Hydrated Real PII
(e.g. Card) v | (StreamHydrator SSE)
+---------------------------------------+
| SHADOW-VAULT PROXY |
+---------------------------------------+
| ^
2. Dehydrated | | 7. Dehydrated Tool
Safe Prompt v | Return Value
+------------------------------------+ +------------------------------------+
| CLOUD LLM / AGENT | | LOCAL MCP TOOL |
| (OpenAI / Anthropic / LangChain) | | (Stripe / SQL / API) |
+------------------------------------+ +------------------------------------+
| ^
| 3. Model Tool Call with | 4. @vault_guard
| Synthetic Tokens | Reversible Hydration
+-------------------------------------------------+
Quickstart (12 Lines)
Install the core library (zero heavy NLP dependencies):
pip install shadow-vault
Protect any Python function or agent tool in seconds:
from shadow_vault import ShadowVault, vault_guard
vault = ShadowVault()
# 1. Protect your internal tool: automatically receives real PII, returns masked PII
@vault_guard(vault=vault, session_id_arg="session_id")
def charge_customer(email: str, card: str, session_id: str = "default") -> dict:
# Function executes with REAL PII: email='alice@example.com', card='4532 0151 1283 0366'
return {"status": "PAID", "receipt": email, "backup_phone": "+91 9876543210"}
# 2. Dehydrate safe prompt for the LLM:
prompt = vault.dehydrate("Charge alice@example.com on card 4532 0151 1283 0366", session_id="s1")
# Prompt: "Charge u_c09753@shadow.vault on card 4000009995970590"
# 3. Model executes tool using synthetic tokens -> returns sanitized output back to agent state:
result = charge_customer(email="u_c09753@shadow.vault", card="4000009995970590", session_id="s1")
# Result: {'status': 'PAID', 'receipt': 'u_c09753@shadow.vault', 'backup_phone': '+919000597059'}
Interactive Zero-Key Terminal Demo
Run the self-contained 4-stage interactive walkthrough (requires NO API keys):
python examples/demo_agent_roundtrip.py
Supported Entities (Global + India)
shadow-vault uses zero-dependency regexes coupled with strict mathematical checksum algorithms so random numbers are never falsely flagged:
| Entity Type | Mathematical Checksum / Algorithm | Real Input Example | Format-Preserving Synthetic Output |
|---|---|---|---|
CREDIT_CARD |
Luhn Checksum (mod 10) (13–19 digits) | 4532 0151 1283 0366 |
4000009995970590 (passes Luhn!) |
AADHAAR |
Verhoeff Dihedral $D_5$ Algorithm | 3675 9834 6016 |
999905970594 (passes Verhoeff!) |
PAN_CARD |
Indian Income Tax Format ([A-Z]{5}[0-9]{4}[A-Z]{1}) |
ABCDE1234F |
SHDWV7059Z |
UPI_ID |
Evaluated before email (@okaxis, @okhdfcbank, @paytm, etc.) |
dhiraj@okaxis |
u_a81c25@okshadow |
EMAIL |
RFC-5322 (ignores @shadow.vault & UPI handles) |
alice@example.com |
u_c09753@shadow.vault |
PHONE |
E.164, Indian mobile (+91 [6-9]\d{9}), US/NANP |
+91 9876543210 |
+919000597059 (valid 10-digit mobile) |
SSN |
US Social Security Number (Area/Group/Serial checks) | 219-45-7890 |
900-99-7059 (unissued area code) |
API_KEY |
OpenAI sk-..., GitHub ghp_..., Google AIza..., Bearer |
sk-abcdef1234567890abcdef... |
sk-sv-shadow-c0975300000000000000 |
Why shadow-vault?
| Capability | shadow-vault |
Microsoft Presidio | LangChain PIIMiddleware |
|---|---|---|---|
| Format-Preserving Tokenization | Yes (Pydantic & regex valid) | No (replaces with <EMAIL>) |
No (replaces with [REDACTED]) |
| Reversible Tool Calls (MCP & Python) | Yes (auto-hydrates at boundary) | Manual (requires custom glue) | No (one-way irreversible) |
| SSE Stream Safe (Zero Token Tearing) | Yes (lookahead holdback buffer) | No (chunks break tokens) | No |
| Zero Heavy NLP Dependencies | Yes (~0 MB, pure Python + SQLite) | No (requires spaCy / PyTorch ~500MB) | Yes |
| Mathematical Checksum Validation | Yes (Luhn & Verhoeff algorithms) | Partial | No |
| Thread-Safe Session SQLite Storage | Yes (built-in with TTL expiration) | Requires External DB | None |
Framework Integrations
1. LangChain & LangGraph
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from shadow_vault import ShadowVault, protect_tool, ShadowVaultChatWrapper
vault = ShadowVault()
session_id = "agent_session"
# Protect Tools: Inputs hydrated before execution, outputs dehydrated before returning
@tool
def lookup_db(card_number: str) -> dict:
"""Fetch record from database."""
return {"card": card_number, "balance": 1500}
safe_tool = protect_tool(lookup_db, vault=vault, session_id=session_id)
# Protect ChatModel: Prompts dehydrated before cloud LLM, tool-call arguments kept synthetic
llm = ChatOpenAI(model="gpt-4o")
safe_llm = ShadowVaultChatWrapper(llm=llm, vault=vault, session_id=session_id)
2. Model Context Protocol (MCP)
Intercept MCP tools/call JSON-RPC requests and responses:
from shadow_vault import ShadowVault, MCPVaultInterceptor
vault = ShadowVault()
interceptor = MCPVaultInterceptor(vault=vault)
# 1. Intercept incoming request from LLM:
safe_request = interceptor.intercept_request(mcp_request, session_id="user_session")
# 2. Intercept outgoing tool response before it reaches LLM:
safe_response = interceptor.intercept_response(mcp_response, session_id="user_session")
3. OpenAI-Compatible FastAPI Proxy Server
Deploy a drop-in proxy for any OpenAI SDK client:
import uvicorn
from shadow_vault import ShadowVault, create_proxy_app
vault = ShadowVault()
app = create_proxy_app(vault=vault, upstream_base_url="https://api.openai.com/v1")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Point your client to http://localhost:8000/v1:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="your-api-key",
default_headers={"X-Session-ID": "session_42"}
)
# Streaming SSE response with real-time token re-hydration:
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Send bill to alice@example.com"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
Benchmark & Performance Profile (1,000 Iterations)
Measured on a realistic 2,000-token prompt containing 12 distinct PII entities, structured MCP tool-call dictionaries, and 500-chunk SSE streams using benchmarks/run_benchmarks.py:
Latency Profile
| Workload / Operation | Input Payload Size | P50 Latency | P95 Latency | P99 Latency | Target Met |
|---|---|---|---|---|---|
| Dehydrate Prompt (12 Entities) | ~2,000 tokens (4.6 KB) | 2.876 ms | 4.043 ms | 4.531 ms | Sub-5ms |
| Hydrate MCP Tool Arguments | 5 Synthetic PII Args | 26.6 μs (0.0266 ms) | 45.9 μs | 73.4 μs | < 0.1 ms |
| StreamHydrator (Per Chunk) | 500 Token Chunks | 50.0 μs (0.0500 ms) | 63.0 μs | 72.3 μs | < 0.1 ms |
| Full 500-Chunk Stream Total | 500 SSE Token Chunks | 25.00 ms | 31.49 ms | 36.16 ms | Real-Time |
Memory Overhead (tracemalloc)
| Metric | Measured Footprint | Comparison vs. Heavy NLP (spaCy / Presidio) |
|---|---|---|
| Memory per Active Session | 0.028 KB (28 bytes) | ~12,500x lighter (~500 MB model load vs. 0.04 KB) |
| Peak Heap Overhead (500 Active Sessions) | 28.56 KB | Zero GPU / Torch dependencies required |
| Storage Engine | SQLite In-Memory / WAL | Built-in TTL expiration prunes inactive sessions |
Checksum Accuracy & False-Positive Rejection
| Test Scenario | Sample Size | False-Positive Rejection Rate | Theoretical Guarantee |
|---|---|---|---|
| Random 16-Digit IDs (Order & Tracking Numbers) | 10,000 | 90.03% | Modulo 10 Luhn algorithm rejects non-card numbers |
| Random 12-Digit IDs (Transaction References) | 10,000 | 89.95% | Verhoeff dihedral group $D_5$ check rejects non-IDs |
| Single-Digit Substitution & Transposition Errors | Exhaustive | 100.0% | 100% mathematical error detection guarantee |
To re-run the benchmark suite locally:
python benchmarks/run_benchmarks.py
Running Tests
pytest -v
# 54 passed in 1.37s (100% pass rate)
Contributing
Contributions are welcome! Read CONTRIBUTING.md to see how you can add a new regional PII detector in under 20 lines of code.
License
Distributed under the MIT License. See LICENSE for details.
Metadata
Release files for shadow-vault 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| shadow_vault-0.3.0.tar.gz | 51.5 kB | With provenance | Details for shadow_vault-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| shadow_vault-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for shadow_vault-0.3.0-py3-none-any.whl |
Total release size: 80.4 kB
Release files / shadow_vault-0.3.0.tar.gz
| Download URL | shadow_vault-0.3.0.tar.gz |
|---|---|
| Size | 51.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f38c0d7e2808f088b1976fc3aacecd9294dcb1c67b6eba116a348ee2231cdb2
|
|
BLAKE2b-256 checksum How to use checksums |
0f5453737ffcd3a990b46a0d5b54d52936abe10ae3a446b300b930ee62b8158c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logRelease files / shadow_vault-0.3.0-py3-none-any.whl
| Download URL | shadow_vault-0.3.0-py3-none-any.whl |
|---|---|
| Size | 28.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
11886cf9ee7ec976cce5c8d1b55223c48959e15d3cc7e1eafc7540792fe4133e
|
|
BLAKE2b-256 checksum How to use checksums |
f984cd9032e6cbb5ae6c105e75b90561b1b09f7bc6f629dac7f2fdbd17a2d970
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logSecurity for shadow-vault 0.3.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for shadow_vault-0.3.0.tar.gz
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 Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.