The prompt is the leak
Every customer name, account number, and diagnosis in a prompt leaves your boundary in plaintext. It is retained, logged, and sometimes reviewed by humans under terms you did not write.
Cryptonym detects, classifies, and replaces sensitive data before a prompt leaves your process, then restores the original values in the response. The model does its job. It never sees who the data belongs to.
Two lines of code. No prompt rewrite, no proxy to operate, no network call in the default path.
7 spans detected
Nadia Haddad (tomas.novak@example.org, +971 55 000 0182) says her card 0647 5855 3500 6467 was declined. Emirates ID <<EMIRATES_ID:D7DPRPNQKDWDGRZU:KYNV>>, born 1980s. Request came from ipv4_l4d2byw5evlt.
Rania Al-Farsi (rania.alfarsi@example.ae, +971 55 123 4567) says her card 4539 1488 0343 6467 was declined. Emirates ID 784-1990-1234567-1, born 1980s. Request came from ipv4_l4d2byw5evlt.
Contains a one-way transform, so some precision does not return
| Entity | Action | Detected by | Confidence |
|---|---|---|---|
| PERSON_NAME | Surrogate | gazetteer, context | 0.88 |
| Surrogate | validator, context | 0.99 | |
| PHONE | Surrogate | validator, e164 | 0.94 |
| CREDIT_CARD | Format preserving | validator, luhn | 0.98 |
| EMIRATES_ID | Token | validator, checksum | 0.99 |
| DATE_OF_BIRTH | Generalise | validator, context | 0.82 |
| IPV4 | Keyed hash | validator | 0.90 |
This is a faithful subset, not a mock: the token grammar, the determinism, the surrogate rules, and the referential integrity are the real ones. It leaves out everything that needs a key store or a vault, which is real HMAC keying, encryption at rest, the statistical detector, and the streaming rehydrator. Tokens render in the ASCII profile here because the monospace face on this page has no glyph for the Unicode brackets.
The problem
These are not hypotheticals. They are the six ways production AI systems leak, in the order teams usually discover them.
Every customer name, account number, and diagnosis in a prompt leaves your boundary in plaintext. It is retained, logged, and sometimes reviewed by humans under terms you did not write.
An agent that queries a database and pastes the row into the next turn has exfiltrated it. Most protection stops at the first user message and never looks at what tools return.
Replace every name with [REDACTED] and the model loses coreference. It cannot tell two people apart, so answer quality falls exactly when the task gets interesting.
Which fields left the building, under which rule, on which date, and who approved the exception. If the answer is a grep across application logs, the answer is no.
The same payload may be lawful to send to a model in your region and unlawful to send across a border. That decision belongs in policy, evaluated per call.
A retrieved document can instruct the model to encode and emit what it was given. If the model was given real data, the attack succeeds.
How it works
Each one is a separate, independently testable subsystem. The order is not decorative: nothing downstream can undo a decision made upstream, which is what makes the fail-closed guarantee hold.
A drop-in wrapper around your existing SDK client, or an explicit session API when you want the control. It walks nested JSON, protects leaf strings, and guarantees the document still validates against your schema. Tool arguments and tool results go through the same path as the prompt, because that is where agents actually leak.
Architecture
The coordination service sits outside the boundary on purpose. A component that cannot reverse anything does not need to be trusted, and a component that does not need to be trusted is not a liability when it is breached.
Inside the boundary
Runs in process. Detection, policy evaluation, transformation, and rehydration all happen here, with no network call in the default path.
Quickstart
No prompt rewrite, no proxy to operate, no infrastructure change. If adoption is slower than not adopting, nobody adopts.
Point the CLI at a repository, a log directory, or a dataset. It reports what is already exposed and where. Free, offline, and no commitment to the rest of the tool.
One line. The wrapper returns an object with an identical surface, so chat, streaming, tool calls, structured outputs, embeddings, and vision inputs all keep working.
Start from a jurisdiction pack, run it in dry-run to see a decision diff, then turn it on. Every decision is recorded against a pinned policy hash so it can be replayed later.
pip install cryptonymnpm install @cryptonym/corecargo add cryptonym-corePackages are not published yet. The registry names are reserved and phase 2 ships the first alpha. The commands above are what the interface will be, not what you can run today.
from openai import OpenAIimport cryptonym client = cryptonym.wrap(OpenAI()) # Nothing below this line changes.response = client.chat.completions.create( model="gpt-4o", messages=[{ "role": "user", "content": "Draft a reply to rania.alfarsi@example.ae about card 4539 1488 0343 6467", }],) print(response.choices[0].message.content)Transforms
Surrogate is the default because it is the only action that protects the value and keeps the model able to reason about it. The others exist because sometimes that trade is wrong.
The default. Use it unless you have a reason not to.
Type correct, locale correct, and stable per subject. An Arabic name maps to another Arabic name, never to a default Anglophone one. Substitutes are drawn from standards-reserved ranges so a surrogate can never collide with a real person.
Audit-heavy contexts where the reader must see that a value was protected.
The checksum is keyed to your tenant, so a model cannot fabricate a token that resolves and a mutated token fails closed. Two profiles ship: a Unicode one using U+27E6 and U+27E7, and the ASCII one shown here for transports and fonts that mangle those brackets. This page uses the ASCII profile because the monospace face it sets has no glyph for them.
The downstream format must stay valid, for example a card or an account number.
FF3-1. The output passes the same checksum as the input and can keep a revealed suffix such as the last four digits, so a validation step downstream still succeeds.
Quasi-identifiers: ages, dates, locations, rare job titles.
Reduces precision rather than identity. This is the honest answer to combinations that identify a person even when every direct identifier is gone. It costs the model information, which is the trade.
You need to join or correlate records without carrying identity.
Truncated HMAC under a tenant key. The same input gives the same digest inside a tenant and a different one across tenants, so correlation works where it should and stops where it should not.
The span carries no analytical value at all.
Removes the span entirely. Cheapest to reason about and the most destructive, so it is rarely the right default.
Credentials, and transfers a jurisdiction does not permit.
The request never leaves. The error names the rule that fired and what your options are, because a bare exception at three in the morning helps nobody.
Policy engine
Declarative, versioned, diffable, signable, and hashed into every audit record so any past decision can be replayed against the exact rules that produced it. Unknown entity classes are blocked, not allowed.
Outbound payload
Nadia Haddad, tomas.novak@example.org, Emirates ID <<EMIRATES_ID:D7DPRPNQKDWDGRZU:KYNV>>, card 0647 5855 3500 6467, born 1980s, from ipv4_l4d2byw5evlt.
version: 1name: gcc-banking-strictmetadata: jurisdictions: [UAE_PDPL, DIFC_DPL, GDPR] frameworks: [PCI_DSS_4, ISO_27701] defaults: action: block # unknown classes are blocked, never allowed unknown_confidence_below: 0.85 on_error: block rules: - entity: EMAIL action: surrogate preserve: [domain] # keep the signal, drop the identity - entity: CREDIT_CARD action: fpe # stays checksum valid downstream reveal: last4 - entity: EMIRATES_ID action: token audit: elevated - entity: PRIVATE_KEY action: block reason: "credentials must never transit a model boundary" scopes: - match: {endpoint: "api.openai.com", data_residency: US} override: - entity: NATIONAL_ID action: block # cross-border transfer not permitted - match: {model: "llama-3.3-70b", endpoint: "local"} override: - entity: EMAIL action: allow # stays inside the boundary egress: scan_outbound: true # verify nothing survived masking on_residual: blockingress: scan_model_output: true # catch PII the model itself produced on_generated_pii: flagDirect identifiers pseudonymised, quasi-identifiers generalised, transfers outside the region blocked.
Jurisdiction packs are engineering defaults written by engineers. They are a starting point for your own review, not legal advice, and they have not been reviewed by counsel in every jurisdiction they name.
Security
A mitigation without a test is a claim. These are the invariants the build refuses to ship without, each one wired to an executable check rather than a paragraph.
If detection, policy evaluation, or the vault is unavailable or ambiguous, the request is blocked. There is no degrade-to-pass-through path anywhere in the engine.
Fault injection per subsystem asserts a blocked outcome
Not in requests, logs, traces, metrics, error messages, or crash dumps. Redaction happens at the logging sink, so a new log statement cannot leak by omission.
Capturing subscriber test over a poisoned input
The default path opens no socket. Detection runs locally. There is no phone-home, and enabling any reporting is explicit and prints a notice the first time.
Full pipeline runs under a network-deny sandbox
The optional service stores keyed digests and opaque identifiers. A full database compromise reveals that some entity of some type exists, and nothing more.
Adversarial test attempts reversal from a full dump
Erasure is a key destruction, not a rewrite of an append-only store. Per-record keys under a tenant key under a root key you hold.
Shred test asserts the subject is unrecoverable
The token checksum is keyed to your tenant. A model that fabricates, mutates, or replays a token from another tenant produces something that fails verification and is flagged.
Every byte of the token body is mutated and rejected
The residual risk column is the reason to trust the other two. A privacy tool that publishes only its strengths is telling you which page of the threat model it hopes you will not read.
| Threat | Mitigation | Residual risk |
|---|---|---|
| Detector misses a direct identifier | Layered detection, then egress verification that re-scans the outbound payload and blocks. | A systematic blind spot is inherited by the egress check. Material for entity classes not in the benchmark. |
| Quasi-identifiers still single someone out | Quasi-identifier detection, population uniqueness scoring, generalisation, and a k-threshold that blocks. | High and irreducible. The scorer estimates; it does not prove. A better auxiliary dataset beats it. |
| Prompt injection tries to exfiltrate values | Tokens carry no information, unissued tokens never resolve, encoded-token detection, ingress scanning. | The entity class of a token is visible to the model by design. An opaque profile trades quality for that. |
| Vault theft | Envelope encryption, root key in the OS keystore or an HSM, ciphertext bound to tenant and policy version. | A vault stolen with an unlocked root key is fully reversible. Key custody is yours. |
| Insider disables protection | The escape hatch is per call, rate limited, emits an elevated audit event, and can be forbidden by policy. | A fully privileged insider can bypass the library entirely. It is not a defence against one. |
Performance
Stated as gates rather than as results, because the public benchmark is phase 4 work and has not run yet. When it does, the methodology and the corpus go out with the numbers.
Reported per entity class rather than as an aggregate. An aggregate hides the class you happen to care about behind the ones that are easy.
These are the engineering targets that gate a release, measured per entity class rather than as an aggregate, on the project benchmark. They are not yet published results: phase 4 builds the public corpus and the leaderboard, and the numbers will be posted with their methodology whether or not they flatter us. Enabling the NER layer or the local model assist changes the latency figures by an order of magnitude, and that will be reported separately. See the roadmap.
Alternatives
Categories rather than product names, because the useful question is not which vendor wins a matrix, it is which of these you already have and what it leaves uncovered.
| Capability | Cryptonym | Regex redaction | Enterprise DLP | Prompt instructions | Manual review |
|---|---|---|---|---|---|
| Model receives no identifying data | Yes | Yes | Partial | No | Yes |
| Original values restored in the response | Yes | No | No | No | No |
| Coreference survives masking | Yes | No | No | No | Partial |
| Checksum-validated detection | Yes | No | Yes | No | Partial |
| Covers tool arguments and tool results | Yes | No | Partial | No | No |
| Streaming safe | Yes | No | Partial | No | No |
| Deterministic and reproducible | Yes | Yes | No | No | No |
| Hash-chained audit trail | Yes | No | Yes | No | Partial |
| Fails closed on error | Yes | No | Partial | No | No |
| Runs offline with no network call | Yes | Yes | No | Yes | Yes |
| Scales past a pilot | Yes | Partial | Yes | Yes | No |
Where it fits
The pattern is the same everywhere: the model is useful, the data is not yours to hand over, and the gap between those two facts is where this sits.
Summarise clinical notes and triage referrals without sending a patient identity to a model.
Dispute handling and fraud narratives where card numbers must stay format valid downstream.
Case work assistants under residency rules that differ per endpoint and per record.
Discovery review and contract summarisation across documents with privileged party names.
Protect tool arguments and retrieved chunks, which is where agent systems actually leak.
Log analysis and incident copilots that would otherwise paste production data into a prompt.
Open source
The specification is versioned separately from the implementation so a third party can build a compatible engine and prove it with the shared conformance suite. The threat model, the decision records, and the limitations page are all in the repository, and the limitations page is the one worth reading first.
Pricing
No security feature sits behind a paywall. Paid tiers exist for what genuinely costs money to run: coordination across machines, managed keys, and the evidence pack an auditor asks for.
The whole engine. Not a crippled tier.
For teams that need shared token consistency across machines.
Regulated deployments that have to show their work.
Cloud and Enterprise are not purchasable yet. The coordination service is phase 5 work, and this page will say so until the day it ships.
Questions
Answered as they would be in a security review, including where the answer is no.
No, and anyone who tells you otherwise is selling something. Pseudonymised data is still personal data. Cryptonym is a technical and organisational measure that sharply reduces exposure at one specific boundary. It is not an exit from the regulation, and the documentation says so on its own dedicated page.
In surrogate mode the target is under two percent degradation against an unprotected baseline, because the model still sees a coherent, type-correct, locale-correct world where the same person is the same person throughout. Token mode is more visible to the model and costs more. Generalising a quasi-identifier genuinely removes information, and no transform can give that back.
On your machine. Deterministic validators and the local NER model both run in process. The optional language model assist runs through llama.cpp or Ollama, locally. Routing detection to a remote model would defeat the entire point, so the engine has no code path that does it.
The outbound payload is re-scanned after transformation, and a surviving high-confidence identifier blocks the request. That converts a silent miss into a visible failure. It is not a complete answer: the egress check uses the same detector, so a systematic blind spot is inherited. That limitation is documented rather than glossed over.
The model never receives it. A token carries no information about what it stands for, and the mapping exists only in your local vault. A token the model invents does not verify, because the checksum is keyed to your tenant, and an unverified token is never resolved. What the model can still reveal is that a value of a given class was present.
There is one engine, written in Rust. Both packages are thin bindings over it, and a shared JSON conformance suite runs against the built artefacts, not the source tree. Byte-identical output for the same input, key, and policy is a blocking CI gate rather than an aspiration.
An egress proxy with a generated development CA for local work, and a sidecar for Kubernetes. There is also an MCP server and an MCP proxy, which matters because tool results are the most common leak path in agent systems and most vendors ignore them entirely.
Destroy that subject's key. The records become unrecoverable ciphertext, which satisfies erasure without rewriting an append-only store. One caveat worth checking in your own deployment: if your key store keeps snapshots, backups, or a soft delete, the data is not actually shredded.
Not yet. The specification, the threat model, and the roadmap are public and ahead of the implementation on purpose. Phase one is in progress and the repository states honestly what is built and what is not. If you need this in production this quarter, talk to us before you plan around it.
Start with the free scanner to see what your codebase and logs are already exposing. Adopt the broker when you are ready. The whole engine is Apache 2.0.