Redactus
Keep-search redaction for bank-statement PDFs. You name what stays visible. Everything else is burned out of the file.
A landlord, auditor, or reimbursement packet does not need your grocery run, your payroll, or your account number. It needs Hostinger on 03/02 for $4.23. Redactus finds those rows, keeps date / description / debit or credit, blanks the running balance and every other transaction, then asks which header fields to take with them.
This is true redaction. Text is removed from the PDF content stream. A black rectangle painted on top is not redaction. Blur is not redaction. If you can copy-paste it, it is still there.
Synthetic layout. Real statements never ship in this repo.
How it thinks
flowchart LR
A[PDF] --> B[Scan / map layout]
B --> C[Find keep terms]
C --> D[Ask PII checklist]
D --> E[Add or remove items]
E --> F[One-shot from map]
F --> G[Verify]
- Scan first. Each statement is mapped before anything burns: header, columns, date style, split-amount quirks, continuation merchants, masked fields.
ready: yesis the gate. - Keep-search.
Hostingermatcheshostinger.com,HOSTINGER *DOMAINS, and USAARECURRING DEB CARD PURCHcontinuations. - Kept rows retain date, description, and the debit or credit. Running balance is blank by default.
- Every other transaction is boxed as a whole row, including merchant continuations (
SHEETZ … WAKE FOREST NC). - Standard fields are a checklist, not a surprise: account number, routing, bank name/address, holder name/address, phone, email, member number, statement period, beginning/ending balance, period totals. The map tells you which of those this PDF actually has.
- Add or remove at any time. Another merchant, one match index, extra text on a kept row (
Larnaka), or “leave the bank name.” Re-plan is ~0.3s. You are not stuck with the first pass. - Verify fail-closed. Kept terms must still extract. Distinctive tokens from blanked rows must not.
Engine: PyMuPDF (add_redact_annot + apply_redactions). There is no JEV CLI in this tree. Overlay-only tools are out of scope.
Install
Python 3.12 or newer. PyMuPDF has wheels; no system PDF toolkit required.
pipx install redactus
# or
pip install redactus
From a clone (development):
git clone https://github.com/mrdulasolutions/redactus.git
cd redactus
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e .
Cursor users: the Agent skill at .cursor/skills/bank-statement-redact/ drives the same redactus command.
Quick start
# Map this statement first (header, columns, quirks, masked fields)
redactus scan statement.pdf --keep Hostinger --workdir /tmp/redactus -o /tmp/redactus/map.json
# Burn a copy from that map. Original is never overwritten.
redactus redact statement.pdf --map /tmp/redactus/map.json \
--keep Hostinger \
--redact-pii account_number,routing_number,running_balance,beginning_balance,ending_balance,period_totals,phone \
-o statement_hostinger.pdf \
--workdir /tmp/redactus
python -m redactus is the same CLI.
find prints indexes. Use those when you want one hit gone and the rest kept.
[72] p6 03/02 RECURRING DEB CARD PURCH … hostinger.com Larnaka $4.23
[79] p6 03/02 RECURRING DEB CARD PURCH … hostinger.com Larnaka $16.36
Revise without starting over
Extract once. Re-plan as the list changes.
| You want | Flag |
|---|---|
| Also keep Apple | --keep Hostinger --keep Apple |
| Blank one match | --unkeep-index 79 |
| Extra blank on kept rows | --also-redact Larnaka |
| Leave bank name visible | omit bank_name from --redact-pii |
| Blank the statement period too | add statement_period to --redact-pii |
| Keep running balance | --keep-running-balance |
redactus plan /tmp/extract.json \
--keep Hostinger --keep Apple \
--unkeep-index 79 \
--also-redact Larnaka \
--redact-pii account_number,routing_number,running_balance,beginning_balance,ending_balance,period_totals \
-o /tmp/plan.json
redactus preview statement.pdf /tmp/plan.json -o /tmp/preview
redactus apply statement.pdf /tmp/plan.json -o statement_hostinger.pdf --engine pymupdf
redactus verify statement_hostinger.pdf /tmp/plan.json
What it asks to blank
Recommended yes unless you have a reason: account number, routing, phone, email, SSN/ITIN, running balance, beginning balance, ending balance, period totals. Totals leak the hidden activity. A $12.99 Hostinger debit next to an ending balance of $37,225.45 is a tell.
Asked with no default yes: bank name, bank address, account holder name, account holder address, statement period.
Full field ids: .cursor/skills/bank-statement-redact/pii-fields.md.
Speed
Timed on a 14-page USAA Classic Checking PDF (~177 transactions, 7 keep hits), local SSD, Python 3.12:
| Step | Wall time |
|---|---|
| scan / map | 0.2–0.5 s |
| extract | 0.2–0.4 s |
| find | ~0.1 s |
| plan | ~0.1 s |
| apply (PyMuPDF burn) | ~0.2 s |
| verify | ~0.1 s |
one-shot redact |
0.3–0.7 s |
| revise (plan + apply, no re-extract) | ~0.3 s |
Nothing is uploaded. The statement never leaves the machine.
Layouts it already survived
USAA Classic Checking splits one visual row across PDF text objects: the Debits/Credits 0 sits ~1.5 pt above the date line, dates are MM/DD, and hostinger.com Larnaka is a continuation under DEBIT CARD PURCHASE. Redactus clusters by Y-midpoint so that still parses as one transaction.
Chase / BofA-style single-line tables with Date Description Debit Credit Balance headers work on the same path. Image-only scans are refused until OCR exists; fail closed beats a silent miss.
A synthetic statement is built in:
redactus sample -o /tmp/sample.pdf
redactus scan /tmp/sample.pdf --keep Hostinger --workdir /tmp/sample-work -o /tmp/sample-work/map.json
redactus redact /tmp/sample.pdf --map /tmp/sample-work/map.json --keep Hostinger \
--redact-pii account_number,routing_number,running_balance,beginning_balance,ending_balance,period_totals \
-o /tmp/sample_redacted.pdf --workdir /tmp/sample-work
Security
- Original PDF is never overwritten. Output is always a copy.
- Metadata is stripped on burn.
- The CLI prints last-4 masks, not full account or routing numbers.
- Do not paste live statements, full account numbers, or non-kept merchants into chat logs.
verifyis the contract: if it exits non-zero, do not deliver the file.
This is a local tool for documents you own. It is not legal advice and it is not a substitute for a records-retention policy.
Repository layout
src/redactus/ # packaged CLI (`redactus`, `python -m redactus`)
cli.py # scan / extract / find / plan / apply / verify / redact
pyproject.toml
.cursor/skills/bank-statement-redact/
SKILL.md # Cursor Agent playbook (same CLI)
pii-fields.md
LICENSE # MIT
License
MIT. Copyright (c) 2026 M.R. Dula.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file redactus-0.1.0.tar.gz.
File metadata
- Download URL: redactus-0.1.0.tar.gz
- Upload date:
- Size: 20.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20b3bf68837d57e86bb061f380f8e134557f9b7a4f5bab07c0c48fccef06da33
|
|
| MD5 |
1fc77955b3b5b7863b88a63e1ebf98fc
|
|
| BLAKE2b-256 |
89cfdf0be68eb0582cb6fd4bae642aca0c9777286518c6b3c98b70b4b33146cb
|
Provenance
The following attestation bundles were made for redactus-0.1.0.tar.gz:
Publisher:
publish.yml on mrdulasolutions/redactus
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redactus-0.1.0.tar.gz -
Subject digest:
20b3bf68837d57e86bb061f380f8e134557f9b7a4f5bab07c0c48fccef06da33 - Sigstore transparency entry: 2880975713
- Sigstore integration time:
-
Permalink:
mrdulasolutions/redactus@a2dc738dcaace4970be265eb372e45379a51a6fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mrdulasolutions
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2dc738dcaace4970be265eb372e45379a51a6fc -
Trigger Event:
release
-
Statement type:
File details
Details for the file redactus-0.1.0-py3-none-any.whl.
File metadata
- Download URL: redactus-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98f08d72cd248faf8d679290c975fd55dbb521446ecd9b56f743c3fd1476e479
|
|
| MD5 |
bea33bc0d548e4c17d6e238ba02c5aad
|
|
| BLAKE2b-256 |
115ebc8e914be423bfe535ccfd1a5f85b16b0cbf2a7d3d8342f0a2e52bcc5b58
|
Provenance
The following attestation bundles were made for redactus-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on mrdulasolutions/redactus
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redactus-0.1.0-py3-none-any.whl -
Subject digest:
98f08d72cd248faf8d679290c975fd55dbb521446ecd9b56f743c3fd1476e479 - Sigstore transparency entry: 2880975743
- Sigstore integration time:
-
Permalink:
mrdulasolutions/redactus@a2dc738dcaace4970be265eb372e45379a51a6fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mrdulasolutions
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2dc738dcaace4970be265eb372e45379a51a6fc -
Trigger Event:
release
-
Statement type: