Skip to main content

Defense-in-depth redaction for Python payloads in logs, events, and nested JSON-like data

Project description

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

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

redactyl-0.1.0a1.tar.gz (27.0 kB view details)

Uploaded Source

Built Distribution

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

redactyl-0.1.0a1-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

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

Hashes for redactyl-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 743f855db379408a53956cca052fe59dec0d1d13a6668f008e95b0c8083aae63
MD5 b015ac3b39bf3213eac63f1df00a04e9
BLAKE2b-256 da544e2def1f85f58f1a5282c9ddab71b5ecda79d113539403c1a75a5a4697a4

See more details on using hashes here.

Provenance

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

Publisher: release.yml on adamcik/redactyl

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

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

Hashes for redactyl-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 4937716c68d9710ba42d873e87ce49bb68788ba11ce21200764817d298766c01
MD5 4c89ef603e2643e7953b599e910c80c8
BLAKE2b-256 9e656b107f7ccf8e03606b4e3677c76d7e9988862e20136af2cde3c4af3d34fd

See more details on using hashes here.

Provenance

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

Publisher: release.yml on adamcik/redactyl

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