Reveal what's hidden in redacted PDFs.
Unredact uses computer vision, font-aware constraint solving, and LLM reasoning to figure out what text is hiding under those black bars. Upload a PDF, and it will detect redactions, calculate exactly which strings could fit based on pixel-width constraints, and let you visually verify guesses with a live overlay.
- Detects redactions automatically using computer vision, or manually by clicking
- Solves for hidden text by finding every string that fits the exact pixel width of a redaction, accounting for font metrics and kerning
- Ranks results with AI using Claude to score candidates by contextual fit with surrounding text
- Lets you verify visually by overlaying guessed text in green on the original document — if the characters align, the guess fits
Unredact combines three techniques:
-
Computer vision — OCR extracts visible text with character-level bounding boxes. OpenCV detects black rectangles. A pixel-level font matcher identifies the document's typeface and size.
-
Constraint solving — Using the detected font's exact character widths (including kerning pairs), a parallel branch-and-bound solver written in Rust enumerates every string that fits the redaction's pixel width within a configurable tolerance.
-
LLM validation — Claude reads the surrounding text context and scores each candidate for plausibility, then results are ranked by a composite of width fit and contextual score.
PDF ──→ Rasterize ──→ OCR ──→ Font Detection ──→ Redaction Detection
│ │
▼ ▼
Width Tables ──→ Constraint Solver (Rust)
│
▼
Candidates ──→ LLM Validation ──→ Ranked Results
│
▼
Visual Overlay
- Python 3.12+
- Rust toolchain (rustup.rs)
- System packages:
- poppler — PDF rendering (
libpoppler-cpp-devon Debian/Ubuntu,poppleron Arch/macOS) - Tesseract — OCR engine (
tesseract-ocron Debian/Ubuntu,tesseracton Arch/macOS) - fontconfig — Font lookup (
fontconfig— usually preinstalled on Linux)
- poppler — PDF rendering (
- An Anthropic API key for LLM features
git clone https://github.com/Alex-Gilbert/unredact.git
cd unredact
# Create Python virtualenv and install dependencies
python -m venv .venv
source .venv/bin/activate
pip install -e .
# Build the Rust constraint solver
make build-solverOn first run, you'll be prompted for your Anthropic API key, which gets saved to .env:
make run
# Enter your Anthropic API key (from console.anthropic.com): sk-ant-...Or create .env manually:
echo "ANTHROPIC_API_KEY=sk-ant-your-key-here" > .envmake runOpen http://localhost:8000 in your browser.
This starts both the Python web server (port 8000) and the Rust constraint solver (port 3100). Press Ctrl+C to stop everything.
Drag and drop a redacted PDF onto the page, or use the file picker. The tool will automatically run OCR on every page and detect redactions and fonts.
Click on a detected redaction (highlighted on the page). A panel opens with analysis details and solve options.
| Mode | What it searches | Use case |
|---|---|---|
| Name | First names or last names from name dictionaries | Redacted names |
| Full Name | Two-word combinations (first + last) | Full name redactions |
| Common email addresses | Redacted email addresses | |
| Word | English nouns and adjectives from dictionary | General text redactions |
| Enumerate | All possible character combinations | Short redactions, anything goes |
- Set the character set (lowercase, uppercase, capitalized)
- Adjust tolerance if results are too narrow or too broad
- Add known characters if you can tell what the first or last letter is
- For word mode, toggle plural only or adjust vocabulary size
- Click Solve — results stream in as they're found
Click Validate to have Claude score each candidate based on the surrounding text context. Results are re-ranked by a composite score combining width fit and contextual plausibility.
Select a result to see it overlaid in green on the original document. If the green text aligns character-for-character with the surrounding visible text, the guess is a strong match. You can fine-tune font, size, position, and character spacing manually.
Unredact runs as two local services:
- Python server (FastAPI, port 8000) — Handles PDF processing, OCR, font detection, redaction detection, LLM calls, and serves the web UI
- Rust solver (Axum, port 3100) — Runs the parallel constraint solver, streams results back via SSE
The frontend is vanilla JavaScript with no build step. It uses canvas for rendering and SSE for real-time result streaming.
Browser ◄──── SSE ────► FastAPI (Python)
│
├── OCR (Tesseract)
├── Font detection (pixel matching)
├── Redaction detection (OpenCV)
├── LLM validation (Claude API)
│
└──── HTTP ────► Constraint Solver (Rust)
unredact/
├── unredact/ # Python package
│ ├── app.py # FastAPI server
│ ├── pipeline/ # Processing modules (OCR, fonts, solver, LLM, etc.)
│ ├── static/ # Frontend (HTML, CSS, JS)
│ └── data/ # Bundled dictionaries and word lists
├── solver_rs/ # Rust constraint solver
│ └── src/ # Axum server + DFS solver
├── tests/ # Test suite
├── scripts/ # Data build scripts
└── Makefile # Build, run, test automation
make run # Start everything (foreground, Ctrl+C to stop)
make app # Start in background
make stop # Stop background services
make status # Check what's running
make logs # Tail logs
make test # Run test suite
make debug # Run with font debug images saved to debug/
make build-word-lists # Rebuild noun/adjective dictionaries from WordNet# Tests require the Rust solver to be running
make test
