Your LLM calls are unencrypted confessions.
Every time you call litellm.completion() or openai.chat.completions.create(), the provider receives your prompt in full plaintext. Names, emails, SSNs, API keys, medical records - all of it sitting in someone else's logs.
That's been a privacy risk for years. In 5 months, it becomes illegal.
August 2, 2026
The EU AI Act enters enforcement. Article 12 mandates tamper-evident audit logs for AI systems - not console.log(), not a JSON file you append to. Logs that regulators can mathematically verify haven't been altered.
The penalty: up to 7% of global annual revenue.
If you use LLMs and handle EU data, you need:
- PII never reaches the provider (or you need explicit consent per entity)
- Every AI interaction logged in a verifiable audit trail
Most teams have neither. I built CloakLLM to fix both.
What CloakLLM Does
CloakLLM is open-source middleware that sits between your app and any LLM provider. Python, Node.js, and MCP for Claude Desktop.
It does three things:
Detects PII - three layers deep:
- spaCy NER for names, orgs, locations (Python)
- Regex for emails, SSNs, credit cards, API keys, IBANs, phones, IPs, JWTs (both SDKs)
- Local LLM via Ollama (opt-in) - catches context-dependent PII that regex misses: addresses, medical terms, financial data, national IDs. Your data never leaves your machine.
Cloaks it with context-preserving tokens:
Your app: "Email sarah.j@techcorp.io about Project Falcon"
Provider sees: "Email [EMAIL_0] about Project Falcon"
You receive: Original email restored in the response
The LLM still understands the prompt. It just never sees real data.
Logs everything to a hash-chained audit trail:
{
"seq": 42,
"event_type": "sanitize",
"entity_count": 3,
"prompt_hash": "sha256:9f86d0...",
"prev_hash": "sha256:7c4d2e...",
"entry_hash": "sha256:b5e8f3..."
}
Each entry's SHA-256 hash includes the previous entry's hash. Tamper with one log entry, and every subsequent hash breaks. This is what Article 12 actually requires.
Python - One Line with LiteLLM
Works with 100+ providers (Anthropic, OpenAI, Azure, Bedrock, Ollama, etc.) via LiteLLM:
import cloakllm
cloakllm.enable() # Done.
import litellm
response = litellm.completion(
model="anthropic/claude-sonnet-4-20250514",
messages=[{
"role": "user",
"content": "Help me email sarah.j@techcorp.io about the Q3 audit"
}]
)
# Provider never saw the email. Response has it restored.
Node.js - OpenAI SDK
const cloakllm = require('cloakllm');
const OpenAI = require('openai');
const client = new OpenAI();
cloakllm.enable(client); // Done.
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{
role: 'user',
content: 'Write a reminder for sarah.j@techcorp.io about the Q3 audit'
}]
});
// Provider never saw the email.
Node.js - Vercel AI SDK
const { createCloakLLMMiddleware } = require('cloakllm');
const { generateText, wrapLanguageModel } = require('ai');
const { openai } = require('@ai-sdk/openai');
const middleware = createCloakLLMMiddleware();
const model = wrapLanguageModel({ model: openai('gpt-4o'), middleware });
const { text } = await generateText({
model,
prompt: 'Write a reminder for sarah.j@techcorp.io about the Q3 audit'
});
// Streaming supported - handles tokens split across chunk boundaries
MCP Server - Claude Desktop
CloakLLM also ships as an MCP server with three tools: sanitize, desanitize, and analyze. Add it to your claude_desktop_config.json and every Claude Desktop conversation gets PII cloaking.
Or Use It Standalone
# Python
from cloakllm import Shield
shield = Shield()
cloaked, token_map = shield.sanitize("Send to john@acme.com, SSN 123-45-6789")
# cloaked: "Send to [EMAIL_0], SSN [SSN_0]"
// Node.js
const { Shield } = require('cloakllm');
const shield = new Shield();
const [cloaked, tokenMap] = shield.sanitize("Send to john@acme.com, SSN 123-45-6789");
Works with any LLM client, any provider, any framework.
Local LLM Detection
Regex catches structured PII. But "I live at 742 Evergreen Terrace" or "diagnosed with hypertension" - regex can't catch that.
CloakLLM has an opt-in LLM detection layer that runs through local Ollama:
shield = Shield(config=ShieldConfig(
llm_detection=True,
llm_model="llama3.2:3b",
))
It detects addresses, medical terms, financial data, national IDs, biometrics, usernames, and passwords - all without your data leaving your machine.
The LLM pass runs after regex, so already-detected entities are skipped. No double counting.
Design Decisions
Regex first, NER second, LLM third. Structured data (emails, SSNs, credit cards) is caught by regex with near-zero latency. spaCy NER runs second for names and orgs. The LLM pass is opt-in and catches everything else. Fast path stays fast.
Overlap detection. If regex catches john@acme.com and NER detects acme.com as ORG, the overlap is caught and the duplicate is skipped.
System prompt injection. Without it, LLMs see [PERSON_0] and ask "what's the real name?" CloakLLM injects a system message telling the model to treat tokens as real values. Only when tokens are present.
Token injection protection. If user input contains [PERSON_0], it gets escaped to fullwidth Unicode brackets before tokenization - preventing attackers from injecting fake tokens to extract other users' PII during desanitization.
try/finally cleanup. Even if the LLM API throws, the token map (which contains PII mappings) is always cleaned up. No PII lingers in process memory.
Vercel streaming. The Vercel AI SDK middleware buffers text-delta chunks and desanitizes on text-end, correctly handling tokens that span chunk boundaries like [EM + AIL_0].
Verify Your Audit Chain
$ cloakllm verify ./cloakllm_audit/
✅ Audit chain integrity verified - no tampering detected.
If someone edits a log entry:
Entry #40 ✅ → #41 ✅ → #42 ❌ TAMPERED → #43 ❌ BROKEN → ...
Hand this to an auditor.
Install
# Python
pip install cloakllm # standalone
pip install cloakllm[litellm] # with LiteLLM middleware
python -m spacy download en_core_web_sm
# Node.js
npm install cloakllm
The Numbers
150 tests across Python (62), JS (79), and MCP (9). Security audited with 6 vulnerability classes found and fixed: backreference injection, fake token injection, ReDoS hardening, spaCy model validation, middleware memory cleanup, and custom pattern safety checks. All regression-tested.
Zero runtime dependencies on the JS side. Python depends on spaCy.
What's Next
The roadmap includes LangChain.js integration, OpenTelemetry span emission, RFC 3161 trusted timestamping, sensitivity-based routing (PII → local model, clean → cloud), and an admin dashboard.
The EU AI Act deadline is August 2, 2026. 5 months from today.
→ GitHub: github.com/cloakllm/CloakLLM
→ Python SDK: github.com/cloakllm/CloakLLM-PY | pip install cloakllm
→ Node.js SDK: github.com/cloakllm/CloakLLM-JS | npm install cloakllm
→ MCP Server: github.com/cloakllm/cloakllm-mcp
Top comments (0)