Skip to main content

Detect and reversibly mask PII / secrets in text before sending it to an LLM.

Project description

redactor

PyPI Python versions License: MIT

redactor

Detect and reversibly mask PII / secrets in text before you send it to an LLM — then un-mask the model's reply.

Part of the ragkit suite. Install with pip install ragkit-redactor, then import redactor.

redactor scans text for things you probably don't want leaving your machine — emails, phone numbers, SSNs, credit cards, IP addresses, URLs, API keys — swaps them for stable placeholders like [EMAIL_1], and gives you a mapping to put the originals back afterward. Send the redacted text to a third-party AI API, get a response that still references the placeholders, and restore it locally.

  • Pure Python standard library. No dependencies.
  • Python 3.8+.
  • Reversible round-trip by design (restore(redact(t).text) == t).
  • Luhn-validated credit-card detection to cut down false positives.
  • Pluggable custom detectors with optional validators.

Install

pip install ragkit-redactor

Local development (from redactor/):

pip install -e .

Quick Start

import redactor

text = (
    "Hi, this is Alice. Reach me at alice@example.com or +1 (555) 123-4567. "
    "My card is 4111 1111 1111 1111 if you need to charge the deposit."
)

result = redactor.redact(text)

print(result.text)
# Hi, this is Alice. Reach me at [EMAIL_1] or [PHONE_1].
# My card is [CREDIT_CARD_1] if you need to charge the deposit.

print(result.mapping)
# {'[EMAIL_1]': 'alice@example.com',
#  '[PHONE_1]': '+1 (555) 123-4567',
#  '[CREDIT_CARD_1]': '4111 1111 1111 1111'}

# --- send result.text to your LLM of choice ---
llm_reply = "Sure — I'll confirm the deposit and email [EMAIL_1] the receipt."

# Un-mask the model's output locally:
print(result.restore(llm_reply))
# Sure — I'll confirm the deposit and email alice@example.com the receipt.

The redacted text is safe(r) to send to a third-party API; the sensitive values never leave your process.

API Reference

redactor.redact(text, **kwargs) -> RedactionResult

Convenience wrapper: builds a Redactor(**kwargs) and redacts text in one call.

redactor.luhn_check(number_str) -> bool

Runs the Luhn checksum over the digits in number_str (ignoring spaces/dashes). Used internally to validate credit-card candidates; exposed for your own use.

class Redactor(detectors=None, placeholder_template="[{type}_{n}]", mask_char=None)

  • detectors — list of detector names to enable. Defaults to all built-ins. Pass [] to start empty and add your own.
  • placeholder_template — format string with {type} and {n} fields. Default "[{type}_{n}]" produces [EMAIL_1], [PHONE_2], etc.
  • mask_char — if set (e.g. "*"), matches are replaced by that character repeated to the length of the match. This mode is one-way (see below).

add_detector(name, pattern, validator=None)

Register a custom detector. pattern is a regex string or compiled pattern. validator(str) -> bool optionally filters raw matches (return False to reject). Custom detectors take priority over built-ins for overlap resolution.

r = redactor.Redactor()
r.add_detector("EMP_ID", r"EMP-\d{4}", validator=lambda s: s != "EMP-0000")

detect(text) -> List[Match]

Returns all matches across enabled detectors, with overlaps resolved and sorted by start offset. Overlap resolution is deterministic: earlier start wins; for the same start the longer span wins; remaining ties go to detector priority (registration order, custom detectors first).

redact(text) -> RedactionResult

Detects, then substitutes placeholders (or mask characters). Identical values get the same placeholder (dedupe). Output is built by walking matches left-to-right, so offsets never corrupt.

restore(text, mapping=None) -> str

Replaces placeholders in text with their originals using mapping (required — a bare Redactor keeps no state). Longer placeholders are substituted first so [X_1] can't be clobbered by [X_11].

class Match

Dataclass: type (detector name), value (original text), start, end (offsets in the source), placeholder (assigned during redact, else None).

class RedactionResult

  • .text — the redacted string.
  • .matches — list of Match with placeholders assigned.
  • .mapping{placeholder: original_value} (empty in mask_char mode).
  • .restore(model_output) — un-mask placeholders in an LLM response using this result's mapping. This is the reversible round-trip.

Built-in Detectors

Name What it matches
EMAIL Email addresses
PHONE US-style and international-ish numbers (+country, separators, parentheses)
SSN US Social Security numbers (###-##-####)
CREDIT_CARD 13–16 digit sequences (spaces/dashes allowed) that pass the Luhn check
IP_ADDRESS IPv4 addresses
IPV6 IPv6 addresses (basic)
URL http/https URLs
API_KEY sk-... tokens and long hex secrets (32+ chars)
AWS_ACCESS_KEY AKIA + 16 uppercase alphanumerics

False positives & Luhn validation

Regex PII detection is a balance between catching real data and not flagging every number in your prose. A few pragmatic choices:

  • Credit cards are only reported when the digits pass luhn_check, which discards the vast majority of random 13–16 digit strings.
  • API keys / secrets are kept deliberately narrow (sk- tokens and long hex blobs) rather than "anything that looks random", to avoid swallowing ordinary words and IDs.
  • Overlaps (e.g. an IP embedded inside a URL) collapse to a single match, so you never get doubled or garbled output.

You will still see occasional misses and occasional false hits. Tune with the detectors list and add_detector.

Reversible placeholders vs. mask_char

There are two modes:

  • Placeholder mode (default, reversible). Values become [TYPE_n] tokens and a mapping is returned. You can restore() both your text and the LLM's reply. restore(redact(t).text, mapping) == t.
  • mask_char mode (one-way). Set mask_char="*" and each match becomes **** of equal length. No mapping is produced and restoration is impossible — use this only when you want to scrub data for display/logging, not round-trip it.
r = redactor.Redactor(mask_char="*")
print(r.redact("SSN 123-45-6789").text)   # SSN ***********

A note on compliance

This library is best-effort. Regex-based detection cannot catch every form of sensitive data, and matching data is not the same as understanding it. redactor is a practical safety net, not a guarantee of GDPR / HIPAA / PCI-DSS compliance or any other regulatory requirement. Review the redacted output before relying on it, and don't treat it as a substitute for proper data-handling controls.

License

MIT.

Project details


Download files

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

Source Distribution

ragkit_redactor-0.1.0.tar.gz (11.7 kB view details)

Uploaded Source

Built Distribution

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

ragkit_redactor-0.1.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for ragkit_redactor-0.1.0.tar.gz
Algorithm Hash digest
SHA256 301f7e92b6c67389114a2506423d4b2945f7a6a59387531ac98aac9893df9bc2
MD5 df371ac2150cc8d1fb9f4e44b75bd164
BLAKE2b-256 12cd009e3d2102304b391ca952b8a54e7bf8d04a9638b60878a9158233c38073

See more details on using hashes here.

Provenance

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

Publisher: publish.yml on Meet2147/pythonLibraries

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

File details

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

File metadata

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

File hashes

Hashes for ragkit_redactor-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 59ee05f184e7818dec10098c187de4206ee7ef2f6c82ecf2ec29e92acaabd1ee
MD5 b53f97dacfeb5d219a553e9434d69637
BLAKE2b-256 663818b593f0c33b8bdf096a01c32939b88ef0df122bfa7acf43f00d3800dd9e

See more details on using hashes here.

Provenance

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

Publisher: publish.yml on Meet2147/pythonLibraries

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 Pingdom Monitoring Sentry Error logging StatusPage Status page