Skip to main content

Pure-function helpers for redacting structured payloads.

Project description

payload-redactor

Pure-function helpers for redacting sensitive data in structured payloads. Deterministic key-based payload redaction (not PII detection). Designed as a small, composable core rather than a framework-centric solution.

Install

pip install payload-redactor

Usage

from payload_redactor import make_redactor, redact, redact_with

payload = {"password": "secret", "user": "alice"}
print(redact(payload))
print(redact(payload, replacement="<hidden>"))

redactor = make_redactor(replacement="###")
print(redactor(payload))

Output:

{'password': '***', 'user': 'alice'}
{'password': '<hidden>', 'user': 'alice'}
{'password': '###', 'user': 'alice'}

Custom replacement per key:

from payload_redactor import redact

payload = {"password": "secret", "token": "abc"}
redacted = redact(
    payload,
    replacement="<hidden>",
    key_replacements={"password": "***"},
)

Output:

{'password': '***', 'token': '<hidden>'}

Examples

Dict/list payload (10 lines):

from payload_redactor import redact
payload = {
    "user": "alice",
    "password": "secret",
    "headers": ["authorization", "Bearer abc"],
    "nested": {"token": "t-123"},
}
redacted = redact(payload)
print(redacted["password"], redacted["headers"][1])
print(redacted["nested"]["token"])

Output:

*** ***
***

Structured logging event dict (10 lines):

from payload_redactor import redact_event_dict
event_dict = {
    "event": "user login",
    "user_id": 123,
    "password": "secret",
    "meta": {"api_key": "k-1"},
}
redacted = redact_event_dict(None, None, event_dict)
print(redacted["password"])
print(redacted["meta"]["api_key"])

Output:

[REDACTED]
[REDACTED]

String redaction behavior (10 lines):

from payload_redactor import redact
message = "password=secret token=abc"
print(redact(message))
message = "no secrets here"
print(redact(message))
message = "authorization bearer abc"
print(redact(message))
message = "tokenization is not a match"
print(redact(message))
print(redact("dsn=https://key@host/1"))

Output:

***=*** ***=abc
no secrets here
*** bearer abc
tokenization is not a match
dsn=https://***@host/1

Policy configuration

from payload_redactor import Policy, redact

policy = Policy(
    sensitive_keywords=["password", "token"],
    key_replacements={"password": "***"},
    string_rules=[r"Bearer\s+\S+"],
    path_rules=[("user", "email")],
)
payload = {"user": {"email": "alice@example.com"}, "auth": "Bearer abc"}
print(redact(payload, policy=policy, replacement="[REDACTED]"))

Output:

{'user': {'email': '[REDACTED]'}, 'auth': '[REDACTED]'}

Non-goals

  • This does not detect PII entities; it redacts based on keys/patterns.
  • This does not classify data or infer sensitivity from values.

Guarantees

  • Deterministic output for the same input and configuration.
  • No mutation of input dict/list/string payloads.
  • No dependencies in the core redaction module.
  • Type preservation for dict/list/string inputs; other types are returned as-is.

Common gotchas

Authorization headers and cookie jars often arrive as pairs or dicts:

from payload_redactor import redact
headers = ["authorization", "Bearer abc"]
cookies = {"cookie": "session=secret; csrftoken=abc"}
print(redact(headers))
print(redact(cookies))

Output:

['authorization', '***']
{'cookie': 'session=***; csrftoken=abc'}

JWTs and DSNs are not detected unless the key matches:

from payload_redactor import redact
payload = {"token": "jwt-value", "dsn": "https://key@host/1"}
redacted = redact(payload, sensitive_keywords=["token", "dsn"])
print(redacted["token"], redacted["dsn"])

Output:

*** ***

Structlog adapter (optional)

Install with the extra:

python -m pip install .[structlog]
import logging
import logging.config

import structlog

from payload_redactor import redact_event_dict


shared_processors = [
    structlog.stdlib.add_logger_name,
    structlog.stdlib.add_log_level,
    structlog.processors.TimeStamper(fmt="iso"),
    structlog.processors.UnicodeDecoder(),
]

logging.config.dictConfig(
    {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "console": {
                "()": structlog.stdlib.ProcessorFormatter,
                "processor": structlog.dev.ConsoleRenderer(colors=True),
                "foreign_pre_chain": shared_processors,
            },
            "json": {
                "()": structlog.stdlib.ProcessorFormatter,
                "processor": structlog.processors.JSONRenderer(sort_keys=True),
                "foreign_pre_chain": shared_processors,
            },
        },
        "handlers": {
            "default": {
                "level": "DEBUG",
                "class": "logging.StreamHandler",
                "formatter": "json",
            }
        },
        "loggers": {"": {"handlers": ["default"], "level": "INFO"}},
    }
)

structlog.configure(
    processors=[
        redact_event_dict,
        *shared_processors,
        structlog.stdlib.ProcessorFormatter.wrap_for_formatter,  # type: ignore
    ],
    logger_factory=structlog.stdlib.LoggerFactory(),
    cache_logger_on_first_use=True,
)

logger = structlog.get_logger("app")
logger.info("user login", user_id=123, password="secret")

Output (JSON formatter):

{"event": "user login", "level": "info", "logger": "app", "password": "[REDACTED]", "timestamp": "2024-01-01T12:00:00Z", "user_id": 123}

Sentry adapter (optional)

Install with the extra:

python -m pip install .[sentry]
import sentry_sdk

from payload_redactor import redact_sentry_before_send

sentry_sdk.init(
    dsn="https://examplePublicKey@o0.ingest.sentry.io/0",
    before_send=redact_sentry_before_send,
)

Stdlib logging adapter (optional)

This adapter has no dependencies; it provides a logging.Filter that redacts LogRecord.msg, LogRecord.args, and any extra fields in-place.

import logging

from payload_redactor import make_redacting_filter

logger = logging.getLogger("app")
logger.addFilter(make_redacting_filter())

logger.info({"password": "secret", "user": "alice"})
logger.info("password=%s", "secret", extra={"token": "t-123"})

Development

python -m venv .venv
. .venv/bin/activate
python -m pip install -U pip
python -m pip install -e ".[dev]"
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

payload_redactor-0.3.0.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

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

payload_redactor-0.3.0-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

File details

Details for the file payload_redactor-0.3.0.tar.gz.

File metadata

  • Download URL: payload_redactor-0.3.0.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for payload_redactor-0.3.0.tar.gz
Algorithm Hash digest
SHA256 d6749b3d876368bb715179f1aa1c04733fb416447a89d768d970d0dbc1c4f0a6
MD5 de74342bbe8395299a1ee3e2192e6c8c
BLAKE2b-256 256c1ad204625bba408225ed81351b621f38758daa3526443d800212a1660726

See more details on using hashes here.

File details

Details for the file payload_redactor-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for payload_redactor-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ba584f4c9c2e373507c26574455742f5efffe24092c9bf98ecc08f4e2df51a00
MD5 e2e15941a67bd4d02155247eb546f9ca
BLAKE2b-256 630c7d81b4903d73589cd8ddcbd62b07ef65adc25e43418ce28d3d039f08c63f

See more details on using hashes here.

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