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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

redactus-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

redactus-0.1.0-py3-none-any.whl (21.9 kB view details)

Uploaded Python 3

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

Hashes for redactus-0.1.0.tar.gz
Algorithm Hash digest
SHA256 20b3bf68837d57e86bb061f380f8e134557f9b7a4f5bab07c0c48fccef06da33
MD5 1fc77955b3b5b7863b88a63e1ebf98fc
BLAKE2b-256 89cfdf0be68eb0582cb6fd4bae642aca0c9777286518c6b3c98b70b4b33146cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for redactus-0.1.0.tar.gz:

Publisher: publish.yml on mrdulasolutions/redactus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for redactus-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98f08d72cd248faf8d679290c975fd55dbb521446ecd9b56f743c3fd1476e479
MD5 bea33bc0d548e4c17d6e238ba02c5aad
BLAKE2b-256 115ebc8e914be423bfe535ccfd1a5f85b16b0cbf2a7d3d8342f0a2e52bcc5b58

See more details on using hashes here.

Provenance

The following attestation bundles were made for redactus-0.1.0-py3-none-any.whl:

Publisher: publish.yml on mrdulasolutions/redactus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 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