Changelog
What changed, and when.
Every released version of oboro, and what each one changed.
0.8.2 (2026-08-07)
- docs: Rewrite documentation prose in plain, direct sentences.
0.8.1 (2026-08-06)
- fix: Make
docs/install.ps1run the way it is advertised. Both published one-liners failed in 0.8.0: the file’s byte order mark came back inside the string fetched over HTTP and broke the opening line. A failure now also reports what went wrong rather than a missingPath. CI runs the script the way the documentation says to, so this cannot ship again. (#113)
0.8.0 (2026-08-06)
- fix: Name the agent hook
oboro uninstallwill not remove, rather than reporting everything as gone. The command sweeps the two settings filesoboro hook installwrites,.claude/settings.local.jsonfor a project and~/.claude/settings.jsonfor a user, butoboro doctorreads a third, a project’s shared.claude/settings.json, so a hook pasted there by hand survived an uninstall that had just said it removed everything and went on running. That file stays untouched, since it is committed and shared with colleagues and Oboro never wrote it, but it is now listed in the uninstall report alongside the plugin and the Docker volume, andoboro doctormarks the hook as one an uninstall leaves behind. (#111) - feat: Add
docs/install.ps1, the Windows counterpart toinstall.sh:powershell -ExecutionPolicy ByPass -c "irm https://m.canouil.dev/oboro/install.ps1 | iex"downloads thex86_64-pc-windows-msvcrelease archive, verifies it againstSHA256SUMS(and against build provenance withOBORO_VERIFY_PROVENANCE=1), and installs into%LOCALAPPDATA%\Programs\oboro\bin, adding it to the userPATHif missing; no administrator rights needed. Arm64 and--features ner/--features ocrare refused, since no prebuilt Windows build carries them, andcargo build --release --features <name>is pointed at instead. Published alongsideinstall.shindocs/_quarto.yml, with anInvoke-ScriptAnalyzerlint job onwindows-latest. (#109) - feat: Add
oboro uninstall, which removes everything the tool wrote: completion scripts, the agent hooks and skill in both scopes, the vault, the recognition model, and the binary itself. It prints everything that would go and asks to confirm;--dry-runstops after printing,--yesskips the prompt, and--keep-vaultleaves the vault and its key in place. A settings file keeps everything but Oboro’s own hook, ending as{}rather than being deleted, and the running binary removes itself on macOS and Linux; on Windows, which locks a running image, the path and the command to finish the job are printed instead. (#108)
0.7.0 (2026-08-04)
Breaking changes
- feat!: Accept
--vaultand--keyafter the command that opens a vault rather than before it, sooboro --vault work.db clean notes/becomesoboro clean --vault work.db notes/.OBORO_VAULTandOBORO_KEY_FILEare unaffected, and so is every spelling that already named the command first, which is every one the documentation ever showed. Declaring the pair once at the root made them global, and global meant listed under every subcommand’s help, includingoboro skill show,oboro models pullandoboro hook install, none of which ever open a vault: they were accepted there and quietly ignored, and the completion script offered them there too, so the help, the completion and the behaviour disagreed in the same place. They are now declared on the commands that use them, still global so that one declaration onmapcoversmap listandmap purgewithout being repeated on either, and the commands that never open a vault refuse them instead of taking them and doing nothing. (#102)
Features
- feat: Print a shell completion script with
oboro completions <shell>, forbash,zsh,fish,elvishandpowershell. The script goes to standard output and the destination it belongs in goes to standard error alongside it, so one command answers both halves of a question a generated script only ever answers half of:oboro completions zsh > ~/.oh-my-zsh/custom/completions/_oborowrites a file with nothing in front of it for the shell to trip over, the instructions still reach the terminal where they are useful at that moment, and2>/dev/nulldrops them for anyone scripting the command. Each shell is told its own convention rather than a generic sentence, including the leading underscore in the zsh filename and the two linescompinitneeds when the directory is not already on$fpath; oh-my-zsh is named first, because it puts$ZSH_CUSTOM/completionson$fpathand runscompinititself, so those users are finished once the file is written and the generic advice would send them to edit~/.zshrcfor no effect. The script is generated under the name the command carries rather than the name it was invoked by, so one written from a build directory or from a renamed copy still completes the installed name. (#101) - feat: Install a completion script with
oboro completions <shell> --install, which writes it where the shell reads it and edits your shell’s configuration only where the file alone is not enough, inside a managed block that re-running replaces and--uninstallremoves.--dry-runreports every path either would touch. The shell is taken from$SHELLwhen it is left out, and printing to standard output is still what happens without a flag, so a redirect, or a pipe intoInvoke-Expression, works as before. PowerShell refuses to be installed and says what to run instead, since it evaluates its script from$PROFILErather than reading a file. - feat: Install zsh completions to oh-my-zsh’s custom directory, or to Homebrew’s
share/zsh/site-functionswhen the prefix has one and it is writable, before falling back to~/.zfuncwith anfpathline. The first two are on$fpathalready, so nothing is added to~/.zshrcfor them. Homebrew’s prefix is read from$HOMEBREW_PREFIX, or found at/opt/homebrewor/usr/local, rather than by runningbrew, which acurl | bashpipe often cannot reach. - fix: Report a stale copy that cannot be removed, rather than failing on it. One of the places swept is Homebrew’s prefix, which is outside the home directory and may belong to another user, and a file there is not reason enough to fail an install that has otherwise worked. Nothing is ever removed with elevated rights.
- feat: Keep an install where it is. A script already on disk is the one that is updated, wherever it is, even once the machine has gained oh-my-zsh, Homebrew or bash-completion and a first install would now choose elsewhere; copies in the other known places are swept so none is left to shadow it. Moving one is
--uninstallfollowed by a fresh install, and an unchanged script is reported asAlready currentrather than rewritten. - feat: Report in
oboro doctorwhether the completion scripts on disk still match the binary. A completion script is a copy of the command surface from the moment it was generated, so a release that adds a command leaves it offering the old set and nothing otherwise says so; each one is regenerated and compared byte for byte, which answers that exactly with no version to parse, and a stale one is followed by the command that rewrites it. Every conventional destination is looked in whatever$SHELLsays, since that variable is wrong often enough to matter and a place holding no file costs no line.docs/install.shlooks in the same places and prints the same command when it finds one, being the moment that knows a version changed. It now compares each script against the binary it has just installed rather than reporting every script it finds, so reinstalling the same version says nothing at all, and a first install stays quiet. (#101) - feat: Restrict the vault database and key on Windows with
icacls, dropping every inherited grant and giving the current account alone read and write; the key is protected before its bytes are written rather than after, and a directoryoborocreates gets an inheritable grant so the WAL and SHM sidecars pick it up without anicaclscall of their own.oboro doctorreports the ACL verdict the way it already reports the Unix file mode. - feat: Publish an
x86_64-pc-windows-msvc.zipfrom the release workflow, the default build only since ONNX Runtime and Tesseract stay source builds on Windows. Checksummed inSHA256SUMSand attested for build provenance alongside the other archives. (#106)
0.6.0 (2026-08-03)
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)
0.5.0 (2026-07-30)
Features
- docs: Say what the tests on recognised scans actually assert, now that they also check the text came back rather than only that nothing survived cleaning; two of the six rendered lines is still a long way from measuring how much of a page was recognised, and the page says so. (#85)
- docs: Say that a PDF page can be read short without anything saying so. A page that cannot be processed is caught and the document refused, but the extractor drops text whose shape it does not recognise rather than failing on it, so a page can come back with less on it than it holds. The per-page floor catches a page reduced to almost nothing; one that loses a line or two is used as it is. (#80)
- feat: Read
.pptxpresentations, taking the text of every slide in presentation order, followed by every speaker note and then every comment, so a deck reaches the detectors instead of being refused; slide layouts and masters are left out, since their text is template prompt material rather than anything the author wrote. (#81) - feat: Read a scanned PDF rather than refusing it, on a build with
--features ocr: the images on its pages go through the recogniser, covering both the way a scanner stores a colour page (DCTDecode) and the way it stores a bilevel one (CCITTFaxDecode), needing no library beyond the Tesseract the feature already asks for; text drawn on the page is kept alongside what is recognised, so a scan with a searchable text layer does not lose half of itself, and a page is refused by name rather than half read when its image is in a codec that cannot be handed over, when a filter is layered over that codec, or when it carries no image at all. (#75) - feat: Install both halves with one command,
oboro skill install --with-hooks, which writes the skill and names both hooks in the same scope, asks once and shows both files it would write, and plans both before writing either, so a scope that refuses one installs neither; drop the flag for the skill alone, or useoboro hook installfor the hooks alone. (#74) - feat: Install the hooks and the skill in one step from a Claude Code plugin marketplace, with
/plugin marketplace add mcanouil/oborothen/plugin install oboro@oboro; the plugin is this repository, so it ships the skill the binary carries rather than a copy of it, and its hooks go through a wrapper that withholds the result and refuses the call when nooborois onPATH, rather than leaving a machine unprotected while looking installed. (#74) - feat: Report an enabled Oboro plugin in
oboro doctor, naming the settings file that enables it, and say of the hooks and the skill that the plugin carries them rather than that they are missing, since what it carries lives in its own files and reporting it as absent would send you to install what you already have. (#74) - feat: Say so when
oboro hook install, oroboro skill install --with-hooks, is about to name hooks a plugin already carries, since both copies would then run on every matching tool call. (#74) - docs: Name
npx skills add mcanouil/oboroas the skill-only path, for agents other than Claude Code, saying plainly that it installs the explanation and not the machine and that it symlinks the skill, which is whyoboro skill installafterwards refuses the path rather than overwriting it. (#74) - feat: Name both hooks in your agent’s settings with
oboro hook install, so wiring Oboro into Claude Code no longer means pasting JSON by hand;--projectwrites.claude/settings.local.jsonand--userwrites~/.claude/settings.json, without either it asks which, and--dry-runprints the settings as they would end up. - feat: Merge into the settings file rather than rewriting it: every other key keeps its place and its order, another tool’s hooks on the same event are left where they are, and a hook already naming
oboro hookis left exactly as you wrote it, matcher included, so a matcher you narrowed by hand survives an install. - feat: Refuse rather than replace a settings file Oboro cannot merge into, naming it: invalid JSON, a root that is not an object, or a
hooksentry of the wrong shape. Nothing is written through a symbolic link, and the file is replaced by renaming a complete one into place. - feat: Say so when the hooks just installed name an
oborothat is not onPATH, since a hook the agent cannot run fails closed on every matching tool call. - feat: Tell an agent what the hooks have done to what it reads, with
oboro skill install, which writes a skill explaining that[[EMAIL_1]]is a real value it cannot see rather than a bug or a template, and that writing the placeholder back verbatim is correct becausepre-tool-userestores it; the text is compiled into the binary so it cannot drift from the hooks it describes,oboro skill showprints it without writing anything, andoboro doctorreports both scopes. - feat: Ask which scope to install the skill into rather than guessing, since the wrong scope fails silently;
--projectand--userskip the question, and with no terminal to ask the command fails and names both flags. - feat: Leave an edited skill where it is, writing what would have been installed to
SKILL.md.oboro-proposedbeside it so the edit can be compared rather than lost;--forceoverwrites instead, and nothing is written through a symbolic link. - feat: Clean text piped in on standard input, with
printf '...' | oboro cleanor an explicit-, so a caller holding text in memory no longer has to write it to a temporary file first; the cleaned text goes to standard output,--outputis refused since there is no name to write alongside,-cannot be combined with file paths, and input that is not valid UTF-8 is refused rather than mangled. - feat: Report whether the agent hooks are installed in
oboro doctor, naming the settings file, the tools each is matched against, and whether the program it names can be run; both events are reported even when neither is found, since having only the cleaning half means placeholders reach your files. - feat: Put real values back into what an agent writes with
oboro hook pre-tool-use, which answers a Claude CodePreToolUsepayload and replaces the tool’s arguments, so a placeholder the model echoed back becomes the value again before aWriteor anEdittouches a file; every string in the arguments is restored whatever field it sits in, arguments holding no placeholder are left alone, a placeholder the vault never issued is reported and left in place, and a failure refuses the call rather than writing placeholders into your source. - feat: Clean what an agent reads with
oboro hook post-tool-use, which answers a Claude CodePostToolUsepayload on standard input and replaces the tool’s result with a cleaned one, so aRead,Grep,BashorWebFetchresult reaches the model as placeholders; a structured result keeps its shape with every string in it cleaned, and when cleaning fails the result is withheld rather than shown, with the reason reported to you and not to the model. - feat: Restore an answer piped in on standard input, with
pbpaste | oboro restoreor an explicit-, sorestorecomposes in a pipeline without a temporary file and without the/dev/stdintrick; piped text has no file to rewrite, so the restored text always goes to standard output.
Bug Fixes
- fix: Read an identifier grouped with a no-break space.
IBAN,CARD,SIRETandSIRENtook an ordinary space between groups and nothing else that reads as one, so an IBAN or a card number written with U+00A0 or U+202F was not detected at all and reached the output whole. French typography groups numbers this way and a word processor inserts them on its own, so a.docxis exactly the input that carries them. The separator is now decided once and shared by every pattern, phone number included, and still excludes line terminators, since a candidate crossing a line merges two values into one that validates as neither. (#83) - fix: Look for the value inside a candidate a checksum rejects, rather than discarding the candidate whole. A pattern that spans a range of lengths absorbs whatever follows it, so a phone number with an address after it on the same line, or a card number with stray digits after it, formed one candidate that validated as neither and reached the output untouched. The shorter spans are tried leftmost and longest first, the same order the pattern itself is matched in, and only the span that validated is redacted, so the house number stays in the text and the address is still found. Two numbers written side by side are now both found, where the first used to consume the start of the second. (#82)
- fix: Narrow the separator between the digits of a candidate phone number so it can no longer cross a line break, since
\smatches a newline: a valid number at the end of a line was merging with a leading digit on the next, such as an address or a date, into a longer candidate that then failedlibphonenumbervalidation and was discarded whole, so the number as actually written reached the output undetected. (#81) - fix: Decide page by page whether a PDF needs reading as a scan, rather than judging the document as a whole: a scanned page in an otherwise textual PDF used to be carried along by the text on the other pages and was never recognised, so whatever it held never reached the detectors. A page carrying a few words and no image, such as a section divider, is kept as it is rather than refused, and a document whose extraction stops part way through is now refused instead of coming back as though it were the whole. (#77)
- fix: Bound the names a document supplies before they reach an error message, so a file of someone else’s making no longer decides how long an error is or which control characters reach your terminal; this covers a
.docxentity and archive part, a spreadsheet’s sheet name and a PDF image filter, and a sheet name is shortened for the further reason that it is one of the places personal data turns up. (#76) - fix: Allocate placeholders under one atomic step, so
oboroinvocations sharing a vault no longer fail withdatabase is lockedor aUNIQUE constraint failederror when two of them meet the same new value at the same moment; each value still maps to exactly one placeholder. - fix: Stop quietly when a reader closes the output pipe, so
oboro clean note.txt --stdout | head -n 1ends instead of reporting a crash;map listalready did this, and nowcleanandrestoredo too. - fix: Stop quietly in
oboro doctor | head -n 1andoboro models status | head -n 1as well, the last two commands whose output was still written with macros that crash on a closed pipe. - fix: Stop quietly when a reader closes the error pipe too, so
oboro clean notes/ 2>&1 | head -n 1ends instead of reporting a crash, and a command that fails still exits 1 rather than 101; every progress and summary line, inclean,restore,map,reviewandmodels pull, now goes through one writer that drops a line it cannot deliver instead of panicking.
0.4.0 (2026-07-23)
Features
- feat: Write each workbook sheet to its own TSV file (
book.xlsxwith a sheetClientsbecomesbook.Clients.clean.tsv), keeping the tabular structure openable in a spreadsheet tool instead of flattening the workbook into one markdown file; sheet names are sanitised for the filesystem, redacted like filenames whenredact_filenamesis on, and numbered apart when they collide. - feat: Read
.csvand.tsvfiles, passed through as plain text so the cleaned output stays a valid tabular file. - feat: Name each output after its input’s format, so
data.csvbecomesdata.clean.csvanddata.tsvbecomesdata.clean.tsvwhile documents keep.clean.md;restoreneeds no change since it rewrites placeholders in any text file. - feat: Refuse any two inputs whose sanitised outputs would land on one file, including sheet outputs, case-folded names, and aliased spellings of one path, before the refused document’s values are stored in the vault.
- feat: Match a denylist term against its exact case with
case_sensitive = true, so a short name such asBellis redacted without also redacting the ordinary wordbell; terms still ignore case by default, and no regular expression is needed to make one case-sensitive. - feat: Match street addresses in the three word orders languages write them in, so
10 Downing Street,Hauptstraße 5and12 Kerkstraatare read alongside12 rue de la Paix, with no language declared anywhere; postcodes now cover the British, Canadian, Dutch and American formats as well as five-digit ones, while a bare four-digit postcode stays unmatched on purpose since it cannot be told apart from a year. - feat: Replace
default_regionwithregions, a list of region codes whose national phone number formats are read, so a document holding numbers from several countries is handled at once; a number valid in any listed region is redacted, an international+number is read whatever the list holds, and an unknown code is now refused by name instead of silently ignored. Without the key the region comes from the environment’s locale, andoboro doctorreports which regions are in force and where they came from. A configuration still usingdefault_regionis refused, naming the unknown key. - feat: Choose the languages Tesseract reads images in from
ocr_languages, or from whatever trained data is installed when it is unset, replacing the hard-codedfra+engthat made French trained data a requirement for reading any image at all; asking for a language with no trained data now says so and lists what is installed. - feat: Tidy text and markdown input before cleaning it, so trailing spaces, runs of blank lines and blank lines at either end of the file do not survive into the output; indentation is kept, since it carries markdown structure, and
.csvand.tsvare passed through byte for byte.
0.3.0 (2026-07-22)
Features
- feat: Publish prebuilt ner binaries (
x86_64-unknown-linux-gnu-ner,aarch64-unknown-linux-gnu-ner,aarch64-apple-darwin-ner), installable withinstall.sh --features ner; the Linux ones need glibc 2.39+ since ONNX Runtime has no musl build. - feat: Publish a ner Docker image under
-nersuffixed tags (ner,<version>-ner,main-ner) with the recognition model baked in and hash-verified at image build, so untold names are found with no download and no network at run time.
0.2.0 (2026-07-22)
Features
- feat: Accept a directory argument to
cleanandreview, cleaning every supported file it holds;--recursivedescends into subdirectories, unsupported files are skipped and counted, and--outputmirrors the input tree. - feat: Redact PII found in the input filename so it no longer leaks into the output name (
jean@example.com.txtbecomesEMAIL_1.clean.md), sharing placeholders with the document body; on by default and disabled withredact_filenames = falseinoboro.toml.
0.1.0 (2026-07-22)
Features
- feat: Replace sensitive values in a document with stable placeholders, so the same value always becomes the same placeholder within a vault.
- feat: Keep the mapping in a local vault encrypted with AES-256-GCM and indexed by a keyed hash, so the database alone reveals neither the values nor whether a guessed value is present.
- feat: Bind each placeholder’s sequence into the encryption, and create the vault, key and write-ahead-log sidecars owner-only, so a swapped row is detected and the files stay readable only by you.
- feat: Clean a document to placeholders with
clean, and put the real values back into a model’s answer withrestore, both reading and writing standard input and output. - feat: Step through every detection with
review, a terminal screen for accepting or rejecting each one before anything is written. - feat: Inspect and wipe the mapping with
map listandmap purge, and report the vault, configuration, supported formats and network use withdoctor. - feat: Detect emails, phone numbers, IBANs, payment cards, SIREN, SIRET, IP addresses and French addresses, each confirmed by a checksum or parser rather than a pattern alone.
- feat: Find names, organisations and addresses with a local multilingual recognition model, built with
--features nerand fetched bymodels pull, which verifies downloads against pinned hashes. - feat: Configure an allowlist, a denylist and custom identifier patterns through
oboro.toml, with accented case folded so an entry such asSociété GénéralematchesSOCIÉTÉ GÉNÉRALE. - feat: Read
.txt,.md,.docxincluding its headers, footers, footnotes and comments,.xlsx, and text-based.pdf, plus images through Tesseract when built with--features ocr. - feat: Refuse a PDF whose pages yield almost no text, rather than producing output that looks sanitised but was never read.
- feat: Publish a Docker image, a single static binary on
distroless/staticwith no shell and no network capability, and read the vault and key paths fromOBORO_VAULTandOBORO_KEY_FILEso a container can point them at a mounted volume. - feat: Install with a script that downloads the prebuilt binary and verifies it against the release checksums, or with prebuilt binaries that carry build provenance you can check with
gh attestation verify.