10 August 2026 · Corpus 25 synthetic · 127 ECHR judgments
Models claude-sonnet-4-6 / gpt-4o · Temperature 0
Replace the identities, keep the document
The premise is ordinary enough. Before a document leaves the building, run it through a deterministic engine that replaces every name, organization and identifier with a stable HMAC pseudonym. The frontier model gets the structure and the legal substance; it never gets the client. The key stays local.
Here is what that actually does to a court filing. Left, the original. Right, what goes over the wire.
As filed
SUPERIOR COURT OF THE STATE OF CALIFORNIA
COUNTY OF ALAMEDA
GREGORY VANCE, an individual,
Plaintiff,
v. Case No. RG26-118842
DORSEY HOLDINGS LLC, a California
limited liability company,
and DOES 1-20,
Defendants.
COMPLAINT FOR BREACH OF CONTRACT AND FRAUD
As dispatched
CODE_9ae488 CODE_a3d387 OF THE CODE_31a0c4 OF CODE_cdef03
CODE_95afec OF CODE_61f946
CODE_ea99de CODE_a28d67, an CODE_c4f52b,
Plaintiff,
v. CASE_NO_cf9b85
ORG_3e8363 ORG_4a9b7c ORG_0d06ce, a CODE_cdef03
CODE_410635 CODE_62c212 CODE_4e7692,
and CODE_0906e3 1-20,
Defendants.
COMPLAINT FOR BREACH OF CONTRACT AND CODE_c2cd00
A synthetic filing from our evaluation corpus. Roughly 85% of tokens are replaced. Legal doc-type vocabulary — Plaintiff, Defendants, COMPLAINT, BREACH OF CONTRACT — is deliberately preserved, because the downstream task needs it.
The reasoning survives
The obvious objection to this design is that entities are the reasoning substrate. Legal analysis is relational — who owes whom, under which agreement, in which posture — so stripping the parties might strip the argument. We had asserted for a month that it didn’t. We had never measured it.
It doesn’t. Scored only over the documents the model actually processed:
TaskOriginal textPseudonymizedDeltaDocument type1.001.000.00Practice area0.950.90−0.05Summary coverage0.960.90−0.06
Classification is untouched. The other two each lose exactly one document out of twenty-one — 0.048 per document, which is this corpus’s resolution floor. At this sample size a one-item swing is indistinguishable from noise.
So the trade everyone assumes they’re making — worse answers in exchange for privacy — mostly isn’t there.
But a third never come back
Keyed · claude-sonnet-4-6
39 of 127 refused · 30.7%
stop_reason: refusal, empty content. On the smaller synthetic corpus the figure was 4 of 25 — 16%, reproduced across three independent runs. The larger measurement is roughly twice as precise and sits in the upper half of the smaller one’s range.
This is the result we didn’t go looking for, and it’s the one that matters. The model isn’t reasoning worse about pseudonymized documents. On a large minority of them it declines to reason at all.
The control is the finding
The obvious objection is that these are simply distressing documents. European Court of Human Rights judgments describe torture, killings and state violence in plain terms, so a refusal on content alone was genuinely plausible — which is exactly why the unmodified arm ran alongside.
Keying removed · same model, same 127 documents
39 refusals → 0
Not one document in the corpus was refused on its own content. Every refusal in the keyed arm was caused by pseudonymizing a document the model would otherwise have processed.
A 0-of-127 control is a cleaner result than we expected to get. It removes the confound entirely: this is not sensitive-content filtering, and it is not a property of legal documents. It is caused by the protection.
Nor is it density. Across both corpora the refused documents have a lower mean substitution rate than the ones that succeed — 0.685 against 0.705 here. Documents where nearly every token is replaced come back fine; some where fewer are replaced do not.
In the synthetic corpus the four refusals were a demand letter, client correspondence, an email thread about legal advice, and an attorney work-product memo.
It’s the provider, not the pseudonyms
The natural conclusion at this point is that pseudonymized text is inherently hard for safety classifiers — dense substitution reads as obfuscation, and a stripped demand letter reads as a threat. It’s a tidy story. We ran a second provider before telling it.
Keying kept · provider swapped to gpt-4o
0 of 40, still keyed
Not a detection artifact. We checked all three ways OpenAI can signal refusal — a populated refusal field, finish_reason: content_filter, and an empty completion — and all three were zero. The model averaged 949 output tokens on keyed input. It is reading the documents, not failing quietly.
If GPT-4o shared the 30.7% rate, the probability of seeing zero refusals in 40 documents is about 4 in 10 million. The two providers are not drawing from the same distribution.
So the finding is not “pseudonymized documents get refused.” It is that your privacy layer and your provider’s safety classifier interact — and the interaction is specific to the provider, not to the technique.
That’s a less dramatic headline and a more useful one. If you are building a pseudonymize-then-dispatch pipeline, this is not a risk you can reason about from first principles or read off a model card. You have to measure it, on your corpus, against each provider you intend to use — and re-measure when either the classifier or your substitution rate changes.
One thing that did generalize
Both models try to enumerate every pseudonym they see as something to redact, and blow through the output budget doing it. GPT-4o hit the cap on 34 of 40 keyed documents; the other model showed the same behavior. Whatever you’re building, the pseudonymized path needs a larger output allowance than the raw one — and if you score truncated JSON naively, you will read a formatting failure as a quality collapse.
Our working hypothesis, on four examples and therefore not established: a demand letter stripped of its firm letterhead and party identity reads like a threat. Pseudonymization removes exactly the context that marks a document as legitimate professional work.
Prepending a system-prompt declaration — this text was pseudonymized by an authorized redaction engine; the tokens are not encoded content — recovers two of the four. Worth shipping. Not a fix.
What this costs, architecturally
The design we were defending said: key everything at intake, then choose cloud or local per task on cost and quality alone. Security becomes a one-time decision at the boundary; locality becomes a budget question.
That’s wrong, on an axis nobody had listed. Cost and quality are not the only dimensions: availability is one too, and it varies by provider in a way you cannot predict. A pipeline that silently returns nothing on a third of its documents is not shippable, so something has to answer for them.
What that something is turns out to be a real choice rather than a forced move. A second provider is the cheap answer — demonstrated here, and far less work than standing up local inference. A local model is the expensive answer, but it keeps the documents from reaching one more vendor, which for some buyers is the entire point of the exercise. Before this measurement we thought local was mandatory. It isn’t. It’s a decision, and it should be made deliberately.
Stable pseudonyms are a join key
A second, quieter problem. Pseudonyms are stable by design — the same name always produces the same token, which is what makes multi-document reasoning work. Stability is also a gift to whoever holds the logs. Over many dispatches, a provider accumulates a linkable corpus under persistent identifiers.
We measured how much. Then we measured it again, properly, because the first answer was garbage:
Counting methodCross-matter pairs linkableAll pseudonyms (naive)72.4%Excluding coded common words28.8%Excluding the firm’s own name13.4%Genuine identities only5.9%
The engine fails closed, so every out-of-vocabulary ordinary word also becomes a stable pseudonym. Join on those and you discover that all 300 document pairs are “linked” — which measures the English language, not the client roster. One pseudonym appeared in all 25 documents. Another resolved to the word as, classified as an organization.
Strip the vocabulary noise, the misclassifications, and the fragments of split entity names, and the honest number is 5.9%. Publish 72% and the first competent reader takes it apart.
Two things worth stating plainly. No unique identifier — case number, matter number, email, account — appeared in two different matters; those are matter-scoped by construction. And the largest single linker is the firm’s own letterhead, on every document it sends. Keying anonymizes your clients, not you.
Mitigation, measured
Per-matter key rotation → 0.0%
Rotating the key per matter eliminates cross-matter linkage entirely while preserving the within-matter linkage the task needs. Per-document rotation also reaches zero, but destroys within-matter linkage — capability paid for nothing.
Limits
-
Two models, unequal samples. 127 documents against one provider, 40 against the other. The gap between 30.7% and 0% is far too wide for sample size to explain, but the null result is a null result on 40 documents — its own upper bound is about 7%, not zero.
-
One genre. The 30.7% comes from European Court of Human Rights judgments — real, third-party-labelled, but public case law rather than private law-firm work product. It settles the rate for court documents. Whether a firm’s own correspondence and memoranda refuse at the same rate is open; the synthetic corpus hints lower, at 16%.
-
Ranges, not points. 39 of 127 carries roughly a 23–39% confidence interval. Quote the range or quote the count, not “30.7%” as though it were a constant.
-
The linkage numbers are still small-sample. The 5.9% rests on the 25-document corpus; ECHR case law has no firm letterhead and no matter structure, so it cannot test that finding. The direction of the rotation result is structural and will hold; the magnitude is not quotable.
-
An artifact we nearly published. At a 768-token output cap, six pseudonymized documents truncated mid-JSON, because the model tried to list every pseudonym as a redaction span. Scored naively, that formatting failure looked like a catastrophic quality collapse. Raising the cap made it vanish. Check your harness before you believe your result.