01 · NLP · Privacy engineering

Legal Text Anonymiser

I built a tool to find and transform sensitive details in legal documents, then tested what it missed and whether the remaining details could single out a case within the dataset.

What remains after you remove a name?

Sending a legal file to an AI service can expose private details. This tool finds sensitive information and helps a reviewer decide what to remove before sharing the text.

Why deleting a name is only the first step Made-up example · illustrates the problem, not a model prediction
1 · Original text

Sarah Jones, the only surgeon in a small town, filed a complaint.

2 · Name removed

[PERSON_A], the only surgeon in a small town, filed a complaint.

The name is gone. The remaining clues could still reveal who it is.

Find

Spot sensitive details.

Detect names, organisations, case numbers and other identifying facts.

Transform

Keep the story useful.

Remove details, make them less specific, or replace names with consistent labels.

Review

Show what still needs care.

Flag distinctive details and let the reader inspect what changed. Keep the name-to-label map locally.

What I learned

Better name detection helps.
It does not guarantee privacy.

In a study of 1,268 legal judgments, one carefully chosen leftover fact singled out 81.2% within that collection. This does not measure identification of people in the real world.
See the evidence and its limits →

I built: the detection models, privacy checks, text transformations and review interface. Status: evaluated prototype; human review is still needed.

A legal benchmark with both entity labels and privacy roles.

The project uses the Text Anonymization Benchmark: 1,268 annotated European Court of Human Rights judgments. Its annotations distinguish entity types such as people, organisations and case codes, and whether an item is a direct identifier, quasi-identifier or does not require masking.

The held-out evaluation contains 555 annotator-document rows covering 127 unique judgments. That distinction matters: presenting those 555 rows as 555 independent judgments would overstate the breadth of the test data.

Corpus
1,268 annotated legal judgments
Held-out set
555 annotation rows across 127 unique judgments
Label dimensions
Entity type plus DIRECT, QUASI and NO_MASK privacy roles
Evaluation unit
Entity spans, with both partial and exact matching reported

Find sensitive details, then decide how to change them.

I normalised every detector to the same contract: text in, typed character spans out. This made spaCy, Presidio, three fine-tunes and their voting rules comparable through one evaluator. The baseline exposed a missing category: spaCy had 0% recall on case-file codes. A custom recogniser helped that label, but noisy organisation predictions kept Presidio behind the fine-tunes. Detection feeds three product modes:

Redact

Remove direct identifiers

Replace names, organisations, dates, IDs and case codes when direct disclosure is the immediate risk.

Anonymise

Generalise indirect details

Try broader ages, locations and dates, then report the remaining corpus uniqueness. If only deleting every fact reaches the target, report failure to converge; an empty fingerprint is not evidence of safety.

Pseudonymise

Preserve relationships

Map entities to stable tokens so a model can follow who did what, then restore names locally if authorised.

Practical distinction

Redaction removes visible identifiers. Anonymisation tries to reduce the chance that remaining information can single someone out. Pseudonymisation is reversible. Keeping its mapping local limits direct disclosure, but the transformed text can still contain identifying context.

In a local deployment, the name-to-label map stays on your system.

Change the names here. Restore them here.

A simplified example of reversible name replacement.

  1. On your system 01 · Prepare

    Replace names with labels

    Detect sensitive details, choose what to change and review the text.

    OriginalSarah contacted James.
    After replacement[PERSON_A] contacted [PERSON_B].

    Send the reviewed text

  2. At the AI provider 02 · Process

    Work with the labels

    The AI receives the transformed text. It does not receive the local name-to-label map.

    Example AI summary[PERSON_A] made contact with [PERSON_B].

    Return the answer

  3. Back on your system 03 · Restore

    Put the names back

    Use the saved map to restore names in the answer, then review it.

    Restored answerSarah made contact with James.

    Ready for human review

Local identity vaultThe map stays on your system.
[PERSON_A] = Sarah[PERSON_B] = James

Saved in step 1. Used again in step 3. Never included in the AI request.

Illustrative text, not a live model output. Keeping the map local limits direct disclosure; remaining details may still identify someone. This workflow requires review and is not a guarantee of privacy.

Model quality and privacy risk are measured as separate questions.

Detection: entity spans are scored with partial-match and exact-match precision, recall and F1 on the held-out annotations. Cluster-bootstrap confidence intervals resample the 127 unique judgments, not the 555 annotation rows. Repeated annotations of the same text are not independent observations. Partial matching gives credit when the right entity is found with an imperfect boundary; exact matching exposes those boundary errors.

Mosaic risk: after masking direct identifiers, the analysis selects increasingly many quasi-identifiers and asks when a record becomes unique within this corpus. This is a controlled corpus-uniqueness study, not a real-world re-identification probability.

Horizontal bar chart comparing partial-match F1 for nine Legal Text Anonymiser model configurations. A majority-vote ensemble of the three fine-tuned models leads at 87.7 percent, ahead of Longformer at 85.0, LegalBERT at 84.9 and RoBERTa at 84.8 percent. Four retired baselines and the earlier union ensemble are drawn faded and hatched, marked Dropped.
The detection ladder retains unsuccessful baselines. This overview includes historical notebook configurations; the table below uses one cache-replay comparison, with RoBERTa and LegalBERT at 512 tokens and Longformer at 4,096. Window settings change scores, so values from these two views should not be subtracted as if they were matched runs.

Better detection helped; it did not make contextual risk disappear.

0.8774
Majority-vote partial F1 · 95% cluster CI 0.8629–0.8913
+0.0214
Partial F1 versus RoBERTa in the same cache-replay comparison
81.2%
Of 1,268 judgments unique within the corpus after one selected residual fact

Detection results below use 555 annotation rows covering 127 judgments. The ensemble votes over saved predictions from RoBERTa, LegalBERT and Longformer; the hosted Studio uses RoBERTa alone at 512/64, while the ensemble rows describe separate offline experiments.

Matched cache-replay comparison · partial-match entity scoring
ConfigurationPrecisionRecallF1
RoBERTa · 512-token window0.84540.86680.8559
LegalBERT · 512-token window0.84560.86210.8538
Longformer · 4,096-token window0.83730.86310.8500
Union · at least one vote0.84210.88350.8623
Majority · at least two votes0.88980.86530.8774

Majority voting improves precision while recall is 0.0015 below RoBERTa. Union catches more mentions and has lower F1: the preferred rule depends on the cost of a missed identifier. Partial overlap can earn detection credit while leaving part of a name visible, so F1 alone cannot validate release quality.

Evidence: scores and cluster confidence intervals · ensemble evaluation.

A product check gives a less reassuring answer

In a separate union-vote simulation, 94.6% of PERSON and 63.2% of ORG mentions marked for masking were fully removed. Only 58.7% of the 555 annotation rows had no such mentions left. When the requirement expands to every person and organisation, including TAB’s NO_MASK annotations, that clean-row rate falls to 0%. The training target deliberately excludes many public institutions; a broader customer requirement needs a different target.

Inspect the coverage simulation. These are annotation-row results, not 555 independent documents or measured real-world release outcomes.

Mosaic diagnostic comparing the withdrawn full-fingerprint claim, selected-fact uniqueness and sensitivity to the document denominator
Revised interview analysis: one well-chosen fact singles out 81.2% within this corpus. This assumes access to a distinctive fact; it is not a random-fact success rate or a probability of identifying a person. Restricting fact types changes which documents qualify, so comparisons must use a common denominator.

Different goals need different versions of the tool.

Finding names, preparing useful text and avoiding missed identifiers are different jobs. These experiments show how the design could change when the priority changes.

Test scope: 555 annotation rows across 127 unique legal judgments. The small model was run on those judgments; the other policies replay saved predictions. These are exploratory comparisons on the existing test set, not fresh external validation or deployed features.

A smaller tool for a narrower job.

If the task is to extract people and organisations for indexing or review, keep the entity detector and omit the privacy-risk scorer, generalisation loop and identity vault. This is named-entity recognition (NER), with no claim that its output is anonymous.

Test run: the small, non-transformer en_core_web_sm model versus the existing fine-tuned RoBERTa, both restricted to PERSON and ORG. Here the target is every annotated name, including public organisations the privacy benchmark permits leaving visible.

Names-only detection · 12,029 mentions · typed partial matching
DetectorPrecisionRecallF1
Small spaCy model52.0%73.3%0.608
Fine-tuned RoBERTa90.5%47.9%0.627

The small model finds more of this broader target, but produces more false positives. RoBERTa was trained for the masking policy, which excludes many public institutions; it needs retraining for an “extract every name” brief. On just the 6,084 names marked for masking, the ranking is different: RoBERTa reaches 0.822 F1 versus 0.386 for the small model.

Possible adaptation: use the small model for a first-pass extraction assistant, with correction by a reader. Distil or retrain a compact model on the actual extraction target before treating it as a replacement. Latency and memory savings have not been benchmarked here.

Remove more text. Leave fewer names behind.

If a missed name costs more than an unnecessary redaction, change the policy. Start by covering every span proposed by any of the three fine-tunes. Then try a broader rule: remove each sentence-like passage containing a detection.

A high F1 score—even 0.90—does not mean every document is clean. This simulation instead checks whether every character of a target name is removed, and how much text is lost in doing so.

96.2%
Target names fully removed by the passage policy
63.9%
Of all text characters removed as the cost
87.4%
Of annotation rows left with no target names

Headline scope: the 6,084 PERSON/ORG mentions marked DIRECT or QUASI, across 555 annotation rows. Full removal is stricter than partial-match detection recall.

6,084 PERSON/ORG mentions marked for masking · 555 annotation rows
Redaction policyNames fully removedRows with no target names leftText removed
One fine-tuned detector79.3%49.9%9.5%
Three detectors · majority vote83.3%56.0%9.6%
Three detectors · cover every predicted span85.3%62.7%10.1%
Remove whole flagged passages96.2%87.4%63.9%
Remove the entire document100.0%100.0%100.0%
More coverage does not establish safety.

The passage policy still misses part or all of 3.8% of target names. Some passages have no detection to trigger removal. Suppressing the entire document is the only tested policy reaching 100%—and it leaves nothing to summarise. Neither result measures residual re-identification risk or unannotated identifiers.

What if “names” includes every person and organisation?

Keeping the policies fixed and adding NO_MASK mentions changes the task. The passage policy fully removes 88.0% of names, but only 23.6% of rows have none left. Changing the requirement exposes a larger gap.

12,029 PERSON/ORG mentions, including NO_MASK · 555 annotation rows
Redaction policyNames fully removedRows with no target names leftText removed
One fine-tuned detector44.5%0.0%9.5%
Three detectors · majority vote47.4%0.0%9.6%
Three detectors · cover every predicted span49.3%0.0%10.1%
Remove whole flagged passages88.0%23.6%63.9%
Remove the entire document100.0%100.0%100.0%
How the simulation works

All five policies are scored against the same rows. Redaction can use predictions of any entity type; a name is counted as fully removed only when the union of removed character positions covers its entire annotated span. “Text removed” counts all characters, including whitespace, and is a proxy for lost utility—not a measured loss in answer quality.

The broad policy uses punctuation and newlines to delimit passages; abbreviations can split them. It removes any segment touched by a prediction. This tests broadening the removal area, not lowering a confidence threshold: the saved cache does not contain confidence scores.

The raw union preserves coverage from overlapping spans. It differs from the earlier Results-tab union, whose longest-span voting can discard fragments: 85.3% versus 84.6% full removal. The majority and single-model rows reproduce the earlier coverage results.

The three fine-tunes use their saved windows: 512 tokens for RoBERTa and LegalBERT, 4,096 for Longformer. Repeated annotations are scored as rows; these are descriptive point estimates without new confidence intervals. A second corpus is needed before claiming general performance.

Download policy results (CSV) · Metrics, counts and provenance (JSON) · Reproduction script

Make the next experiment match the intended use.

Compact extraction

Train for all names.

Include public organisations in the labels, train or distil a smaller detector, and compare recall, memory and latency on a new corpus.

Recall-first review

Measure the review burden.

Cache confidence scores, choose thresholds on validation data and test word- or sentence-level expansion. Measure both missed names and time spent correcting excess redaction.

Useful private text

Test the downstream answer.

Compare summary accuracy and retained facts across the privacy modes. Add a separate contextual-risk assessment and external-domain evaluation.

Try the tool with a made-up legal example.

Anonymiser Studio lets you compare Source, Redact, Anonymise and Pseudonymise, inspect each detected phrase, and restore names using the demo identity map. It runs the RoBERTa fine-tune on CPU with a 512/64 inference window; the three-model ensemble results above are a separate comparison. Use the supplied synthetic example or your own synthetic text. Input and identity mappings are processed on Hugging Face infrastructure, not on your computer. Do not paste confidential information into this public demo.

Open Anonymiser Studio on this page

Compare Source, Redact, Anonymise and Pseudonymise, inspect detected phrases, and try restoring names. The hosted app loads when you open this panel; if it is waking up, allow a moment or open it in a new tab.

← Back to selected work Next: QuoteGuard →