Skip to main content

High-throughput secret redaction for Python, backed by Rust.

Project description

ScrubbeRS

ScrubbeRS is a Rust-first, zero-copy (in-place) redaction engine with:

  • A stdin → stdout CLI for shell pipelines.
  • A Rust library API.
  • Optional Python and Node.js bindings.
  • Built-in high-confidence detector signatures for direct redaction.
  • Optional .scrub signature files for custom org-specific patterns.

Why this is fast

  • Redaction happens in place (&mut [u8]) with byte-mask filling.
  • Literal signatures are matched with Aho-Corasick (single-pass multi-pattern automaton).
  • Regex signatures use compiled regex::bytes::Regex and run against raw bytes.
  • Release profile is tuned (lto=fat, codegen-units=1, panic=abort).

CLI usage

# Build release binary
cargo build --release

# Pipe mode (stdin -> stdout)
cat app.log | ./target/release/scrubbers > redacted.log

# With custom signatures
cat app.log | ./target/release/scrubbers --scrub-file .scrub > redacted.log

# Custom mask byte
cat app.log | ./target/release/scrubbers --mask "#" > redacted.log

# Line-oriented streaming mode for log pipelines
tail -F app.log | ./target/release/scrubbers --stream-lines

.scrub format

Each non-empty, non-comment line is either:

  1. name=regex_or_literal
  2. regex_or_literal (auto-named)

Example:

# redact internal session tokens
session_token=sess_[A-Za-z0-9]{32}

# redact literal phrase
MY_INTERNAL_SECRET_PREFIX

Rust API

use std::io::Cursor;
use scrubbers::Scrubber;

let scrubber = Scrubber::new()?;
let mut bytes = b"ghp_123456789012345678901234567890123456".to_vec();
scrubber.scrub_in_place(&mut bytes);

let mut output = Vec::new();
scrubber.scrub_lines(
    Cursor::new(b"safe\nprefix ghp_123456789012345678901234567890123456 suffix\n"),
    &mut output,
)?;

Python bindings

Install from PyPI once published:

uv add scrubbers

Build Python distributions locally with uv:

uv build

On Linux, a local uv build wheel is useful for smoke testing but is not guaranteed to be PyPI-uploadable. The release workflow builds Linux wheels in a manylinux container.

Exposed functions:

  • scrubbers.scrub_bytes(data: bytes) -> bytes
  • scrubbers.scrub_text(data: str) -> str
  • scrubbers.scrub_lines_bytes(data: bytes) -> bytes
  • scrubbers.scrub_lines_text(data: str) -> str

The scrub_lines_* helpers apply the library's newline-delimited streaming path over the provided input.

Example:

import scrubbers

scrubbers.scrub_text("prefix ghp_123456789012345678901234567890123456 suffix")
# "prefix **************************************** suffix"

scrubbers.scrub_lines_text("safe\nprefix ghp_123456789012345678901234567890123456 suffix\n")
# "safe\nprefix **************************************** suffix\n"

Smoke test the built wheel and sdist locally:

python3 scripts/test_python_package.py --artifact all

You can still exercise the raw extension crate directly with:

cargo build --release --manifest-path bindings/python/Cargo.toml

Node.js bindings

Build the Node extension crate:

cargo build --release --manifest-path bindings/node/Cargo.toml

Exposed functions:

  • scrubBuffer(buf: Buffer) -> Buffer
  • scrubLinesBuffer(buf: Buffer) -> Buffer

scrubLinesBuffer(...) applies the library's newline-delimited streaming path over the provided buffer.

Example:

const { scrubBuffer, scrubLinesBuffer } = require("./scrubbers.node");

scrubBuffer(Buffer.from("prefix ghp_123456789012345678901234567890123456 suffix"))
// <Buffer 70 72 65 66 69 78 20 2a ...>

scrubLinesBuffer(
  Buffer.from("safe\nprefix ghp_123456789012345678901234567890123456 suffix\n", "utf8"),
).toString("utf8");
// "safe\nprefix **************************************** suffix\n"

Run binding smoke tests locally:

python3 scripts/test_bindings.py --binding all

Verify the publishable Python package locally:

python3 scripts/test_python_package.py --artifact all

Benchmark the Python binding in a logging-style path:

python3 scripts/bench_python_bindings.py

Publishing

Release publishing is tag-driven through publish.yml:

python3 scripts/release.py

Dry run the release flow first:

python3 scripts/release.py --dry-run --verbose

That workflow:

  • builds the Linux wheel in a manylinux2014 container and smoke tests it before upload
  • builds and smoke tests Python wheels on macOS and Windows with uv build
  • builds and smoke tests a source distribution
  • publishes Python distributions to PyPI with Trusted Publishing
  • verifies and publishes the scrubbers crate to crates.io

Local preflight checks:

cargo publish --dry-run --locked --package scrubbers
python3 scripts/test_python_package.py --artifact all

scripts/release.py reads the version from the Cargo manifests, checks for tracked local changes, switches to main, pulls fast-forward from origin, pushes main, creates v<version>, and pushes the tag.

Before the release workflow can publish, configure trusted publishers on both registries:

  • PyPI: add the GitHub repository/workflow as a trusted publisher for the scrubbers project and create the pypi environment.
  • crates.io: publish the crate manually once, then add this repository/workflow as a trusted publisher for scrubbers and create the crates-io environment.

TruffleHog parity workflow

TruffleHog detector coverage is tracked in src/generated_trufflehog.rs:

python scripts/sync_trufflehog_signatures.py
go run ./scripts/sync_trufflehog_pattern_fixtures.go
python scripts/verify_trufflehog_coverage.py

CI runs these commands and fails if:

  • any upstream detector directory is missing from our generated signature surface, or
  • generated signatures are missing when tests run.
  • extracted positive fixtures are missing when tests run.

The generated TruffleHog data is tracked for parity and audit purposes, but it is not applied by default as raw redaction rules. Many upstream detectors rely on keyword gating and verifier callbacks, and running their extracted regexes directly creates false positives.

src/generated_trufflehog.rs is treated as a parity inventory, not a public API surface. Generated signature names are content-addressed hashes of the pattern data, so reordering upstream extraction no longer renumbers the whole file.

The extracted positive fixtures are also used in the Rust test suite as inline redaction cases. Each case builds literal secret fragments from the upstream positive example and asserts the scrubber preserves length while masking the matched spans in place.

Benchmark

Run the native Criterion benchmark:

cargo bench --bench throughput -- --noplot

It generates a 64 MiB synthetic payload, injects multiple secret shapes, and compares:

  • raw memcpy
  • straight std::io::copy pass-through into a fixed buffer
  • scrubber/in_place
  • scrubber/stream_lines

For a quick single-number smoke run, you can still use:

cargo run --release --bin scrub-bench

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

scrubbers-0.1.1.tar.gz (128.3 kB view details)

Uploaded Source

Built Distributions

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

scrubbers-0.1.1-cp39-abi3-win_amd64.whl (650.8 kB view details)

Uploaded CPython 3.9+Windows x86-64

scrubbers-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (839.1 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

scrubbers-0.1.1-cp39-abi3-macosx_11_0_arm64.whl (703.4 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

File details

Details for the file scrubbers-0.1.1.tar.gz.

File metadata

  • Download URL: scrubbers-0.1.1.tar.gz
  • Upload date:
  • Size: 128.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for scrubbers-0.1.1.tar.gz
Algorithm Hash digest
SHA256 eca022b3f0755c246e968d35d42d9e0fe05800c2518c7dfdcf0487578abd30c0
MD5 6dc2de6a302c5817f10cea7586f6e5bc
BLAKE2b-256 f74365542493fb23018e21748234ada1fb8ee1263af7a1fd4a15f384b90c9bba

See more details on using hashes here.

Provenance

The following attestation bundles were made for scrubbers-0.1.1.tar.gz:

Publisher: publish.yml on zmackie/ScrubbeRS

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

File details

Details for the file scrubbers-0.1.1-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: scrubbers-0.1.1-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 650.8 kB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for scrubbers-0.1.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 803a15f6c19181a6e42b37c766375bf4639ff666861c3460dfc58fe6d48790d7
MD5 30ce0a77503ca08eb5b43454a32410a0
BLAKE2b-256 f0917cc4b7af58e699e0ab3cc7d66e27836fca98c0c6d85b72bafc438f454120

See more details on using hashes here.

Provenance

The following attestation bundles were made for scrubbers-0.1.1-cp39-abi3-win_amd64.whl:

Publisher: publish.yml on zmackie/ScrubbeRS

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

File details

Details for the file scrubbers-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for scrubbers-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d0d876e61d41870ddab4634a02d92792102f0dda9c064b742231a15ad8aaf291
MD5 b6f7695bafd9b74c660e31e003a68194
BLAKE2b-256 af28cda699d89448e15c48cf3229004bed6aec2b8d68779c14dd9826933fa1a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for scrubbers-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on zmackie/ScrubbeRS

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

File details

Details for the file scrubbers-0.1.1-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for scrubbers-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f6f0afffccd0f92f1fbb3e451a4b2b3adc0b8a3bca57b21e3929f490397e9312
MD5 36ca19476b1ae4d7bf49b540ff68ab0f
BLAKE2b-256 36c84f7f9c12d1be4bd83ef7e6a45e42f98e0f9c89658124588737c79dc6e93b

See more details on using hashes here.

Provenance

The following attestation bundles were made for scrubbers-0.1.1-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on zmackie/ScrubbeRS

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