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, thenimport 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 ofMatchwith placeholders assigned..mapping—{placeholder: original_value}(empty inmask_charmode)..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 amappingis returned. You canrestore()both your text and the LLM's reply.restore(redact(t).text, mapping) == t. mask_charmode (one-way). Setmask_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.
Metadata
Release files for ragkit-redactor 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ragkit_redactor-0.1.1.tar.gz | 11.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ragkit_redactor-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.7 kB
Release files / ragkit_redactor-0.1.1.tar.gz
| Download URL | ragkit_redactor-0.1.1.tar.gz |
|---|---|
| Size | 11.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d6b82c014abc34d5691b390e1317e95661d127ebb35ddc697db85f83c7545041
|
|
BLAKE2b-256 checksum How to use checksums |
10750cc6d0ab000660ccd2f55e42bb83a63a708ee34ad92947ced9a71f2582ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 Jul 25, 2026.
Transparency logRelease files / ragkit_redactor-0.1.1-py3-none-any.whl
| Download URL | ragkit_redactor-0.1.1-py3-none-any.whl |
|---|---|
| Size | 11.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1d38054a3d5ac2240cc92a7fd9c6a105a8fcfe995b1a24060a6a294a4ba4d4e7
|
|
BLAKE2b-256 checksum How to use checksums |
e804e15fbd2c275e260bc265c48e72c28f1a41ce00b5adbe30beb23dac9877dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.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 Jul 25, 2026.
Transparency log