Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Redactyl logo

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 UrlRule when you know a field is a URL and want to apply URL rules.
  • Use RegexValueRule(..., action=Action.URL, rules=...) to find URLs inside strings.
  • SCRUB always relies on the shared SecretStore for the current call or provided secrets=.

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_depth and max_items replace oversized structures with [...].
  • min_length controls which redacted values are registered for SCRUB.

Tests

uv run pytest

Release files for redactyl 0.1.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for redactyl 0.1.0a1
File Size Uploaded
redactyl-0.1.0a1.tar.gz 27.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for redactyl 0.1.0a1
File Interpreter ABI Platform
redactyl-0.1.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 41.9 kB

Release files / redactyl-0.1.0a1.tar.gz

Download URL redactyl-0.1.0a1.tar.gz
Size 27.0 kB
Tags Source
SHA-256 checksum
How to use checksums
743f855db379408a53956cca052fe59dec0d1d13a6668f008e95b0c8083aae63
BLAKE2b-256 checksum
How to use checksums
da544e2def1f85f58f1a5282c9ddab71b5ecda79d113539403c1a75a5a4697a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Apr 12, 2026.

Transparency log

Release files / redactyl-0.1.0a1-py3-none-any.whl

Download URL redactyl-0.1.0a1-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4937716c68d9710ba42d873e87ce49bb68788ba11ce21200764817d298766c01
BLAKE2b-256 checksum
How to use checksums
9e656b107f7ccf8e03606b4e3677c76d7e9988862e20136af2cde3c4af3d34fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Apr 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0a1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page