An anonymisation layer between your files and a language model.
oboro replaces sensitive values in a document with stable placeholders.
You can then paste the text into Claude Code, Copilot, Codex or Cursor.
No phone numbers, bank details, addresses or client names leak out.
Oboro keeps the mapping in a local, encrypted vault.
Use the vault to turn the model's answer back into the real values.
The tool never sends anything anywhere. It is a single binary and makes no network requests.
contract.txt ──► oboro clean ──► contract.clean.md ──► paste into a model
│ │
▼ ▼
vault (encrypted) ◄────── oboro restore ◄── model's answer
The same value always becomes the same placeholder within one vault. So the model can still see that two documents mention the same client.
There are several ways to install Oboro, quickest first. See the Quickstart for full detail.
Install script — installs the prebuilt binary for macOS or Linux, and verifies it against the release checksums:
curl -fsSL https://m.canouil.dev/oboro/install.sh | bashAdd --features ner to also detect names not on any list (Linux needs glibc 2.39+). Then fetch the model:
curl -fsSL https://m.canouil.dev/oboro/install.sh | bash -s -- --features ner
oboro models pull # about 348 MB, onceWindows — installs the prebuilt binary and verifies it against the release checksums:
powershell -ExecutionPolicy ByPass -c "irm https://m.canouil.dev/oboro/install.ps1 | iex"This installs Oboro into %LOCALAPPDATA%\Programs\oboro\bin. It adds that folder to your user PATH if the folder is missing. You do not need administrator rights. Windows has no prebuilt build yet for name recognition (ner) or image reading (ocr). For either feature, build from source: cargo build --release --features ner (or ocr).
Docker — needs no toolchain, just one static binary. The vault volume holds the mapping, so you must create it:
docker volume create oboro-vault
docker run --rm -v oboro-vault:/vault -v "$PWD":/work -w /work \
--user "$(id -u):$(id -g)" ghcr.io/mcanouil/oboro:latest clean contract.docxThe ghcr.io/mcanouil/oboro:ner tag carries the ner build with the recognition model already inside. It finds names with no download and no network at run time.
Prebuilt binary, by hand — download the archive for your machine and SHA256SUMS from the releases page. Verify the archive, then put oboro on your PATH.
With Rust — cargo install --git https://github.com/mcanouil/oboro.
From source — the only way to get ocr, since no prebuilt binary or image carries it:
git clone https://github.com/mcanouil/oboro.git
cd oboro
cargo build --release # default build
cargo build --release --features "ner,ocr" # names and image OCRDevcontainer — for building or contributing with only Docker on the host. It carries the pinned toolchain, Tesseract and the OCR libraries. Reopen the folder in the container in Visual Studio Code or a GitHub Codespace. See CONTRIBUTING.md.
Uninstalling — oboro uninstall removes everything the tool wrote, including the vault. See the reference.
The default prebuilt binary and Docker image carry no optional feature. Name recognition (ner) links ONNX Runtime, which has no musl build. So its prebuilt forms are separate: glibc release archives from the install script, and the :ner image. Optical character recognition (ocr) needs the Tesseract shared libraries at run time, so ocr stays a source-only build.
Claude Code reads files itself. Pasting a cleaned copy into it protects nothing, because the agent already read the original. Two hooks put Oboro in that path. The plugin installs both hooks, plus the skill that explains what they do:
/plugin marketplace add mcanouil/oboro
/plugin install oboro@oboro
The plugin still needs the binary on your PATH. It cannot install one.
Until the binary is there, Oboro withholds every matching tool result and
refuses every matching write, rather than leave you unprotected.
If the binary is already installed, one command does the same job without a plugin:
oboro skill install --with-hooksIt asks whether to cover this project, in .claude/settings.local.json and
.claude/skills/, or every project, in ~/.claude/settings.json and
~/.claude/skills/. --project and --user skip the question. --dry-run
prints what both halves would write, without writing either. Oboro plans
both halves before it writes either one. So if a scope refuses one half,
Oboro installs neither.
Drop --with-hooks to install only the skill. Use oboro hook install to
install only the hooks. Oboro never moves, reorders or removes anything
already in the settings. A hook that already names oboro hook is left
exactly as you wrote it. This is what the hooks half adds:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Read|Grep|Bash|WebFetch",
"hooks": [{ "type": "command", "command": "oboro hook post-tool-use" }]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": "oboro hook pre-tool-use" }]
}
]
}
}post-tool-use replaces a tool's result with a cleaned one. So the model
reads [[PHONE_1]] where the file held a phone number. pre-tool-use puts
the real values back into what the model writes. So the placeholder never
reaches your source. You need both hooks: with only post-tool-use, the
model writes placeholders straight into your files.
An agent that was never told what [[PHONE_1]] is will guess. It might
guess a bug in your file, a template to fill in, or a redaction to work
around. The skill explains what the placeholder is. It is the other half of
the command above, and it installs on its own too:
oboro skill installoboro skill show prints the skill text without writing anything. The
plugin carries the same skill. Install the skill one way or the other, not
both.
For an agent other than Claude Code, install just the skill:
npx skills add mcanouil/oboroThat installs the explanation, not the machine. It installs no binary and
no hooks, and redacts nothing until you add them. It also symlinks the
skill by default. That is why oboro skill install afterwards refuses to
overwrite the path, and names it instead.
oboro doctor reports which halves are installed. It says whether they
came from the plugin or from your own settings, and whether the skill is
there. Believing the agent side is wired up is not the same as having done
it.
If Oboro cannot do its job, it does not fail silently. On the way out, it withholds the result. On the way in, it refuses the call. Either way, it tells you why.
Oboro never covers what you type yourself. The event that fires on a prompt can add context to it, but cannot rewrite it. So a value you paste into the chat reaches the model exactly as you typed it. Paste a document instead, and the hook covers it. See Limitations for the full account.
Without an agent that has hooks, or when you handle a document yourself:
# Anonymise a document.
oboro clean contract.txt
# Look at the result, then paste it into a model.
cat contract.clean.md
# Put the real values back into the answer you got.
oboro restore answer.md
# See what the vault holds.
oboro map list
# Go through the detections yourself before anything is written.
oboro review contract.txt
# Check the setup.
oboro doctorBoth clean and restore accept --stdout. Both read standard input when
you pipe text in. So they compose in a pipeline, and nothing has to touch
disk first:
oboro clean report.txt --stdout | pbcopy
pbpaste | oboro restoreclean and review also accept a directory. They clean every supported
file inside it. Unsupported files are skipped and counted; they do not
stop the run:
# Every readable file in the folder, then its subfolders too.
oboro clean contracts/
oboro clean contracts/ --recursive --output sanitised/With --output, Oboro mirrors the directory's subfolders under it. So
files that share a name in different subfolders do not collide.
| Format | How |
|---|---|
.txt, .md |
Read directly; trailing spaces and blank-line runs tidied |
.csv, .tsv |
Read byte for byte; the output keeps the tabular extension |
.docx |
Text runs from the body, headers, footers, footnotes and comments |
.pptx |
Text from slides, speaker notes and comments |
.eml |
Headers, every body part, forwarded messages, attachment names |
.odt |
Body, headers and footers, annotations, footnotes, image alt text |
.xlsx, .xlsm |
One TSV file per sheet, named book.<sheet>.clean.tsv |
.pdf |
Embedded text; a scan needs a build compiled --features ocr |
.png, .jpg, .tif |
Tesseract, with a build compiled --features ocr |
Optical character recognition is optional, because it needs the Tesseract
system libraries. Without it, the binary depends on nothing but Rust, and
Oboro refuses images with a message, rather than reading them as empty.
With it, Oboro uses whatever trained data Tesseract has installed, so you
do not need to declare a language; ocr_languages picks among them.
cargo build --release --features ocrDetection does not depend on the document's language. A file that mixes languages is handled in one pass. This build recognises:
| Kind | How it is verified |
|---|---|
| Email addresses | Pattern |
| Phone numbers | libphonenumber |
| IBANs | ISO 13616 mod-97 checksum |
| Payment cards | Luhn checksum, 13 to 19 digits |
| SIRET | Luhn on both the SIREN prefix and the whole number |
| SIREN | Luhn checksum |
| IP addresses | Parsed as IPv4 or IPv6 |
| Street addresses and postcodes | Pattern |
| Anything you list yourself | Your regular expressions and terms |
Oboro matches street addresses in the three word orders languages use. So
12 rue de la Paix, 10 Downing Street and Hauptstraße 5 are all read,
with nothing declared. Two settings exist as hints, and neither is
required. regions widens which national phone formats are read; an
international + number is caught whatever it holds. ocr_languages
names the language an image is written in. See
Limitations for
what the postcode patterns cover and miss.
A local, multilingual recognition model finds personal and company names.
Any build with --features ner includes it. The install script and the
:ner Docker image both ship a prebuilt model. From source, build it
yourself:
cargo build --release --features ner # downloads ONNX Runtime while building
oboro models pull # about 348 MB, once, verified against pinned hashesThe build fetches ONNX Runtime. So --features ner needs network access
at build time, and a fully offline build fails there. The model itself
runs on your machine. Once built, models pull is the only command that
touches the network, and only when you run it. The :ner image skips
even that, since the model is baked in.
Without the model, names are matched from the denylist in oboro.toml
instead.
The model over-redacts, so oboro review exists to put some of it back.
It lists every detection with its kind, confidence and surrounding line.
You accept or reject each one before Oboro writes a single byte:
j/k move space toggle a accept all n reject none w write s skip q quit
Rejecting a detection leaves the value in the output. Oboro never records a rejected value in the vault.
The model over-redacts, deliberately. A real name and an ordinary
phrase score almost the same: "Thomas Bernard" scores 0.237 while "The
quick brown fox" scores 0.218. No threshold separates them cleanly. So the
default errs towards redacting, and expects you to read the result. Raise
ner_threshold to redact less, at the risk of missing names. Lower it to
redact more.
oboro reads the nearest oboro.toml, searching upwards from the working
directory. Every section is optional.
# Regions whose national phone number formats are read. Optional: without it
# the region comes from the locale, and international + numbers always work.
regions = ["FR", "GB"]
# Languages requested from Tesseract when reading images. Optional: without it
# whatever trained data is installed is used.
ocr_languages = ["fra", "eng"]
# The local recognition model. Lower the threshold to redact more.
ner_enabled = true
ner_threshold = 0.15
# Redact PII in the input filename too, not just the contents.
redact_filenames = true
# Values that must never be redacted.
allowlist = ["My Own Company Ltd"]
# Terms that must always be redacted.
# Case is ignored unless case_sensitive = true.
[[denylist]]
term = "Acme Consulting SARL"
kind = "provider"
# Your own identifier formats.
[[patterns]]
name = "contract number"
regex = "CT-[0-9]{6}"| Path | Contents |
|---|---|
~/.oboro/vault.db |
Placeholder mapping, values encrypted with AES-256-GCM |
~/.oboro/key |
The 32-byte key, created on first use |
Oboro creates both files readable only by you: an owner-only file mode on Unix, an ACL granting only your account on Windows. Oboro looks up values through a keyed hash, not the plaintext. So the database alone reveals neither the values nor whether a guessed value is present.
If you lose the key, no one can read the vault, including you. Pass
--vault and --key to keep a separate vault per project.
Read them before trusting the output with anything that matters.
- Identifiers that fail their own checksum are not recognised. A mistyped IBAN will not be detected.
- The recognition model redacts some ordinary prose as though it were a name. This is the intended direction of error, not a bug. Read the output before you send it.
- Without
--features ner, names are only redacted if you list them. - Oboro reads a PDF made of scanned images only with the
ocrfeature, and only when the page image isDCTDecode,JPXDecodeorCCITTFaxDecodewith nothing layered over it. For any other page, Oboro refuses the file and names the reason, rather than reading it half-way. This includes a page with no image at all. - Oboro decides whether to recognise a page one page at a time. So a scanned page inside an otherwise textual PDF is still read. A page with a few words and no image is kept as it is, not refused.
- Reading images needs the
ocrfeature and Tesseract. A plain build refuses them. - Recognition is tested on rendered text, not on real photographs. Treat text recovered from an image as less reliable than text read directly.
- Older
.doc,.xlsand.pptare not read at all, nor are.odsand.odp. - Detection favours redacting too much over too little. Use the allowlist when it goes too far.
- Read the sanitised output before you share it. No tool of this kind catches everything.
See CONTRIBUTING.md for bug reporting, development setup, and commit conventions.
If you use Oboro in your work, please cite it.
Citation metadata is provided in CITATION.cff.
GitHub renders it via the "Cite this repository" widget on the repository sidebar.
This project is licensed under the MIT License. See the LICENSE file for details.