Note · · 6 min read
redact, Rewritten in Go: One Binary, a TUI, and Reproducible Scans
The new version of redact ships as a single static Go binary with an interactive TUI and a headless CLI. Same two scanners, plus persisted, reproducible results -- and no Python to set up.
Earlier this year we open-sourced redact — a tool that scans cloned repositories for secrets, credentials, and personally identifiable information, so you can build guardrail lists and redaction rules before any content reaches an LLM. Today we are releasing a new version, rewritten from scratch in Go. It now lives at github.com/lab34-es/redact.
The original was a Python CLI: clone the repo, run setup.sh, let it create a virtualenv, then drive everything through a wrapper script. That worked, but distributing a Python environment across security teams, CI runners, and air-gapped machines was friction we did not want. The new redact is a single static binary with two faces:
- An interactive TUI — run
./redactin a terminal and walk from path selection to a browsable results view, with live progress in between. - A headless CLI —
./redact scanand./redact ollama, for scripts, cron jobs, and CI pipelines.
Both run the same engine, and every run is persisted to a timestamped results directory that records exactly what was scanned, at which git commit, with which configuration.
What Changed
| Python version | Go version | |
|---|---|---|
| Install | setup.sh + virtualenv | one static binary |
| Interface | run-scan.sh wrapper | interactive TUI + headless CLI |
| Results | printed or exported on demand | every run persisted to results/<date_time>/ |
| Allowlisting | — | regex.txt suppression pass before scanning |
| CI integration | — | meaningful exit codes (1 = findings present) |
| Card number patterns | regex only | regex + Luhn validation |
What did not change: the two complementary scanning approaches, the 45 regex patterns across 8 categories, git history scanning, multi-repo discovery, and the output formats (table, json, txt, csv).
The TUI
Run ./redact with no arguments and the TUI takes you through five screens:
- Path selection — type the path to scan and the discovery depth.
- Repository selection — every discovered repo is listed with its HEAD SHA. Toggle with
space,aselects all,nnone. - Configuration — pick the scanner with
←/→. For Ollama, the model picker is filled from the server’s model list; if the server is unreachable you can type a model name. Set include/exclude globs, a suppression regex file, and git-history options. - Live progress — an overall repo progress bar, per-file progress, a live findings ticker, and (for Ollama) the model’s streaming output for the file being analyzed.
qcancels; partial results are still saved. - Results browser — a findings table with severity filters (
h/m/l,afor all).enteropens a detail view,nstarts a new scan, and the footer shows where results were saved.
./redact tui --path /repos --depth 3 prefills the first screen, so you can script your way to the interactive part.
The CLI
The headless interface keeps the shape of the previous version — one subcommand per scanner:
# Scan a directory of cloned repos with the regex patterns
./redact scan --path /path/to/repos --depth 2
# Context-aware scan with a local Ollama model
./redact ollama --path /path/to/repos --model llama3
# Persist per-repo JSON, or a single CSV
./redact scan --path /path/to/repos --output json
./redact scan --path /path/to/repos --output csv --output-dir ./reports
# Only scan Python and YAML files, skip git history
./redact scan --path /path/to/repos --include '*.py' --include '*.yml' --no-git-history
# 8 parallel workers per repo, 4 repos in parallel
./redact scan --path /path/to/repos --workers 8 --processes 4The flags you know from the Python version are still here (--path, --depth, --output, --include/--exclude, --no-git-history, --history-depth, --workers, --processes), joined by a few new ones:
| Flag | Description | Default |
|---|---|---|
--regex-file | Suppression patterns applied before scanning | - |
--output-dir | Base directory for the timestamped results dir | ./results |
--quiet | No per-finding progress lines on stderr | false |
--no-color | Disable colored output (NO_COLOR is honored too) | false |
--max-file-size | Ollama only: skip files larger than N bytes | 102400 |
--ollama-host | Ollama only: API URL | http://localhost:11434 |
--ollama-timeout | Ollama only: seconds without model output before a file is aborted | 120 |
Every Run Is Persisted
Whether you scan from the TUI or the CLI, redact writes a results directory for every run:
results/2026-08-22_14-05-33/
├── README.md # scanner, config, and a table of scanned paths with their git SHAs
├── findings.json # full machine-readable results (config + all findings)
├── config.json # the exact resolved configuration, for reproducibility
└── ... # extras per --output: <repo>.json / <repo>.txt, or findings.csvThe README’s path table records what was scanned and at which commit:
| Path | Git SHA | Findings | Files scanned | Commits scanned |
|---|---|---|---|---|
/repos/org-a/api | 3f2a1c9... | 12 | 240 | 87 |
This turns every scan into an auditable artifact. When a compliance review asks “what did you scan, and when?”, the answer is a directory you can archive — including the exact configuration needed to reproduce the run. Interrupted runs (ctrl+c, or q in the TUI) still write the directory with the partial results and a note.
Built for CI
Exit codes now mean something, so redact can gate a pipeline:
| Code | Meaning |
|---|---|
0 | Scan completed, no findings |
1 | Scan completed, findings present |
2 | Usage error (bad flags) |
3 | Runtime failure (unreachable Ollama, unreadable path, …) |
# Fail the job when sensitive data is found
./redact scan --path . --quiet --output csv --output-dir ./scan-resultsAllowlisting with a Suppression File
Once you have triaged findings, you do not want the same known-safe matches showing up in every scan. --regex-file points at a file with one Go (RE2) regular expression per line (# comments allowed). Every match is replaced with [REDACTED] before scanning — in files and in git history — so allowlisted values can never produce findings again.
Two things to know: RE2 has no lookahead, lookbehind, or backreferences, so patterns written for Python’s re may need adjusting; and invalid patterns fail fast with their line number.
How It Works
Interactive TUI Headless CLI
./redact ./redact scan | ollama
| |
+------------+-------------+
v
[ Repository discovery ]
walks --path for .git dirs up to --depth
|
v
[ Scan engine ]
repos in parallel (--processes)
files in parallel (--workers)
working-tree files + git history
suppression pass (regex.txt -> [REDACTED])
regex scanner | Ollama scanner
|
v
[ Findings ]
severity - category - file:line - commit
| |
v v
terminal output results/<date_time>/
(table, json, ...) (README, findings.json, config.json)The engine is UI-agnostic: the TUI and the CLI consume the same progress-event stream, and both persist every run — including partial results when a scan is cancelled.
Migrating from the Python Version
./run-scan.sh scan ...becomes./redact scan ..., and./run-scan.sh ollama ...becomes./redact ollama .... There is nosetup.shand no virtualenv anymore.--include '*.py' '*.yml'(space-separated values) is now--include '*.py' --include '*.yml'or--include '*.py,*.yml'. A stray positional argument is a hard usage error, so the change cannot pass silently.- Results are always persisted now;
--output-diris the base directory for the timestamped results directory rather than a plain export target.
Getting Started
# Clone and build
git clone https://github.com/lab34-es/redact.git
cd redact
go build -o redact ./cmd/llm-redact
# Interactive TUI
./redact
# Headless scans
./redact scan --path /path/to/repos --depth 2
./redact ollama --path /path/to/repos --model llama3Open Source
redact is MIT-licensed and available on GitHub. We welcome contributions, bug reports, and feature requests.
- GitHub: github.com/lab34-es/redact
- Requirements: Go 1.22+ (to build), Git, optionally Ollama for LLM-based scanning
- Background: the original announcement covers why we built it and how the two scanners complement each other