Every time a backend concatenates a user message into a prompt, writes a request body to a log or forwards a payload to a helpdesk, it is moving personal data out of the process and into a system that was never designed to hold it. sanitype is a TypeScript library that puts one call between that payload and its destination: it takes the object and returns a copy with the same shape and the sensitive fields redacted, masked, hashed or dropped, plus a report of everything it touched. It runs entirely in-process, with no network calls and no runtime dependencies. It was also built in an unusual way: the idea was dictated by voice to an agent, the agent wrote 563 lines of specification, and Claude Code implemented version 0.1.0 against them in a single goal, 39 minutes later.
TL;DR
- It combines two strategies: field-path rules for what you already know is sensitive, and detectors over free text for the email someone pasted into a notes field.
- By default an object goes in and an object with the same structure comes out. Every call returns a report of what was touched, where and by which detector, and that report never contains the original value.
- The repository was written the other way round: first SPEC, ARCHITECTURE, ROADMAP and COMPARISON, then the code. 39 minutes passed between the specification commit and the implementation commit.
sanitype in twenty seconds:
your backend
│
▼
sanitize(payload)
├─ field rules what you already know is sensitive
├─ detectors what shows up inside free text
└─ action redact · mask · hash · drop · tokenize
│
▼
LLM · logs · analytics · third-party APIs
+ report: what was touched, where and by which detector
What sensitive data leaks out of a backend
The problem is not that somebody wants to leak data. It is that there are four common exits through which a payload leaves the process whole, and none of the four feels like a decision about privacy when you write it:
user payload
├──► LLM prompt OpenAI, Anthropic, a local model
├──► logs and tracing Sentry, Datadog, the request logger
├──► analytics Segment, PostHog, internal events
└──► third-party APIs helpdesk, webhooks, partner integrations
The first one is the newest and the quietest. A support ticket carrying the customer's phone number and national ID gets concatenated into the prompt as it is, and the prompt travels to an external vendor that may retain it. The second is the oldest: somebody added logger.info({ body: req.body }) to debug one case, and that log has been collecting emails and card numbers for two years. The third and fourth are variants of the same carelessness: the whole object goes out because separating the fields the destination actually needs takes work.
What these four have in common is that they happen at the outbound edge of the process. That is where something has to sit, and that something has to be cheap to call, because if it costs a network round trip nobody is going to put it in the logger.
Why nothing that already exists fit
Before writing a line I went through the landscape, and I wrote it down in a COMPARISON.md inside the repository, because "why not just use X" is a question that shows up in the first issue.
| Option | What it does well | Why it did not fit |
|---|---|---|
| DLP as a service (Google Cloud DLP, Purview, Macie) | Broad entity coverage, model-based detection, compliance tooling around it | A network call per scan: latency, cost per request and one more vendor dependency |
| Self-hosted Microsoft Presidio | Mature, strong entity recognition, no per-call cost | It is a Python project: from a Node backend it means deploying and monitoring another service |
| Regex npm packages | Lightweight, dependency-free, easy to read | They work on loose text: they do not know that user.ssn in your type is a national ID, and they return no report |
| Browser extensions | They catch what a user pastes into a UI | They see nothing of server-to-server traffic, which is exactly the case here |
The gap is specific: a native TypeScript library that runs in-process, understands the shape of your objects and not just the text, and returns an auditable record of what it did. That is the position sanitype occupies, and the price it pays for it is in the comparison: there is no entity-recognition model behind it, and free-text detection is bounded by the patterns it ships.
Keep reading
That is the first half. The full walkthrough — with the rest of the implementation, the trade-offs and the things that only show up in production — is on my blog:
Read the full post on ramonchancay.me →
Originally published at www.ramonchancay.me/blog/scrubbing-pii-before-the-llm-sanitype.

Top comments (0)