Two scripts for redacting sensitive information from PDFs:
generate_wordlist.py— generates a text file of all variants of personal info (names, SSNs, phone numbers, etc.)redact_pdf.py— redacts terms from a PDF or a folder of PDFs using that wordlist
uv venv
uv pip install -e .Requires Python 3.9+. Runtime dependency: pymupdf.
# 1. Generate a wordlist from personal info
python3 generate_wordlist.py \
--name "John Michael Doe" \
--ssn "345-34-2345" \
--dob "01-01-1990" \
--phone "555-555-0000" \
--email "john@example.com" \
--address "123 Main St, Springfield, IL 62701" \
-o wordlist.txt
# 2. Redact a folder of PDFs using the wordlist
python3 redact_pdf.py -i ./docs/ -w wordlist.txtOutput PDFs are saved alongside the originals as <name>_redacted.pdf.
Generates a plain-text wordlist (one term per line) covering all common variants of each input field. All matching in redact_pdf.py is case-insensitive, so case variants are not generated.
| Flag | Description |
|---|---|
--name "Full Name" |
Full name — repeatable or comma-separated |
--ssn "SSN" |
Social Security Number — repeatable or comma-separated |
--dob "DOB" |
Date of birth (MM-DD-YYYY or YYYY-MM-DD) — repeatable or comma-separated |
--phone "Phone" |
Phone number — repeatable or comma-separated |
--email "Email" |
Email address — repeatable or comma-separated |
--address "Address" |
Mailing address (Street, City, ST ZIP) |
-o FILE |
Output file (default: stdout) |
-a, --append |
Append to existing file, deduplicating against current contents; creates file if missing |
All fields except --address accept multiple values either by repeating the flag or using comma separation:
--name "John Doe" --name "Jane Smith"
# or
--name "John Doe, Jane Smith"Name — full name, reversed (Doe, John), first+last, first+middle, each token, initial combos (J. Doe, J.M. Doe, John M. Doe), hyphen variants
SSN — dashes (345-34-2345), spaces (345 34 2345), dots (345.34.2345), no separator (345342345), masked last-4 variants (XXX-XX-2345, ***-**-2345, xxx-xx-2345, XXXXX2345, *****2345)
DOB — US numeric formats, ISO, European (when unambiguous), 2-digit year, no-separator compact (01011990, 19900101), named month, abbreviated month, ordinal day (January 1st, 1990)
Phone — (555) 555-0000, (555)555-0000, 555-555-0000, 555.555.0000, 5555550000, 555 555 0000, and all of those with +1 and 1 country code prefixes
Email — as-is, local part as standalone (if not generic like info or admin), subaddress tag stripped (john+work@ → john@)
Address — full address, street only (with abbreviation expansion, e.g. St ↔ Street), city+state (abbreviated and full, e.g. Springfield, IL and Springfield, Illinois), city, ZIP, no-comma variant
# Multiple people, append to existing file
python3 generate_wordlist.py --name "John Doe" --ssn "345-34-2345" -o wordlist.txt
python3 generate_wordlist.py --name "Jane Smith" --phone "555-999-1234" -a -o wordlist.txt
# Pipe to redact in one step
python3 generate_wordlist.py --name "John Doe" --ssn "345-34-2345" | \
python3 redact_pdf.py -i input.pdf -w /dev/stdin -o output.pdfPerforms true redaction — removes text from the PDF content stream, not just a visual overlay. Images and vector graphics are left untouched.
Matching is case-insensitive by default.
Note: Scanned or image-only PDFs will yield zero matches. OCR must be run first.
| Flag | Description |
|---|---|
-i FILE|DIR |
Input PDF or folder (required) |
-o FILE |
Output PDF — required for single file, ignored in folder mode |
-w TERM [TERM...] |
Terms to redact, or a path to a .txt wordlist file |
--log FILE |
Write detailed per-file output to a log file (folder mode) |
--color R,G,B |
Redaction fill color as floats 0.0–1.0 (default: 0,0,0 black) |
--case-sensitive |
Enable case-sensitive matching |
--dry-run |
Count matches without writing output |
--verbose |
Print per-page match details (written to log in folder mode) |
--workers N |
Parallel workers for folder mode (default: CPU count; 1 = sequential) |
python3 redact_pdf.py -i input.pdf -w wordlist.txt -o output.pdf
python3 redact_pdf.py -i input.pdf -w "John Doe" "confidential" -o output.pdfProcesses all *.pdf files in the folder in parallel. Already-redacted *_redacted.pdf files are skipped. Outputs <name>_redacted.pdf alongside each original.
python3 redact_pdf.py -i ./docs/ -w wordlist.txt
python3 redact_pdf.py -i ./docs/ -w wordlist.txt --log redact.log --verbose
python3 redact_pdf.py -i ./docs/ -w wordlist.txt --workers 4The console shows a progress line per file as it completes. All detail (per-page hits, per-term counts, warnings) is written to --log if provided:
[info] Found 8 PDF file(s) — using 4 worker(s)
[info] Detailed log : redact.log
[1/8] 12% contract.pdf (14 matches)
[2/8] 25% invoice.pdf (0 matches) ⚠ no matches
...
Folder summary:
File Matches Pages affected
---------------- ------- --------------
contract.pdf 14 3/10
invoice.pdf 0 0/2