0.6.0
Install
Quick install (script)
curl -fsSL https://m.canouil.dev/oboro/install.sh | bash
# or pin this exact release
curl -fsSL https://m.canouil.dev/oboro/install.sh | bash -s -- --version 0.6.0The script picks the archive for your machine, verifies it against SHA256SUMS, and installs into /usr/local/bin when writable, otherwise ~/.local/bin.
It needs bash and curl; on a minimal distribution such as Alpine, install them first with apk add bash curl.
Add --features ner for the build that also finds untold names (on Linux it needs glibc 2.39+), then fetch its model:
curl -fsSL https://m.canouil.dev/oboro/install.sh | bash -s -- --version 0.6.0 --features ner
oboro models pull # about 348 MB, once, verified against pinned hashesDocker
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:0.6.0 clean contract.docxThe vault volume is not optional.
Without it the mapping between placeholders and real values disappears with the container, and the document can never be restored.
The ghcr.io/mcanouil/oboro:0.6.0-ner tag carries the ner build with the recognition model already inside the image, so untold names are found with no download and no network at run time.
A prebuilt binary
Pick the archive for your machine from the table below, then:
VERSION=0.6.0
TARGET=x86_64-unknown-linux-musl # or whichever row matches
curl -fsSLO "https://github.com/mcanouil/oboro/releases/download/${VERSION}/oboro-${VERSION}-${TARGET}.tar.gz"
curl -fsSLO "https://github.com/mcanouil/oboro/releases/download/${VERSION}/SHA256SUMS"
# Check it is what was published.
sha256sum --ignore-missing --check SHA256SUMS
tar -xzf "oboro-${VERSION}-${TARGET}.tar.gz"
install -m 0755 oboro /usr/local/bin/oboroOn macOS, shasum -a 256 --ignore-missing --check SHA256SUMS does the same job.
With Rust already installed
cargo install --git https://github.com/mcanouil/oboro --tag 0.6.0From source, with the optional features
The default binaries read .txt, .md, .docx, .xlsx and text-based .pdf, and find structured values and anything on your denylist.
They do not find names nobody told them about, and they do not read images.
Names need a -ner archive from the table below (then oboro models pull), the :0.6.0-ner image, or a source build.
Images need the Tesseract system libraries, so reading them stays a source build:
cargo build --release --features ner # names and organisations, then: oboro models pull
cargo build --release --features ocr # images and scanned pages, needs TesseractIf names are not being redacted, a default build is almost certainly why.
oboro doctor reports what any build can do.
To build the optional features without setting up the system libraries on your machine, use the devcontainer, which carries the pinned toolchain, Tesseract and the OCR libraries:
docker build -f .devcontainer/Dockerfile -t oboro-dev .devcontainer
docker run --rm -it -v "$PWD":/work -w /work -u vscode oboro-dev \
cargo build --release --features "ner,ocr"In Visual Studio Code or a GitHub Codespace, reopen the folder in the container instead.
Verify what you downloaded
Beyond the checksum, every archive carries build provenance, so you can confirm it came from this repository's workflow and not from somewhere else:
gh attestation verify "oboro-0.6.0-x86_64-unknown-linux-musl.tar.gz" \
--repo mcanouil/oboroThis tool checks the model it downloads against a pinned hash before using it.
It would be inconsistent to ask you to trust its own binaries on sight.
Which archive is which
| Archive | For |
|---|---|
x86_64-unknown-linux-musl |
Linux on Intel or AMD. Statically linked, so any distribution, glibc version or Alpine. |
aarch64-unknown-linux-musl |
Linux on ARM, including most cloud instances. Statically linked. |
aarch64-apple-darwin |
macOS on Apple silicon. |
x86_64-unknown-linux-gnu-ner |
Linux on Intel or AMD, with name recognition. Needs glibc 2.39+ (Ubuntu 24.04+, Debian 13+). |
aarch64-unknown-linux-gnu-ner |
Linux on ARM, with name recognition. Needs glibc 2.39+. |
aarch64-apple-darwin-ner |
macOS on Apple silicon, with name recognition. |
The -ner archives find untold names once the model is fetched with oboro models pull; the others are the smaller default build.
There is no Windows build.
The code that creates the vault key readable only by you is Unix-specific, and shipping a build where that quietly does nothing would misrepresent what the tool guarantees.
Documentation
Changes
Features
- fix: Skip a byte order mark at the head of an
.eml. Windows tooling writes one, RFC 5322 has no place for it, and the parser read it as the beginning of the first header's name, so that header stopped being one and was dropped whole.Fromis written first more often than anything else and is the densest identifying header a message has, so a name and an address reached nothing while the message still read as though it had been sanitised. (#98) - feat: Read
.emlinput, so an email reaches the detectors instead of being refused. The headers a person writes and reads are rendered first, then every part holding text: taking the bodies from the parts themselves rather than from the parser's own idea of what a mail client would display is what stops amultipart/relatedwhose HTML root is not its first part being read as headers alone. An HTML body has a line break put before every element other than the inline ones, because the flattener breaks a line on<br>and a closing</p>and on nothing else, and Gmail composes one<div>per line with no whitespace between the tags; naming the elements that run on, rather than the ones that break, keeps an element nobody thought of from welding two values into one that matches no rule. Amailto:ortel:link target is lifted into the text, since flattening discards attributes. A body is told from an attachment by its disposition rather than by carrying a filename, since a client that labels the HTML it displays with aname=is still displaying it. HTML the flattener cannot follow, an unclosed<head>or comment among it, is read by stripping its tags instead of being dropped. Every attachment is named, by its type alone when it has no filename, and its bytes are never read; every copy of a header written more than once is read; a forwarded message is followed; and a body whose transfer encoding will not decode fails rather than being read short. A parsed message is taken apart a level at a time rather than dropped, so a file of eighty thousand stacked forwards is read instead of overflowing the stack and aborting. Charsets are decoded single-byte and multi-byte alike, so a Shift-JIS message is read as written. (#97) - docs: Say that
.jsonand.yamlare not read. The formats section named the older Office and OpenDocument extensions the tool turns away, and the parts of a.pptxand a.odtit misses, but never these, so a reader checking whether their file was covered learnt nothing about them. A file with one of those extensions is refused rather than read partially, and the message names the formats the build does read. (#96) - docs: Give the changelog page a single heading. It supplies its own title, and then included this file, which opens with a heading and a line of boilerplate written for reading it on its own, so the page carried two level-one headings and said twice what it was. The preamble is cut on the way in and this file is left as it is. (#92)
- feat: Refuse to start
oboro mcpuntil you have said what itscleantool may read, with--root DIRnaming a directory it may read within, repeatable, or--unconfinedto let it read any file you can read. The caller over this protocol is a model rather than you, and a client that offers to remember its approval turns one careless click into a standing licence, so reading the whole disk is not something to arrive at by leaving an argument off. A root is resolved once at startup, so a symbolic link inside one that points outside it is refused rather than followed, and a root that does not exist or is not a directory stops the server while there is still a person watching rather than becoming a refusal to every later call; a path outside every root is refused in the same words whether or not it exists, so the refusals cannot be used to map a disk. (#91) - docs: Say what
oboro mcpdoes not guarantee, beside the tool list where you decide whether to add it and at length in Limitations: it offers the agent a tool rather than intercepting anything, so unlike the hook it depends on the agent choosing that tool and its worth is reach into clients that have no hook system; cleaning is not confinement, since passwords, API keys and access tokens are not detected kinds and pass through as written, so a root bounds which files can be reached rather than promising anything about what is in them, and for a client with no file access of its own this server is the read capability it did not otherwise have; and the absence of a restore tool is not itself protection, since in any client with a shell the model can runoboro restore --stdoutoroboro map list --revealwithout the server being involved at all. (#90) - feat: Serve the Model Context Protocol on standard input and output with
oboro mcp, JSON-RPC 2.0 one message to a line, so an agent in a client with no hook system can ask for a cleaned file instead of reading the file itself. Two tools are offered:clean, which takes a path and answers with that file's text as placeholders, one content block per part and one block per sheet under a## <sheet name>heading for a workbook, the sheet name in that heading cleaned whateverredact_filenamessays, since that setting governs a filename written to your own disk and this heading goes to a model instead; covering the.pdf,.docx,.xlsx,.pptxand.odta plain file read cannot open at all; andmap_list, which takes no arguments and names the placeholders the vault has issued, never the values behind them and not the times they were created either, whichoboro map listdoes print, since a timestamp for each entry is a record of your working hours rather than anything a model can reason with. Restoring is deliberately not offered, since over this protocol the caller is the model and a model that can write a file of placeholders and read it back afterwards could use a restore tool to obtain every value the vault holds. The2025-11-25revision is implemented and2026-07-28is never answered with, because that revision removes theinitializehandshake andpingand requires aserver/discovermethod, none of which this server does.--config, orOBORO_CONFIG, is worth setting: a client launches the server with a working directory of its own choosing, so the nearestoboro.tomlis unlikely to be your project's, and without one the server runs on the defaults with no denylist and no custom patterns. Two lines on standard error at startup name the configuration that was resolved and which detectors are configured and installed. (#89) - feat: Read
.odtdocuments, taking the body fromcontent.xmland the headers and footersODFkeeps instyles.xml, along with annotations, footnotes and the alt text set on an image. Whether a piece of text belongs to a paragraph is decided by namespace and position rather than by naming the elements that hold metadata: text is running text only when the element holding it is inODF's text namespace and a paragraph is the innermost thing open around it, and everything else is placed on a line of its own. That way round, an element nobody thought of costs a line break instead of letting a name concatenate onto the word beside it and escape detection. An image's bytes and a document's macros are skipped rather than run through the detectors. (#88)