Skip to main content

Redactus

Keep-search redaction for bank-statement PDFs. You name what stays visible. Everything else is burned out of the file.

PyPI License: MIT Python 3.12 Engine: PyMuPDF Runs locally

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.

Redacted USAA-style statement: Hostinger rows visible, other transactions and account number blacked out

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]
  1. Scan first. Each statement is mapped before anything burns: header, columns, date style, split-amount quirks, continuation merchants, masked fields. ready: yes is the gate.
  2. Keep-search. Hostinger matches hostinger.com, HOSTINGER *DOMAINS, and USAA RECURRING DEB CARD PURCH continuations.
  3. Kept rows retain date, description, and the debit or credit. Running balance is blank by default.
  4. Every other transaction is boxed as a whole row, including merchant continuations (SHEETZ … WAKE FOREST NC).
  5. 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.
  6. 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.
  7. 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.
  • verify is 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.

Release files for redactus 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for redactus 0.1.0
File Size Uploaded
redactus-0.1.0.tar.gz 20.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for redactus 0.1.0
File Interpreter ABI Platform
redactus-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 42.6 kB

Release files / redactus-0.1.0.tar.gz

Download URL redactus-0.1.0.tar.gz
Size 20.7 kB
Tags Source
SHA-256 checksum
How to use checksums
20b3bf68837d57e86bb061f380f8e134557f9b7a4f5bab07c0c48fccef06da33
BLAKE2b-256 checksum
How to use checksums
89cfdf0be68eb0582cb6fd4bae642aca0c9777286518c6b3c98b70b4b33146cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release files / redactus-0.1.0-py3-none-any.whl

Download URL redactus-0.1.0-py3-none-any.whl
Size 21.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
98f08d72cd248faf8d679290c975fd55dbb521446ecd9b56f743c3fd1476e479
BLAKE2b-256 checksum
How to use checksums
115ebc8e914be423bfe535ccfd1a5f85b16b0cbf2a7d3d8342f0a2e52bcc5b58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page