# cpr-filter

A Claude Code mod that keeps three kinds of personal data out of Claude's context.

Every time a tool runs (reading a file, running a command, searching), the mod rewrites the result
before the model sees it:

| In the file | What Claude reads |
|---|---|
| A Danish CPR number, `120384-1234` or `1203841234` | `[CPR]` |
| An email address, including æ, ø and å | `[EMAIL]` |
| A Danish phone number, with or without +45 or 0045 | `[TELEFON]` |

The file on disk is never changed. Claude also gets a short note saying how many values were replaced,
so it does not conclude that your file is masked, and that the filter can miss a value written another
way, so it does not treat the rest of the result as clean.

## When it cannot mask something, it withholds

Since version 0.3.0 (2 October 2026) the filter fails closed:

- A failed command is filtered like any other result. Before 0.3.0 an error result went through
  untouched, so `cat kunder.csv; exit 1` showed Claude every value.
- A CPR number spread out one digit at a time (`1 2 0 3 8 4 - 1 2 3 4`) is replaced too.
- If a result still looks like it holds a CPR number, email or phone number spread out one character
  at a time, as `sed 's/./& /g'` prints it, the whole result is withheld and Claude is told why.
- If the filter itself breaks on a result, that result is withheld. A mod hook that throws is
  skipped, so without this the raw result would reach Claude.

## Audit log

Every result the filter changes or withholds writes one line: time, tool, and how many CPR numbers,
emails and phone numbers were replaced, or why the result was withheld. Never the values. One file
per day in `~/.claude/cpr-filter/audit/`, or in the folder named by `CPR_FILTER_AUDIT_DIR`. If a line
cannot be written, Claude Code says so on screen.

## Install

Mods need a Claude Code version with mod support (launched 1 October 2026). Unzip the folder, then:

```
claude --plugin-dir ./cpr-filter
```

or package it in your own plugin marketplace, so your organisation's managed settings can load it
for everyone.

Check it:

```
claude plugin validate ./cpr-filter
claude plugin test ./cpr-filter
```

## What it will not do

- It matches formats, not meaning. A ten-digit order number shaped like a date can be taken for a
  CPR number, and an eight-digit number can be taken for a phone number. Test it on your own data.
- It does not see names, addresses or free-text notes. A name next to a placeholder is still personal
  data.
- It filters what tools hand to Claude. It does not filter what you type into the prompt yourself.
- It is a safety net, not a permission system. A value rewritten in a form it does not know (base64,
  written out in words, reversed) still gets through. It stops accidents, not someone who wants the
  data. Claude refused every request to get around it in our tests, but that is Claude's judgement,
  not the filter's.
- Mods run with the same access to your machine as Claude Code. Read the code before you install
  it, as you would any code.

Made by Brinvik, Copenhagen. Free to use and change, no warranty. Write-up:
https://brinvik.com/en/journal
