Skip to main content

LocalRedact

PyPI Python CI

Alpha software: automated redaction can miss sensitive content. Visually review every high-stakes output before sharing it.

LocalRedact is a small, private PDF-redaction tool for instructions such as:

Redact email addresses, phone numbers, SSNs, and the customer name 'Jane Doe'.

It uses deterministic Python rules—not an LLM, agent, cloud API, telemetry, or model download. Text and coordinates are read locally with PyMuPDF. Matches are turned into PDF redaction annotations and then applied, removing the underlying content instead of merely drawing a black rectangle over it.

Install

From PyPI:

python -m pip install localredact

To run the optional loopback HTTP service:

python -m pip install "localredact[server]"

The base install has one runtime dependency: PyMuPDF. The project is packaged as both a wheel and a source distribution.

Use it

The shortest complete command is:

localredact input.pdf "Redact email addresses, phone numbers, and SSNs"

This creates input.redacted.pdf, strips common PDF extras such as metadata, embedded files, JavaScript, links, and saved form state, reopens the result, and checks that detected values are no longer extractable.

More examples:

# Exact text plus detected categories
localredact contract.pdf \
  "Redact account numbers and the customer name 'Jane Doe'" \
  --output safe-contract.pdf

# Page scope and an explicit exception
localredact report.pdf \
  "Hide contact information on pages 2-4 except 'support@example.com'"

# A custom labeled field without defining a regex
localredact form.pdf \
  "Redact the field labeled 'Employee ID'"

# Review counts without writing a PDF
localredact form.pdf "Remove financial information" --dry-run --explain

# Write a report that contains no matched values
localredact form.pdf "Remove PII" --report form.redaction-report.json

Existing output files are not replaced unless --force is supplied. The input PDF itself is never an allowed output target.

Preview instruction interpretation

The parser can be used without opening a document:

localredact-plan "Redact email addresses and card numbers on pages 1 and 3"

LocalRedact rejects unknown target words by default instead of silently guessing. --lenient is available only when a caller intentionally wants the recognized subset and is prepared to review the warning.

English it understands

The language is intentionally constrained and auditable:

  • Actions: redact, hide, remove, mask, black out, obscure, erase.
  • Common fields: emails, phones, SSNs, card numbers (Luhn-checked), IPs, URLs, dates of birth, street addresses, labeled names, account/routing numbers, passports, driver's licenses, medical records, tax IDs, insurance IDs, IBANs, API keys, bearer tokens, and JWTs.
  • Groups: PII, contact information, financial information, government IDs, health information, and credentials.
  • Exact values: quote them, for example redact 'Project Falcon'.
  • Exceptions: except, excluding, but not, or but keep, followed by a known category or quoted exact value.
  • Pages: page 2, pages 2-5, pages 1 and 3, first page, or last page.
  • Layout cues: the field labeled 'Member ID', everything after 'Secret:', or text between 'PRIVATE START' and 'PRIVATE END'.

Names and ambiguous identifiers are detected only in strong labeled contexts. For an unlabeled person's name, quote the exact value. That narrower behavior is deliberate: a tiny generic-name heuristic is more likely to redact headings and company names than to provide trustworthy coverage.

Python API

from localredact import parse_instruction, redact

plan = parse_instruction(
    "Redact email addresses, SSNs, and 'Jane Doe' except 'support@example.com'"
)
print(plan.describe())

result = redact("input.pdf", plan, "output.pdf")
print(result.redaction_count, result.category_counts, result.verified)

Use dry_run=True, ocr=True, overwrite=True, or sanitize=False only when the corresponding tradeoff is intentional. Results and JSON reports carry an ephemeral keyed digest, type, page, character count, and rectangle count; raw matched text is never returned.

Optional local HTTP service

python -m pip install "localredact[server]"
localredact-server

The service binds to 127.0.0.1:8765, disables access logs, returns no-store responses, and refuses a non-loopback bind unless --allow-network is explicit. It never makes an outbound request.

curl -o redacted.pdf \
  -F "document=@input.pdf" \
  -F "instruction=Redact email addresses and SSNs" \
  http://127.0.0.1:8765/v1/redact

Do not expose this small local service to an untrusted network without adding authentication, TLS, request isolation, rate limits, and normal production hardening.

Scanned PDFs and OCR

Born-digital PDFs work with the base pip install. Image-only pages fail closed instead of being reported as clean. For scanned documents, install Tesseract and its language data locally, make it discoverable to PyMuPDF, and run:

localredact scan.pdf "Redact PII" --ocr --ocr-language eng

OCR is local but much slower. There is no automatic cloud fallback. If an image-only page is known to be safe or will be reviewed manually, --allow-image-only-pages makes that exception explicit in the report.

Safety model and limits

LocalRedact provides best-effort automated detection, not a compliance certification. Important behavior:

  • Real PDF content removal uses add_redact_annot() plus apply_redactions(images=2), so intersecting text is removed and intersecting image pixels are blanked.
  • Output is written to a temporary sibling, verified, and atomically moved into place. A failed verification leaves no claimed-safe output.
  • No raw match values, original text, reversible maps, or learning files are persisted by the package.
  • Sanitization removes common hidden payloads by default. --keep-pdf-extras opts out when interactive features matter more than that safety margin.
  • Detection can still miss unusual formatting, handwriting, weak OCR, text stored as vector outlines, novel identifiers, or semantically sensitive prose. Visually review high-stakes output.
  • Removing text can also remove overlapping vector art or pixels. This is a safety-biased consequence of irreversible redaction.

Why this differs from OpenRedaction

The referenced OpenRedaction project has a strong local, regex-first TypeScript detector with validators and a wide ecosystem. Its document flow extracts PDF text and returns substituted text; it does not produce an applied, redacted PDF, and its public interfaces use options rather than English instructions.

LocalRedact borrows the useful architectural ideas—not source code—while keeping only a Python library, two small CLIs, an optional loopback API, a controlled English compiler, and coordinate-aware PDF removal. The pinned upstream analysis and source citations are in docs/upstream-research.md.

Test and build

Clone the source repository, then install the development dependencies:

python -m pip install -e ".[dev]"
pytest
ruff check .
python -m build
python -m pip install --force-reinstall dist/localredact-0.1.0-py3-none-any.whl

The end-to-end tests generate PDFs locally and assert that sensitive content is absent from text extraction after redaction while surrounding content and page count remain intact.

Reproduce the 25-case synthetic evaluation

Install the development dependencies and run the suite:

python -m pip install -e ".[dev]"
python tools/run_synthetic_pdf_suite.py

The suite creates 24 verified before/after scenarios across easy, medium, and complex tiers, plus one image-only case that must fail closed without OCR. It checks output with both PyMuPDF and pypdf, produces a visual comparison report, and packages every source and redacted PDF into a ZIP under output/pdf/.

License and responsible use

LocalRedact is licensed under the GNU Affero General Public License v3.0 only. Its mandatory PyMuPDF dependency is separately offered under the AGPL and commercial licensing. If your distribution or hosted-service use cannot satisfy the applicable AGPL obligations, obtain appropriate commercial licensing from Artifex or use a different backend. See third-party licensing.

This is not legal advice. Security problems should be reported privately using the process in SECURITY.md, not in a public issue.

Download files

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

Source Distribution

localredact-0.1.0.tar.gz (38.3 kB view details)

Uploaded Source

Built Distribution

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

localredact-0.1.0-py3-none-any.whl (37.5 kB view details)

Uploaded Python 3

File details

Details for the file localredact-0.1.0.tar.gz.

File metadata

  • Download URL: localredact-0.1.0.tar.gz
  • Upload date:
  • Size: 38.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for localredact-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c9dd51bd9bba18759d0854977ec31fcc7304d801592cdb5e3dc1b0d98c5a58e2
MD5 f55d142469e344a9b4cf306a8949c76e
BLAKE2b-256 d09e22e7dae4442864bafc5fb0d1b39cdab38b1103d237f03cd4db9230e425b6

See more details on using hashes here.

Provenance

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

Publisher: publish.yml on 1aifanatic/localredact

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

File details

Details for the file localredact-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: localredact-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 37.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for localredact-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a1a60129ea77e73f189e6720440918b260838671227478d598bc27da47f1d95e
MD5 2b477448dee79d85eee1ab3960b62b81
BLAKE2b-256 4eda4e4f1f7266ff5889fc90bc69bb612032ecef25b4a79bf90d59976df2381c

See more details on using hashes here.

Provenance

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

Publisher: publish.yml on 1aifanatic/localredact

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page