swapback
Your data stays yours. A local proxy that swaps names, emails, phones and keys for placeholders before a prompt reaches an LLM provider, and puts them back in the reply.

Quickstart
pip install swapback
swapback demo
swapback init && swapback-proxy
Python 3.11 or newer. swapback demo runs a fake model with made-up data: no key, no network. The proxy listens on 127.0.0.1:8787; point your client at it and change nothing else:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8787/v1")
Set it up for a real provider
swapback init writes swapback.yaml and known.yaml. Put the names, emails, phones and IDs that must never reach a provider in known.yaml (or build it from a contacts export with swapback known init --vcf contacts.vcf), set upstream: in swapback.yaml, run swapback check-config, then start swapback-proxy. The full steps, including OpenRouter and Anthropic base URLs, are in the guide.
Works with
- OpenClaw: tested end to end (2026.10.7) with one provider block; its exec, web_fetch, web_search, browser and message tools keep placeholders by default. Config.
- Hermes Agent: runs in front of the maintainer's own Hermes agent every day, on real email and calendar traffic.
- Any OpenAI-compatible client: OpenAI Chat Completions and Responses (the official SDKs, OpenRouter, LiteLLM clients). Set the base URL, change nothing else.
- Anthropic Messages: the Anthropic SDK, plain and streamed, including tool_use.
- Tool-policy recipes for Claude Code and Codex CLI: framework recipes.
How it works
flowchart LR
A["Your app or agent"] -- "Email Jane Q Doe" --> S["swapback (127.0.0.1:8787)"]
S -- "Email «PERSON_1»" --> P["LLM provider"]
P -- "Hi «PERSON_1» ..." --> S
S -- "Hi Jane Q Doe ..." --> A
- Swap. Values from your own list are matched in their common spellings (case, separators, escapes, nested JSON); detectors catch phones, emails, cards, IBANs and API keys you did not list. Each becomes a typed placeholder.
- Check. An egress re-check scans the outgoing request again. If anything listed is still there, or the scrub fails, the request is blocked (503). It fails closed.
- Restore. Placeholders in the reply are put back byte-exact, in plain and streamed replies and in tool-call arguments. Network, shell and send-style tool calls keep the placeholder unless you allow the tool, so a prompt-injected web_fetch cannot carry your email out.
No model to download and nothing probabilistic: exact matching plus patterns, about 5,900 lines of Python, 3 dependencies. Re-run the leak test yourself with swapback selftest (made-up data, 7 request paths, about 12 s).
Documentation
| Topic | Where |
|---|---|
| Why swapback, setup, agents, tool calls, configuration, hooks | docs/guide.md |
| What it does not catch, and security notes | docs/limits.md |
| Leak test, performance, detection score | docs/testing.md |
| Comparison with PasteGuard, Maskit, Veil, piighost and Kiji | docs/comparison.md |
| Changes per release | CHANGELOG.md |
| Reporting a vulnerability | SECURITY.md |
Project facts: Python 3.11+, about 5,900 lines (302 KB), 3 dependencies (PyYAML, phonenumbers, pyahocorasick), stdlib HTTP server, MIT.
Development
pip install -e ".[test]"
pytest -q # the full suite, including the holdout score as a regression floor
python evals/run_holdout.py # the detection score table in docs/testing.md (--misses lists the cases not caught)
python examples/smoke.py # end-to-end: mock upstream + proxy, plain and streamed
The vendored rule sets live in swapback/vendor (see THIRD_PARTY_NOTICES.md); tools/build_vendor.py regenerates the Presidio patterns.
See CONTRIBUTING.md and the changelog.
Roadmap
- Restore Anthropic Messages streams. Done in 0.1.1.
- Catch separator and case variants of listed values (NBSP, ., _, -, %20, all caps) under one placeholder. Done in 0.1.1.
- Restore OpenAI Responses API streams. Done in 0.3.0.
- Keep placeholders in tool calls that could send data out. Done in 0.3.0.
- Build the known-values file from a contacts export. Done in 0.3.0 (swapback known init).
- Optional, pluggable NER for names you never listed, off by default.
- More national ID and address patterns, with tests from synthetic data.
- Release on PyPI. Done: pip install swapback.
- Hooks for spend caps, local fallback and usage accounting. Done in 0.4.0.
- A public, re-runnable leak test. Done in 0.4.2 (swapback selftest).
Ideas and pull requests are welcome; open an issue first for anything large.
License
MIT, see LICENSE. Bundled rule data: gitleaks (MIT) and Microsoft Presidio (MIT), see THIRD_PARTY_NOTICES.md.