Defense-in-depth redaction for Python payloads in logs, events, and nested JSON-like data
Project description
Redactyl
Defense-in-depth redaction and scrubbing for Python structured payloads.
Redactyl is a Python library for removing sensitive data from structured logs, error payloads, Sentry events, and other nested JSON-like dict/list payloads. It is built for real-world payloads where the full shape is not known ahead of time. You define layered rules and heuristics, then Redactyl mutates payloads in-place.
REDACT replaces the matched value directly. SCRUB removes previously seen
secrets from surrounding text using a shared SecretStore.
Features
- Built for Python services that process logs, events, and nested payloads
- In-place mutation of dict/list payloads with no schema requirement
- Path-based rules with wildcards (
PathRule("user.*", ...)) - Regex rules for paths and string values (
RegexPathRule,RegexValueRule) - Key token matching across snake_case and camelCase (
SubstringRule) - URL-aware rewriting for query params, userinfo, and fragments (
UrlRule,Action.URL) - Actions for different handling strategies:
REDACT,SCRUB,DROP,SAFE,URL,HASH - Optional limits (
max_depth,max_items) to cap large payload traversal
Install
This project uses uv for dependency management.
uv sync
Quick start
from redactyl import Action, PathRule, SubstringRule, build_redactor
rules = [
PathRule("user.password", Action.REDACT),
SubstringRule(tokens=frozenset({"token"}), action=Action.REDACT),
]
redactor = build_redactor(rules)
payload = {
"user": {"password": "hunter2"},
"id_token": "secret",
"message": "token=hunter2",
}
redactor(payload)
print(payload)
API taxonomy
The API has two primary concepts:
- Rules: match a path or value and return a
RuleDecision. - Actions: what to do when a rule matches (replace, scrub, drop, stop).
Common taxonomy:
- Path-based rules (
PathRule,RegexPathRule,UrlRule) match structured payload paths. - Value-based rules (
RegexValueRule) match and transform string content. - Token rules (
SubstringRule) match key names by tokenizing camel/snake case.
Notes:
- Prefer
UrlRulewhen you know a field is a URL and want to apply URL rules. - Use
RegexValueRule(..., action=Action.URL, rules=...)to find URLs inside strings. SCRUBalways relies on the sharedSecretStorefor the current call or providedsecrets=.
Cookbook
Examples are independent. Copy the snippet you need.
import re
from redactyl import Action, PathRule, RegexValueRule, SubstringRule, UrlRule, build_redactor
from redactyl.secrets import SecretStore
# Redact known sensitive fields
redactor = build_redactor(
[
PathRule("user.password", Action.REDACT),
PathRule("auth.token", Action.REDACT),
]
)
# Redact by key token (camel/snake)
redactor = build_redactor(
[
SubstringRule(tokens=frozenset({"token", "secret"}), action=Action.REDACT),
]
)
# Scrub previously seen secrets from a message
redactor = build_redactor(
[
PathRule("user.password", Action.REDACT),
PathRule("message", Action.SCRUB),
]
)
# Share secrets across multiple payloads in a request lifecycle
secrets = SecretStore()
redactor = build_redactor(
[
PathRule("secret", Action.REDACT),
PathRule("message", Action.SCRUB),
]
)
redactor({"secret": "token", "message": "token"}, secrets=secrets)
redactor({"message": "token"}, secrets=secrets)
# Redact URL params in a known URL field
redactor = build_redactor(
[
UrlRule(
"url",
(
PathRule("params.token", Action.REDACT),
PathRule("params.password", Action.REDACT),
),
),
]
)
# Redact sensitive param names inside URLs found in text
url_rules = (
SubstringRule(tokens=frozenset({"token", "apikey", "password"}), action=Action.REDACT),
PathRule("userinfo.*", Action.REDACT),
PathRule("fragment", Action.REDACT),
)
redactor = build_redactor(
[
RegexValueRule(
pattern=re.compile(r"https?://\S+"),
action=Action.URL,
rules=url_rules,
),
]
)
# Scrub URL params using previously seen secrets
redactor = build_redactor(
[
PathRule("secret", Action.REDACT),
UrlRule("url", (PathRule("params.*", Action.SCRUB),)),
RegexValueRule(
pattern=re.compile(r"https?://\S+"),
action=Action.URL,
rules=(PathRule("params.*", Action.SCRUB),),
),
]
)
Secret store lifetime
By default, each redactor(...) call uses a fresh mutable secret store, so newly seen secrets do not leak across calls. You can also pass your own SecretStore to persist secrets for a request lifecycle.
Important limitation: if a later call sees a new secret that should have been scrubbed in an earlier call, it will not be scrubbed retroactively. Avoid relying on cross-call scrubbing for newly discovered values.
from redactyl import Action, Options, PathRule, build_redactor
from redactyl.secrets import SecretStore
base = SecretStore()
base.add('seed', '[REDACTED]')
options = Options(base_secrets_store=base)
redactor = build_redactor(
[
PathRule("secret", Action.REDACT),
PathRule("message", Action.SCRUB),
],
options=options,
)
request_store = SecretStore()
payload = {"secret": "token", "message": "seed token"}
redactor(payload, secrets=request_store)
Rules and actions
PathRule
Matches dot-delimited paths with optional * wildcards.
from redactyl import Action, PathRule
PathRule("user.password", Action.REDACT)
PathRule("user.*", Action.REDACT)
RegexPathRule
Matches a path using a regular expression.
from redactyl import Action, RegexPathRule
RegexPathRule(pattern=r"\.secret$", action=Action.REDACT)
SubstringRule
Matches key tokens derived from camelCase, snake_case, and alphanumerics.
from redactyl import Action, SubstringRule
SubstringRule(tokens=frozenset({"token"}), action=Action.REDACT)
RegexValueRule
Matches string values by regex and applies the action to the match.
from redactyl import Action, RegexValueRule
RegexValueRule(
pattern=r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}",
action=Action.REDACT,
)
URL rules
Use UrlRule to parse a URL string and apply rules to its parts.
from redactyl import Action, PathRule, UrlRule, build_redactor
redactor = build_redactor(
[
UrlRule("url", (PathRule("params.token", Action.REDACT),)),
]
)
preset = build_redactor(
[
UrlRule(
"url",
(
PathRule("userinfo.user", Action.REDACT),
PathRule("userinfo.password", Action.REDACT),
PathRule("fragment", Action.REDACT),
PathRule("params.*", Action.REDACT),
),
),
]
)
Actions
REDACT: Replace with the configured replacement string (default[REDACTED]).SCRUB: Replace any previously-seen secrets within the value.DROP: Remove the key from the payload.SAFE: Stop rule processing and skip children.URL: Parse and redact URL components using default rules.HASH: Replace a string with a short hash digest.
Presets
Common entry points for structured logging and error reporting.
from redactyl import (
sentry_before_send_redactor,
sentry_breadcrumb_redactor,
structlog_redactor,
PathRule,
Action,
)
rules = [PathRule("user.password", Action.REDACT)]
before_send = sentry_before_send_redactor(rules)
breadcrumb = sentry_breadcrumb_redactor(rules)
structlog = structlog_redactor(rules)
Options
Configure behavior via Options:
from redactyl import Options, build_redactor
options = Options(
replacement="[REDACTED]",
min_length=4,
max_depth=None,
max_items=None,
hash_secret=b"secret",
hash_length=10,
)
redactor = build_redactor([], options=options)
Notes:
max_depthandmax_itemsreplace oversized structures with[...].min_lengthcontrols which redacted values are registered forSCRUB.
Tests
uv run pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file redactyl-0.1.0a1.tar.gz.
File metadata
- Download URL: redactyl-0.1.0a1.tar.gz
- Upload date:
- Size: 27.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
743f855db379408a53956cca052fe59dec0d1d13a6668f008e95b0c8083aae63
|
|
| MD5 |
b015ac3b39bf3213eac63f1df00a04e9
|
|
| BLAKE2b-256 |
da544e2def1f85f58f1a5282c9ddab71b5ecda79d113539403c1a75a5a4697a4
|
Provenance
The following attestation bundles were made for redactyl-0.1.0a1.tar.gz:
Publisher:
release.yml on adamcik/redactyl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redactyl-0.1.0a1.tar.gz -
Subject digest:
743f855db379408a53956cca052fe59dec0d1d13a6668f008e95b0c8083aae63 - Sigstore transparency entry: 1280908829
- Sigstore integration time:
-
Permalink:
adamcik/redactyl@24fd363a1d309ffdeb138c6949a0635e42247705 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/adamcik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@24fd363a1d309ffdeb138c6949a0635e42247705 -
Trigger Event:
release
-
Statement type:
File details
Details for the file redactyl-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: redactyl-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 14.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4937716c68d9710ba42d873e87ce49bb68788ba11ce21200764817d298766c01
|
|
| MD5 |
4c89ef603e2643e7953b599e910c80c8
|
|
| BLAKE2b-256 |
9e656b107f7ccf8e03606b4e3677c76d7e9988862e20136af2cde3c4af3d34fd
|
Provenance
The following attestation bundles were made for redactyl-0.1.0a1-py3-none-any.whl:
Publisher:
release.yml on adamcik/redactyl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redactyl-0.1.0a1-py3-none-any.whl -
Subject digest:
4937716c68d9710ba42d873e87ce49bb68788ba11ce21200764817d298766c01 - Sigstore transparency entry: 1280908832
- Sigstore integration time:
-
Permalink:
adamcik/redactyl@24fd363a1d309ffdeb138c6949a0635e42247705 -
Branch / Tag:
refs/tags/v0.1.0a1 - Owner: https://github.com/adamcik
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@24fd363a1d309ffdeb138c6949a0635e42247705 -
Trigger Event:
release
-
Statement type: