Skip to content

0.6.0

Choose a tag to compare

@mcanouil-dev mcanouil-dev released this 03 Aug 13:41
5b87888

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.0

The 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 hashes

Docker

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.docx

The 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/oboro

On 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.0

From 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 Tesseract

If 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/oboro

This 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

https://m.canouil.dev/oboro/


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. From is 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 .eml input, 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 a multipart/related whose 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. A mailto: or tel: 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 a name= 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 .json and .yaml are not read. The formats section named the older Office and OpenDocument extensions the tool turns away, and the parts of a .pptx and a .odt it 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 mcp until you have said what its clean tool may read, with --root DIR naming a directory it may read within, repeatable, or --unconfined to 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 mcp does 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 run oboro restore --stdout or oboro map list --reveal without 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 whatever redact_filenames says, since that setting governs a filename written to your own disk and this heading goes to a model instead; covering the .pdf, .docx, .xlsx, .pptx and .odt a plain file read cannot open at all; and map_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, which oboro map list does 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. The 2025-11-25 revision is implemented and 2026-07-28 is never answered with, because that revision removes the initialize handshake and ping and requires a server/discover method, none of which this server does. --config, or OBORO_CONFIG, is worth setting: a client launches the server with a working directory of its own choosing, so the nearest oboro.toml is 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 .odt documents, taking the body from content.xml and the headers and footers ODF keeps in styles.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 in ODF'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)