secretscreen
Detect and redact secrets in key-value pairs, dicts, and environment variables.
Best-effort defense-in-depth. Not a security boundary.
Install
As a library:
pip install secretscreen
As a command-line tool — pipx or uv keep it in its own environment and put secretscreen on your PATH everywhere, rather than only in whichever venv is active:
pipx install secretscreen
uv tool install secretscreen
Zero dependencies, Python 3.11+.
Quick start
from secretscreen import redact_pair, redact_dict, audit_dict, Mode
# Single pair
redact_pair("DB_PASSWORD", "hunter2") # → "[REDACTED]"
redact_pair("APP_NAME", "myapp") # → "myapp"
# Dict with recursion
redact_dict({"db": {"password": "x", "host": "localhost"}})
# → {"db": {"password": "[REDACTED]", "host": "localhost"}}
# Aggressive mode (adds entropy detection)
redact_dict(env, mode=Mode.AGGRESSIVE)
# Audit mode (structured findings, no mutation)
findings = audit_dict(env)
# → [Finding(key="DB_PASSWORD", reason="key_pattern:password", ...)]
# Custom safe suffixes (keys ending with these are never redacted)
redact_dict(env, safe_suffixes=("_config", "_enabled"))
Command line
The library only helps Python callers. The CLI covers the shell side — docker exec, Makefiles, CI logs, anything you are about to paste somewhere.
secretscreen tandoor.env # redact and print, cat-like
docker exec app env | secretscreen # scrub a stream before it hits your terminal
secretscreen --audit config.json # findings only, no values, exit 1 if any
secretscreen [FILE...] reads stdin when FILE is omitted or '-'
--audit report findings without values; exit 1 if any
--format env|json|ini|yaml|dsn|auto default: auto-detect from extension, then content
--aggressive add entropy detection; more false positives
--explain account on stderr for every value, including the untouched ones
--replacement TEXT default: [REDACTED]
Redaction is structural, not line-based: it parses the format, so it catches DB_PASSWORD=hunter2 on the key name and rewrites postgres://admin:s3cr3t@host/db to postgres://admin:REDACTED@host/db without destroying the rest of the line. Comments, blank lines, quoting, export prefixes, INI sections and : separators all survive the round trip.
Inside a URL the replacement drops its brackets, because [ in that position makes the result unparseable — screening already-screened output would otherwise destroy it.
Compose files and grep output
$ secretscreen compose.yaml
# Watchtower stack
services:
watchtower:
image: containrrr/watchtower
environment:
WATCHTOWER_NOTIFICATION_URL: discord://REDACTED@1234567890
WATCHTOWER_CLEANUP: "true"
# keep an eye on this one
restart: unless-stopped
Indentation, comments, and block openers (services:) survive; only values are touched. List entries are screened whether they carry a key (- DB_PASSWORD=hunter2) or not (- discord://token@id).
This is line-oriented, not a real YAML parse — a parser would mean a third-party dependency, and this package is stdlib-only on purpose. Block scalars (|, >) are the visible consequence: their bodies are not key: value, so they are redacted and reported rather than quietly passed through.
grep's file:NN: and file-NN- markers are recognised and stripped before parsing, then put back on output, so grep -rn SECRET ~/stacks | secretscreen keeps its file-and-line context:
$ grep -n 'NOTIFICATION' compose.yaml | secretscreen
secretscreen: detected grep-style line prefixes; stripped before parsing, restored on output
6: WATCHTOWER_NOTIFICATION_URL: discord://REDACTED@1234567890
That stripping is what makes colon-separated detection safe to attempt at all. Without it the first colon on the line belongs to grep's line number, the key becomes 6, and the whole remainder — secret included — becomes one value that matches nothing. So when the input is only partly prefixed and the shape is ambiguous, the sniffer refuses the colon format and falls back to env, which redacts what it cannot parse. Useless output beats quiet output.
One case stays deliberately blunt: a single stream mixing formats, such as grep -rn across both .env and .yaml files, picks the majority format and redacts the rest wholesale. Loud and safe rather than half-parsed.
Knowing what was left alone
A missed secret is invisible: the output looks screened and nothing says otherwise. --explain writes an account of every value to stderr, so stdout stays a usable redacted stream and secretscreen app.env --explain > clean.env still works.
$ secretscreen watchtower.env --explain > screened.env
secretscreen: explain — key names and reasons only, no values
redacted WATCHTOWER_NOTIFICATION_URL url_credentials credential in userinfo position
vetoed GF_OAUTH_TOKEN_URL key_pattern matched 'token', suppressed by safe suffix '_url'
clean IMAGE_DIGEST - entropy 4.14 (aggressive would not flag; threshold 4.50)
clean DEPLOY_PUBLIC_KEY - entropy 4.67 (aggressive WOULD flag; threshold 4.50)
unscanned BACKUP_BLOB - 2202009 bytes exceeds the 65536-byte scan cap
Four states. redacted and clean are self-explanatory; the two in between are the ones worth reading. vetoed means a rule fired and something suppressed it — that is where the tool decided to stay quiet. unscanned means the size cap skipped the value-scanning layers.
Clean lines carry the nearest miss rather than silence. The entropy figure is computed even in normal mode, where that layer never runs, so you can see what --aggressive would change before turning it on.
Like --audit, this never prints a value — paste it into a bug report as-is.
What the exit code means:
| Code | Meaning |
|---|---|
| 0 | Everything was parsed and screened |
| 1 | --audit found secrets |
| 2 | Something could not be parsed or read — see stderr |
That third case is the one that matters. This is best-effort defense-in-depth, and a cat-replacement is exactly the tool people stop thinking about, so the CLI never prints unparsed content verbatim: a line it cannot structure is replaced with the redaction token, named on stderr, and turns the exit code non-zero. The same applies to values above the 64 KB detection cap — they are reported as unscanned rather than passed off as clean.
If you are scanning a git repository rather than config-shaped data, use gitleaks instead. That is a different job.
Detection layers
- Key-name denylist — substring match against ~30 known secret key patterns
- Structured value parsing — JSON, Python literals, DSN, INI, URL query params
- Value-format detection — 222 known formats via vendored gitleaks patterns (MIT)
- URL credential detection — partial redaction of
user:pass@hostURLs - Entropy detection — Shannon entropy for machine-generated strings (aggressive mode only)
Contributing
Bug reports and pull requests welcome. See CONTRIBUTING.md.
Support
If you find secretscreen useful, consider buying us a coffee.
License
MIT. Gitleaks patterns are also MIT-licensed.
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 secretscreen-0.4.0.tar.gz.
File metadata
- Download URL: secretscreen-0.4.0.tar.gz
- Upload date:
- Size: 75.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
981db7def0b34f7a8f0bf1b6aafbc485177b372a38abd943d8e9116dcf3666a9
|
|
| MD5 |
ae5920a96d62a9f68b4d550ffbe44771
|
|
| BLAKE2b-256 |
5c892f5dc71aca963a2fd575cd462ea50a7398d399640722018085e5343cc202
|
Provenance
The following attestation bundles were made for secretscreen-0.4.0.tar.gz:
Publisher:
publish.yml on featurecreep-cron/secretscreen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
secretscreen-0.4.0.tar.gz -
Subject digest:
981db7def0b34f7a8f0bf1b6aafbc485177b372a38abd943d8e9116dcf3666a9 - Sigstore transparency entry: 2306951424
- Sigstore integration time:
-
Permalink:
featurecreep-cron/secretscreen@a2bee960eccae6817e18b9d122fdd4f2ba2d6270 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/featurecreep-cron
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2bee960eccae6817e18b9d122fdd4f2ba2d6270 -
Trigger Event:
release
-
Statement type:
File details
Details for the file secretscreen-0.4.0-py3-none-any.whl.
File metadata
- Download URL: secretscreen-0.4.0-py3-none-any.whl
- Upload date:
- Size: 50.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a37f6e70d05fa559a9fdcf96f6c835774e5ae580e76c7a1b02714f010004ae9
|
|
| MD5 |
0c4d819d44c8e74dd25b6f30f627268c
|
|
| BLAKE2b-256 |
650588bd49197416b0fcb2929a436b2fdf467cfe8af6d0f8195aa4111bdff63f
|
Provenance
The following attestation bundles were made for secretscreen-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on featurecreep-cron/secretscreen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
secretscreen-0.4.0-py3-none-any.whl -
Subject digest:
3a37f6e70d05fa559a9fdcf96f6c835774e5ae580e76c7a1b02714f010004ae9 - Sigstore transparency entry: 2306951483
- Sigstore integration time:
-
Permalink:
featurecreep-cron/secretscreen@a2bee960eccae6817e18b9d122fdd4f2ba2d6270 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/featurecreep-cron
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2bee960eccae6817e18b9d122fdd4f2ba2d6270 -
Trigger Event:
release
-
Statement type: