Skip to main content

secretscreen

CI Codecov OpenSSF Scorecard License: MIT Python PyPI Ruff

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

  1. Key-name denylist — substring match against ~30 known secret key patterns
  2. Structured value parsing — JSON, Python literals, DSN, INI, URL query params
  3. Value-format detection — 222 known formats via vendored gitleaks patterns (MIT)
  4. URL credential detection — partial redaction of user:pass@host URLs
  5. 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

secretscreen-0.4.0.tar.gz (75.3 kB view details)

Uploaded Source

Built Distribution

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

secretscreen-0.4.0-py3-none-any.whl (50.1 kB view details)

Uploaded Python 3

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

Hashes for secretscreen-0.4.0.tar.gz
Algorithm Hash digest
SHA256 981db7def0b34f7a8f0bf1b6aafbc485177b372a38abd943d8e9116dcf3666a9
MD5 ae5920a96d62a9f68b4d550ffbe44771
BLAKE2b-256 5c892f5dc71aca963a2fd575cd462ea50a7398d399640722018085e5343cc202

See more details on using hashes here.

Provenance

The following attestation bundles were made for secretscreen-0.4.0.tar.gz:

Publisher: publish.yml on featurecreep-cron/secretscreen

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

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

Hashes for secretscreen-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3a37f6e70d05fa559a9fdcf96f6c835774e5ae580e76c7a1b02714f010004ae9
MD5 0c4d819d44c8e74dd25b6f30f627268c
BLAKE2b-256 650588bd49197416b0fcb2929a436b2fdf467cfe8af6d0f8195aa4111bdff63f

See more details on using hashes here.

Provenance

The following attestation bundles were made for secretscreen-0.4.0-py3-none-any.whl:

Publisher: publish.yml on featurecreep-cron/secretscreen

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

Release history Release notifications | RSS feed

0.5.1

2 files

0.5.0

2 files

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page