Credactor
Find the secret. Fix it. Commit clean.
Most secret scanners stop at the alert: you get a list of leaked credentials and the cleanup is yours. Credactor finds hardcoded secrets and rewrites them in place, so a leak in your working tree goes from found to fixed in one command. It runs on your machine, as a pre-commit hook, or as a CI gate, and it can also fix what Gitleaks, TruffleHog or Betterleaks find.
# Before
db_password = "h8Tq2vKp9mRz4Wd"
# credactor --fix-all . (default: a sentinel that fails loudly at runtime)
db_password = "REDACTED_BY_CREDACTOR"
# credactor --replace-with env --fix-all . (read it from the environment)
db_password = os.environ["DB_PASSWORD"]
Redaction cleans your working tree. It does not revoke the credential and does not touch git history. If a secret was ever committed or shared, rotate it, and rewrite history if you need it gone from there too (for example with
git filter-repo).
Why Credactor
- It fixes, not just flags. Each finding is replaced in place with a sentinel, your own marker, or an environment-variable lookup in the file's own language.
- Careful writes. Each edit is planned in memory, backed up first (mode
0600), written to a temp file and renamed into place. A file that changed during the run, a path that leaves the scan root, or a private key block is skipped with a warning, and its finding stays reported. - Works with the scanner you already run. Feed it a Gitleaks, TruffleHog or Betterleaks report and it fixes their findings and its own in one deduplicated pass.
- Built for pipelines. A read-only
--cigate with clear exit codes, SARIF 2.1.0 with line and column locations for GitHub Code Scanning, a GitHub Action and a pre-commit hook. Secrets are masked in every report format, and text output is cleaned so a scanned file cannot inject escape sequences or CI workflow commands into your terminal or log. - Releases checked against their source. Pure Python 3.11+ standard
library with no runtime dependencies. Before a release reaches PyPI, every
Credactor file in the wheel and sdist is compared byte for byte with the
tagged source, and the package metadata must match
pyproject.toml(see Supply-chain hardening).
Where it is not the right tool on its own: its built-in rules cover the credential formats that leak most often rather than hundreds of providers, and it does not check whether a secret is live. A clean run means nothing matched its rules, not that the code holds no secrets. Pair it with a dedicated scanner for breadth (see Bring your own scanner).
Install
pip install credactor
With uv, install it as a tool, or run it once without installing:
uv tool install credactor
uvx credactor --dry-run .
Or with Homebrew on macOS and Linux:
brew install rxb06/tap/credactor
Needs Python 3.11 or newer. Tested in CI on Linux and Windows, Python 3.11 to
3.13. For files in encodings other than UTF-8, Latin-1 or simple UTF-16, add
the optional extra: pip install 'credactor[encoding]' or
uv tool install 'credactor[encoding]' (the Homebrew formula includes it).
Quick start
1. See what it finds. Nothing is changed.
credactor --dry-run .
======================================================================
CREDENTIAL SCAN REPORT -- 2 finding(s) in 1 file(s)
======================================================================
FILE: config.py
────────────────────────────────────────────────────────────
Line 3 [HIGH] [variable:db_password]
db_password = "h8Tq[REDACTED]"
Line 4 [CRITICAL] [pattern:AWS access key]
aws_key = "AKIA[REDACTED]"
Reports show only the first four characters of each secret.
2. Fix them. Review the findings first: --fix-all rewrites every one,
false positives included. To keep one, add # credactor:ignore to its line
(see Suppress false positives) or fix
interactively and answer n. Before each file is changed, a .bak copy is
written beside it so you can undo.
credactor . # ask y/N for each finding (needs a terminal)
credactor --fix-all . # fix all after one confirmation
credactor --fix-all --yes . # fix all, no prompt (scripts)
credactor --replace-with env --fix-all . # environment lookups instead of the sentinel
Env mode writes the lookup, not the import: add import os or your language's
equivalent if the file lacks it. On Windows Git Bash, run the interactive mode
as winpty credactor ..
3. Check, then clean up the backups. Each .bak still holds the original
secret, and Credactor never edits your .gitignore, so a plain git add .
would stage it.
diff config.py.bak config.py # check what changed
cp config.py.bak config.py # undo
Add *.bak and *.credactor.tmp to .gitignore and delete the backups once
you are happy. To keep backups out of the tree from the start, pass
--secure-backup-dir DIR (a directory outside the repository) on the fix run.
--secure-delete wipes each backup instead, which leaves git as your only
undo.
Commands at a glance
| Command | What it does | Changes files |
|---|---|---|
credactor --dry-run . |
Report findings | No |
credactor . |
Review and fix one finding at a time | Yes, on y |
credactor --fix-all . |
Fix every finding after one confirmation | Yes |
credactor --ci . |
Gate: exit 1 on findings | Never |
credactor --staged --ci . |
Gate only what is staged for commit | Never |
credactor --scan-history . |
Scan the last 100 file-changing commits | Never |
credactor --ci -f sarif . > results.sarif |
SARIF 2.1.0 for Code Scanning | Never |
credactor --ci -f json . > findings.json |
Machine-readable report | Never |
Every flag and combination is in the Manual.
Use it in pre-commit and CI
Pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/rxb06/credactor
rev: v2.7.5
hooks:
- id: credactor
pre-commit install
Every git commit then scans the staged content and is blocked if it holds a
credential. The hook never rewrites files. It needs pre-commit 3.2.0 or newer;
without the framework, see the
standalone hook.
GitHub Action
steps:
- uses: actions/checkout@v7
- uses: rxb06/credactor@v2.7.5
The Action always runs with --ci, so it never rewrites the checkout. Findings
fail the step, and so does any error. To report to Code Scanning instead of
failing:
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v7
- uses: rxb06/credactor@v2.7.5
with:
format: sarif
upload-sarif: true
fail-on-findings: false
The CI integration guide lists every Action input and output and covers GitLab CI and plain shell steps.
Bring your own scanner
Already running Gitleaks, TruffleHog or Betterleaks? Hand Credactor the report
and it fixes their findings together with its own, deduplicated (on a
duplicate, the higher severity is kept). Preview with --dry-run first, as for
a native scan.
gitleaks dir . -f json -r gl.json
credactor --from-gitleaks gl.json --fix-all --yes .
trufflehog filesystem . --no-verification --json > th.json
credactor --from-trufflehog th.json --fix-all --yes .
betterleaks dir . -f json -r bl.json
credactor --from-betterleaks bl.json --fix-all --yes .
Point Credactor at the same directory the scanner ran on, and regenerate the
report after the tree changes. A report holds every secret it found in
plaintext and is not scanned itself (.json needs --scan-json), so write it
outside the repository or delete it after the run. A finding Credactor cannot
place safely (no valid line number, a path under .git, a value too short to
match on its own) stays reported and is never written. Details and supported
versions are in the
Manual.
What it detects
| Category | Examples | Severity |
|---|---|---|
| Cloud and SaaS keys | AWS (AKIA…, ASIA…), GCP (AIza…), Stripe live (sk_live_…, rk_live_…), Slack (xoxb-…, xoxp-…) |
Critical |
| Platform tokens | GitHub (ghp_, gho_, ghs_, github_pat_), GitLab (glpat-), npm (npm_), PyPI (pypi-) |
Critical |
| Private keys | PEM blocks (-----BEGIN … PRIVATE KEY-----, encrypted ones included), PGP private key blocks |
Critical |
| JWTs | eyJ… three-segment tokens |
High |
| Connection strings | URLs with inline credentials (scheme://user:pass@host) |
High |
| Credential variables | password = "…", api_key = "…", secret_key = "…" |
High, Medium or Low |
| XML attributes | <add key="Password" value="…" /> |
High, Medium or Low |
| Stripe test keys | sk_test_… |
Medium |
| High-entropy strings | quoted hex (32 to 64 characters) or Base64 (60 or more) | Medium or Low |
Provider tokens are matched by their format, so they are found even in
comments and whatever their randomness. Heuristic matches must also look
random enough, and a bare hex or Base64 value is reported only when it is
quoted or assigned to a credential-named variable, which spares git SHAs and
checksums. Placeholders such as changeme, environment lookups and template
references are not flagged.
Good to know:
- Private keys are reported, never rewritten. Rotate the key and delete the
block by hand;
--fix-allcounts it as failed. - History is read-only and capped.
--scan-historycovers the 100 most recent file-changing commits, and warns when the repository has more than 100 commits. - A fixed set of file types is scanned: 29 code, config and text
extensions plus
.env.*and SSH key files (id_rsaand friends). JSON needs--scan-json, and anything else needsextra_extensionsin.credactor.toml. A file you name directly is always scanned. - Gitignored files are skipped when walking a directory, and that usually
includes a local
.env. The text report lists them, but-f jsonand-f sarifsay nothing about them. To check one, name it directly:credactor --dry-run .env. - Some formats are out of scope natively, for example SendGrid and Twilio keys and Slack webhooks, as are secrets built at runtime and values under 8 characters.
The full rules are in the Manual, and the threat model is in the Security doc.
How it rewrites
--replace-with |
Result | Use it when |
|---|---|---|
sentinel (default) |
"REDACTED_BY_CREDACTOR" |
You want anything still using the secret to fail loudly |
env |
os.environ["DB_PASSWORD"] |
The value should come from the environment |
custom |
"<your marker>" via --replacement |
You have your own convention |
Env mode writes the lookup in the file's language: os.environ[…] in Python,
process.env[…] in JavaScript and TypeScript, ENV[…] in Ruby, and the native
form in Go, Java, Kotlin and PHP. Every other file type gets a ${VAR}
placeholder, which is a real lookup only where something expands it (a shell
script, a .env loader, many config tools). In C# it stays a plain string,
and in Terraform it reads as a reference that does not exist, so the config
fails to validate; use the sentinel for both. The name comes from the finding:
db_password becomes DB_PASSWORD, and a known token type gets its own name,
so an AWS key becomes AWS_ACCESS_KEY whatever the variable was called. Where
a lookup would break the code (inside a larger string, say), the sentinel is
used instead.
Suppress false positives
On the line itself, in a #, //, /* */ or <!-- --> comment:
example_key = "AKIAIOSFODNN7EXAMPLE" # credactor:ignore
Or in a .credactorignore at the scan root:
tests/fixtures/*
app/settings.py:42
An entry can be a glob, a file:line, or an exact value. A value that contains
., /, ? or * (a URL, a JWT, Base64) must be written as
value:<literal>, or it is read as a glob and has no effect. Findings from an
ingested report are filtered by .credactorignore only: an inline
credactor:ignore does not stop them. Thresholds, skipped directories and
extra file types go in a
.credactor.toml.
Exit codes
| Code | Meaning |
|---|---|
0 |
No findings, or all of them fixed |
1 |
Findings left unresolved |
2 |
Error, such as a bad path, an invalid flag combination, an unreadable report or explicit --config, or --staged / --scan-history outside a git repository |
An unresolved finding never exits 0. A file Credactor could not read is only a
warning by default, so add --fail-on-error to a gate that must read every
file; it then exits 2. The full list is in the
Manual.
Supply-chain hardening
A tool that reads every secret in your repository should be safe to install, not only safe to run. Several popular packages have shipped malicious releases after an attacker got into their build or publishing pipeline: a tampered build dependency, a stolen upload token, or a file slipped into the release archive. Credactor's release pipeline has a specific check for each of those.
- No runtime dependencies.
pip install credactorinstalls Credactor and nothing else (the optional[encoding]extra addscharset-normalizer), so there is no third-party code to vet at install time. - Every release is checked against its source. In CI and again before
publishing,
scripts/audit_wheel.pycompares every file in the wheel and sdist that comes from the repository byte for byte (sha256) with the committed source, and rejects anything else apart from the package metadata, which must matchpyproject.toml. An extra or altered module, an injected dependency, a repointed command, or a file that is in the tarball but not in git fails the release. - Hash-pinned builds. CI and release builds install their tools with
pip install --require-hashes, build backend included, so a tampered dependency fails the build instead of reaching the package. - No upload token in the pipeline. Releases go to PyPI through OIDC trusted
publishing from a dedicated
pypienvironment, with Sigstore attestations, and only when the package version matches the release tag. The pipeline stores no long-lived PyPI token. - Locked-down CI. Every GitHub Action is pinned to a commit SHA, workflow tokens default to read-only, checkouts keep no credentials, and Dependabot proposes updates monthly.
- The Action installs outside your checkout. The GitHub Action installs
Credactor from the runner's temp directory with pip in isolated mode
(
python -I), then runs the installed command.
These checks show that what is published matches the source committed at the release tag. They cannot make that source trustworthy or protect a build machine that is already compromised; the Security doc has the full detail and limits.
Security
Report a vulnerability privately through GitHub Security Advisories, not a public issue. SECURITY.md lists the supported versions, and the Security doc covers the threat model, each hardening measure and the known limits.
Docs
| Document | What is in it |
|---|---|
| Manual | Every flag, mode and combination, with verified behaviour |
| Setup | Installation and .credactor.toml |
| CI integration | Pre-commit, the GitHub Action, other CI systems, ingestion |
| Examples | Common workflows with output |
| Security | Threat model, hardening, known limits |
| Disclaimer | Safe usage and warranty |
| Changelog | Version history |
| Contributing | Development setup and pull requests |
Licence
Apache 2.0. See LICENSE.
Metadata
Release files for credactor 2.7.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| credactor-2.7.5.tar.gz | 113.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| credactor-2.7.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 227.1 kB
Release files / credactor-2.7.5.tar.gz
| Download URL | credactor-2.7.5.tar.gz |
|---|---|
| Size | 113.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16b13bdc442aac62b7eb1648817d1f95a750d7ef35b91397e0c8db37cc534ba7
|
|
BLAKE2b-256 checksum How to use checksums |
e949eb4eaecdaab99d5c1653ff7d96e9ac20d43e628493a0f41dca8e8952c2b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / credactor-2.7.5-py3-none-any.whl
| Download URL | credactor-2.7.5-py3-none-any.whl |
|---|---|
| Size | 113.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
55c2c90e7fa1aab853290cbfcec1f3da444dfdddfec6786aa2704e37658ccb3e
|
|
BLAKE2b-256 checksum How to use checksums |
6aab3209902c4d649f1ca89e1a020138e9132865339676520e0158f8d7080e9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for credactor 2.7.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for credactor-2.7.5.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.