Skip to content

PII handling and local-first privacy

Where personal data flows in an LLM app, what running models locally does and does not solve, and how to minimise, redact, pseudonymise and retain data, with working redaction code, ways to measure redaction quality and local-first design patterns.

14 min·Verified 2026-111 animated diagram+50 XP

Privacy is one of the strongest reasons to self-host models. It is also commonly oversold, so be precise about what it buys you, and what you still have to do yourself.

Where does personal data land?
Interactive
  • Your appraw PII · yours
  • Model providerraw PII · third party
  • Embeddings + vector storeraw PII · yours
  • Prompt / response logsraw PII · yours
  • Analytics + error trackerraw PII · third party
  • Eval + fine-tune datasetsraw PII · yours

Places holding raw PII

6 / 6

…outside your control

2

Local inference removes the model provider from the list. It does not clean up the logs, vector store or datasets, which is where most leaks happen.

#What counts as personal data

Personal data (PII) is information that identifies or can reasonably be linked to a person. Laws define it differently, but in practice include:

  • Direct identifiers: names, email addresses, phone numbers, postal addresses, government ID numbers, account numbers, IP addresses, device IDs.
  • Quasi-identifiers: job title plus company, date of birth plus postcode, rare conditions, unique events: harmless alone, identifying in combination.
  • Sensitive categories: health, biometric, financial, children's data, precise location, political, religious or union information, sexual orientation. These usually carry stricter rules and higher risk (compliance).
  • Secrets: passwords, API keys and tokens. Not personal data in the legal sense, but pasted into prompts all the time.

Free-text prompts are the problem: users paste whatever they have, including things you never asked for.

#Map the data flow

Draw every place personal data goes:

  • user input → your app → model server (local or hosted)
  • embeddings service, vector store and cache
  • logs, traces, analytics and error trackers
  • tools and APIs the model calls
  • backups, evaluation datasets, fine-tuning sets
  • third-party SDKs (analytics, crash reporting, feedback widgets)

Hosted APIs add a third-party processor on the first arrow. Self-hosting removes that one but not the rest. Most privacy incidents in LLM apps happen in the rest of the list: a debug log, an error tracker, a shared cache or a test set copied from production (data leakage).

For each store, record: what personal data lands there, who can read it, how long it stays and who the processor is. That table is the single most useful privacy artefact you can produce.

#What local inference does and does not solve

Local inference does It does not
Keep prompts and outputs on infrastructure you control Make logs, caches or backups private
Avoid sending data to a model vendor Stop prompt injection or tool abuse
Give you control over retention Remove the need for access control
Simplify some data-residency requirements Remove legal obligations (consent, rights requests)
Let you run air-gapped for the most sensitive work Make the model forget what it was trained or fine-tuned on

#Minimise, then protect

  1. Minimise. Send the model only the fields it needs. Replace identifiers with tokens where the task allows. Ask: does the summary of this support ticket need the customer's phone number? Usually not.
  2. Redact before storing. Scan prompts and responses for emails, phone numbers, card and ID numbers, and secrets before they reach logs.
  3. Retain briefly. Set explicit retention (for example 30 days for debug logs) and enforce deletion automatically.
  4. Encrypt in transit (TLS) and at rest, with managed keys.
  5. Restrict access. Only people who need transcripts should read them, and reads should be audited.
  6. Be transparent. Tell users what is processed, where, and for how long.

#A tiny redaction pass

python
import re
PATTERNS = {
    "EMAIL": r"[\w.+-]+@[\w-]+\.[\w.-]+",
    "PHONE": r"\+?\d[\d\s().-]{8,}\d",
    "CARD":  r"\b(?:\d[ -]*?){13,16}\b",
}
def redact(text: str) -> str:
    for label, rx in PATTERNS.items():
        text = re.sub(rx, f"[{label}]", text)
    return text

Regexes miss names and free-text identifiers, and can over-match (a long order number looking like a card number). For better recall use a named-entity-recognition (NER) based tool such as Microsoft Presidio, and test it on realistic data. Redaction reduces risk; it does not guarantee anonymity.

A sketch with Presidio (check its documentation for current setup, as models and APIs change):

python
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

analyzer, anonymizer = AnalyzerEngine(), AnonymizerEngine()
text = "Contact Anna Weber at anna.weber@example.com or +49 151 2345678."
results = analyzer.analyze(text=text, language="en")
print(anonymizer.anonymize(text=text, analyzer_results=results).text)

Card numbers deserve a validity check (the Luhn checksum) to cut false positives:

python
def luhn(digits: str) -> bool:
    s = 0
    for i, ch in enumerate(reversed(digits)):
        d = int(ch)
        if i % 2 == 1:
            d *= 2
            if d > 9: d -= 9
        s += d
    return s % 10 == 0

#Pseudonymise reversibly

When the model needs a person to stay consistent across a conversation but not to know who they are, replace "Alice Smith" with PERSON_1, keep the mapping in your app, and restore it in the final response.

python
class Pseudonymiser:
    def __init__(self): self.fwd, self.back = {}, {}
    def hide(self, text, names):
        for n in names:
            tok = self.fwd.setdefault(n, f"PERSON_{len(self.fwd)+1}")
            self.back[tok] = n
            text = text.replace(n, tok)
        return text
    def restore(self, text):
        for tok, n in self.back.items():
            text = text.replace(tok, n)
        return text

Keep the mapping server-side, short-lived and access-controlled: it is the key to re-identify people. Note that pseudonymised data is still personal data under many laws, because it can be re-identified with the mapping.

#Measure your redaction

A redactor is a classifier, so evaluate it like one. Build 200 realistic samples with the sensitive items labelled, and compute:

  • Recall (found ÷ real): the share of real personal data caught. For privacy this is the metric that matters most.
  • Precision (correct ÷ flagged): how often flagged items really are personal data. Low precision wrecks utility (the model sees [PHONE] where an order number was).

Example: 100 real emails, the tool flags 100 items of which 90 are real. Recall 90 percent, precision 90 percent. Track recall per category (names are usually the weak spot), re-test when you change tools, and keep a log of misses as a regression set (regression testing).

#Local-first design patterns

Pattern What it means Trade-off
On-device The model runs on the user's phone or browser; data never leaves (phones, browser) Small models only
On-premise Model server inside your network or VPC You operate it
Split processing Redact or summarise sensitive parts locally; send only the sanitised result to a bigger remote model Complexity; redaction quality matters
Air-gapped No outbound connectivity at all Updates and model transfer need a process
Tenant-isolated Separate model instances, indices and logs per customer Higher cost
Retrieval-based Keep data in a governed store and retrieve at query time instead of fine-tuning on it Needs retrieval permissions done right

Prefer retrieval to fine-tuning for personal data: deleting a row is easy, deleting its influence from a model's weights is not.

#Rights requests and deletion

If someone asks for their data, you need to find and delete it across every store. Plan for it:

  • Tag data with a subject or user ID at write time (logs, vector chunks, caches, datasets).
  • Keep an inventory of stores and a script per store that can export and delete by ID.
  • Remember derived data: embeddings, summaries and cache entries about a person are personal data too.
  • Document backup handling and retention.
  • Test the process with a dry run before a real request arrives.

#Special categories

Health, biometric, financial, children's and precise location data usually carry stricter rules and higher risk. Treat these as separate, higher-protection flows: more aggressive minimisation, stronger access control, shorter retention, explicit legal review, and consider whether an LLM is the right tool at all. Read the compliance basics lesson.

#Evaluation and fine-tuning data

Test sets and training data copied from production are personal data too. Anonymise them, restrict access, and delete them on the same schedule as other data. Prefer synthetic or scrubbed examples for shared evaluation sets, and keep the raw originals out of repositories (eval harness).

#A privacy checklist

  • Data flow diagram with every store, processor, region and retention period
  • Prompts minimised to the fields the task needs
  • Redaction before logs, analytics, evaluation sets and error trackers, with measured recall
  • Retention limits enforced automatically
  • Encryption in transit and at rest; access limited and audited
  • No personal data in metric labels, URLs or file names
  • Vendors listed, with contracts where required
  • Deletion and export tested end to end
  • Special-category flows reviewed separately
  • Users told what is processed and where

#Common mistakes

  1. Treating "local" as "private" and ignoring logs and caches.
  2. Logging full prompts "temporarily" and forgetting.
  3. Regex-only redaction with no measurement.
  4. Copying production data into test sets.
  5. Fine-tuning on personal data with no deletion plan.
  6. Personal data in URLs, metric labels or file names.
  7. No way to find one person's data.

#Exercise

Your support assistant is self-hosted. It logs every prompt for 12 months at DEBUG level, sends error traces to a hosted tracker, and keeps a vector index of past tickets. A customer asks for all their data to be deleted.

  1. Where might their data be?
  2. What would you change so deletion is feasible?

Answers:

  1. In the prompt logs, in error traces at the third-party tracker (and its backups), in the vector index (chunks and embeddings of their tickets), in caches, in analytics, in any evaluation or fine-tuning sets built from tickets, and in database backups.
  2. Redact and minimise before logging, shorten retention to days, tag every chunk, log line and dataset row with a customer ID, scrub traces before they reach the tracker (or use an in-region tool with an agreement), keep an inventory with a delete script per store, and test it. Where deleting from backups is impractical, document the retention period.

Quick quiz

+10 XP per correct answer, +25 XP for a perfect score

  1. 1. Running the model locally on your own server means...

  2. 2. Data minimisation means...

  3. 3. A regex-based redactor catches emails and phone numbers. Which important item will it most likely miss?

  4. 4. Why replace 'Alice Smith' with PERSON_1 and keep the mapping in your app?

  5. 5. A redaction tool finds 90 of 100 real emails and falsely flags 10 non-emails. What are its recall and precision for emails?