Deterministically verify that your redactors actually redact.
Project description
scrufflehog
Unit-test your redaction.
Everyone scans for secrets that already leaked (trufflehog, gitleaks). Almost nobody tests whether the redaction they rely on actually works. scrufflehog is the inverse tool: it runs adversarial probes through your own redaction code and deterministically asserts the secret is gone — and checks that your field denylist/allow-list covers the sensitive names you think it does.
No model, no guessing: every verdict is a hard assertion against a planted secret you control. Zero false positives by construction.
Two things it checks
1. Transform-strength — does the redactor's output still contain, or trivially reverse to, the secret? It executes your redactor on planted probes and applies three oracles:
literal_survival— the secret appears verbatim in the output.noop_passthrough— the "redactor" returned its input unchanged.reversible— the output is a keyless, low-entropy transform (truncated or unsalted hash, base64, static substitution) that a bounded candidate space recovers. Catches "redaction" that only looks redacted.
2. Coverage — is every sensitive field name actually on your list? A field
denylist/allow-list is data, not behaviour, so scrufflehog extracts it straight
from source and checks a sensitive-field corpus against it — without executing
your code. This works across languages (Go maps, Python collections,
pino/fast-redact path lists, Rust sets).
Languages
| transform-strength | coverage | |
|---|---|---|
| Python | in-process import | ✓ |
| Go | driver built in your module | ✓ (map literals) |
| Rust | cargo --example driver |
✓ (set literals) |
| Node/JS | node driver via stdin | ✓ (pino path lists) |
Install
Latest from source (works today):
pip install git+https://github.com/seanturner83/scrufflehog
Once the first release is published, from PyPI:
pip install scrufflehog
Use
Write a scrufflehog.toml declaring your redactors (see examples/):
[[transform]]
lang = "python"
module = "app/redact.py"
fn = "redact_value"
kind = "value"
[[coverage]]
module = "app/redact.py"
symbol = "SECRET_FIELDS"
extract = "py_collection"
match = "exact_ci"
Then:
scrufflehog verify --config scrufflehog.toml --target . --format text
scrufflehog verify --config scrufflehog.toml --target . --format sarif # for code-scanning
scrufflehog verify --config scrufflehog.toml --target . --fail-on-defect # CI gate
Deterministic by default; optional agentic assist
The core is entirely deterministic and that's the point. An optional advisor
(--advisor llm) can propose domain-matched probes, discover redactors, and
confirm coverage hypotheses against real field usage — but it only ever supplies
inputs and hypotheses; the deterministic oracle still renders every verdict.
With no advisor, output is fully reproducible. See docs/AGENTIC.md.
Why "scrufflehog"
trufflehog finds the secrets. scrufflehog scruffs through the code that's supposed to hide them and checks it actually does.
License
MIT.
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 scrufflehog-0.1.1.tar.gz.
File metadata
- Download URL: scrufflehog-0.1.1.tar.gz
- Upload date:
- Size: 27.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
497d6d3b6b73651f18bb797a6f7766b00b940894acd9131484bd0cbd3ffce2f0
|
|
| MD5 |
b232d190046b239705833e084559afe8
|
|
| BLAKE2b-256 |
cbb18a13893ab0e3e1cc939f258ee528a37de30e59cf193eeb04daaf51e4b4dc
|
Provenance
The following attestation bundles were made for scrufflehog-0.1.1.tar.gz:
Publisher:
release.yml on seanturner83/scrufflehog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrufflehog-0.1.1.tar.gz -
Subject digest:
497d6d3b6b73651f18bb797a6f7766b00b940894acd9131484bd0cbd3ffce2f0 - Sigstore transparency entry: 2063537881
- Sigstore integration time:
-
Permalink:
seanturner83/scrufflehog@91f02d27a95466ef2becc6df732395ffc80f97a7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/seanturner83
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@91f02d27a95466ef2becc6df732395ffc80f97a7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file scrufflehog-0.1.1-py3-none-any.whl.
File metadata
- Download URL: scrufflehog-0.1.1-py3-none-any.whl
- Upload date:
- Size: 28.6 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 |
a1a78693306cc1bedbce0db921f94acc3a3d70e2897e25726d8e1f668ebaa620
|
|
| MD5 |
cc3d170ab1e199715262983eac355ea5
|
|
| BLAKE2b-256 |
de36973a33d859cd320460f6e31e48ab9a8f38f15cf083caa9bcbfbc421ed87c
|
Provenance
The following attestation bundles were made for scrufflehog-0.1.1-py3-none-any.whl:
Publisher:
release.yml on seanturner83/scrufflehog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrufflehog-0.1.1-py3-none-any.whl -
Subject digest:
a1a78693306cc1bedbce0db921f94acc3a3d70e2897e25726d8e1f668ebaa620 - Sigstore transparency entry: 2063537898
- Sigstore integration time:
-
Permalink:
seanturner83/scrufflehog@91f02d27a95466ef2becc6df732395ffc80f97a7 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/seanturner83
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@91f02d27a95466ef2becc6df732395ffc80f97a7 -
Trigger Event:
release
-
Statement type: