Lint your environment & secret hygiene before it leaks.
envaudit is a small, zero-dependency CLI that audits a project's .env
files for the mistakes that actually cause credential leaks:
- Is the
.envfile actually covered by.gitignore? - Has it already been committed to git, ignore rule or not?
- Are its file permissions too open (readable by other users)?
- Do its values look like real secrets, or are they placeholders?
It's built to be dropped into a pre-commit hook or CI pipeline and fail fast, with no config required to get useful output.
go install github.com/sapiuwu/envaudit/cmd/envaudit@latestOr clone and build locally:
git clone https://github.com/sapiuwu/envaudit
cd envaudit
go build -o envaudit ./cmd/envauditenvaudit scan # scan the current directory
envaudit scan ./backend # scan a specific path
envaudit scan --format json # machine-readable output
envaudit scan --no-strict # always exit 0 (report only)
envaudit init # generate .envaudit.yml
envaudit install-hook # install a pre-commit hook that runs the scan$ envaudit scan
envaudit v1.0.0 — scanning .
[CRITICAL] .env file is tracked by git — run `git rm --cached .env` and rotate any secrets inside it
[HIGH] .env:9 AWS_ACCESS_KEY_ID looks like a real AWS Access Key ID (matches known pattern: AWS Access Key ID)
[HIGH] .env:11 ETH_PRIVATE_KEY looks like a real Ethereum/EVM Private Key (matches known pattern: Ethereum/EVM Private Key)
[MEDIUM] .env file is writable by group members (mode -rw-rw-r--) — recommend chmod 600
Summary: 1 critical, 2 high, 1 medium, 0 low — 1 file(s) scanned
Exit code is non-zero whenever the highest finding meets or exceeds the
configured severity_exit_code (default: high) — so envaudit scan can
be dropped straight into CI or a pre-commit hook:
#!/bin/sh
# .git/hooks/pre-commit
envaudit scan || exit 1The install-hook command does this for you and refuses to clobber an
existing hook unless asked:
envaudit install-hook # write .git/hooks/pre-commit
envaudit install-hook --force # overwrite any existing hookReal projects occasionally have .env files that must stay out of
gitignore, or a value that looks like a secret but isn't. Instead of
lowering severity_exit_code for everyone, add a per-file entry to
.envaudit.yml:
allowlist:
- file: deploy/.env # silence a whole file
rule: not-gitignored
- file: .env
line: 5 # silence one line
- file: .env
key: AWS_ACCESS_KEY_ID # silence a specific keyAn entry suppresses a finding only when all the fields you set match, so the baseline stays precise.
Those tools do deep, whole-repo secret scanning and are great at it.
envaudit is narrower on purpose: it focuses specifically on .env
hygiene — the gitignore/permissions/placeholder-vs-real-secret triage
that happens before a secret ever gets scanned by something bigger. It's
meant to be the fast, zero-config check that catches the most common
mistake (a .env that was never gitignored, or already got committed)
before you reach for a heavier tool.
Run envaudit init to generate .envaudit.yml:
rules:
entropy_threshold: 4.0
ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"
custom_secret_patterns:
- name: internal_service_token
regex: "^svc_[a-zA-Z0-9]{32}$"
allowlist:
- file: .env.example.deploy
rule: not-gitignored
severity_exit_code: high| Key | Description | Default |
|---|---|---|
entropy_threshold |
Shannon entropy (bits/char) above which a value is considered random enough to be a real secret | 4.0 |
ignore_patterns |
Filename globs to skip entirely | *.env.example, *.env.sample, *.env.template |
custom_secret_patterns |
Extra regex rules, checked alongside the built-ins | none |
allowlist |
Suppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positives |
none |
severity_exit_code |
Minimum severity (low/medium/high/critical) that causes a non-zero exit |
high |
file in an allowlist entry matches the full path or just the basename
(e.g. .env), so baselines stay portable across machines and scan roots.
For each KEY=VALUE pair in a .env file:
- Placeholder check — common stand-ins (
changeme,your_api_key_here,<insert-key>, empty strings,${VAR}-style templates) are ignored. - Known pattern match — the value is checked against built-in regexes for AWS, Stripe, GitHub, OpenAI, Google, Telegram, Slack, JWTs, EVM private keys, database connection strings, and more. A match is high confidence regardless of entropy.
- Entropy + key-name heuristic — otherwise, the value's Shannon
entropy is measured. High entropy in a key named
*_SECRET,*_TOKEN,*_PASSWORD, etc. is flagged with a severity that scales with confidence.
This keeps false positives low — a placeholder like changeme is never
flagged, but a stray real key almost always is.
envaudit ships as a Docker-based GitHub Action. Add this to
.github/workflows/envaudit.yml in any repo you want scanned:
name: envaudit
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1
with:
path: "." # optional, default "."
strict: "true" # optional, default "true"
config: ".envaudit.yml" # optionalSee examples/consumer-workflow.yml.
| Input | Description | Default |
|---|---|---|
path |
Directory to scan | . |
config |
Path to config file | .envaudit.yml |
strict |
Fail the step on findings ≥ configured severity | true |
| Output | Description |
|---|---|
findings-count |
Total findings across all severities |
highest-severity |
ok, low, medium, high, or critical |
exit-code |
The exit code envaudit produced |
The action also writes a summary to the workflow run's Summary tab.
go test ./... -v -cover
go vet ./...
gofmt -l .This repo's own .github/workflows/ci.yml runs the full test suite and
also dogfoods the action against itself on every push and PR.
envaudit follows Semantic Versioning. Released
versions are tagged vX.Y.Z and summarized in CHANGELOG.md.
The version reported by envaudit version defaults to the latest release
and is injected at build time by ./build.sh, which derives it from the
nearest git tag. To pin a specific version:
go build -ldflags "-X main.version=1.2.3" -o envaudit ./cmd/envauditTo cut a release:
# bump the version in CHANGELOG.md (move [Unreleased] to a new version),
# then tag and push the tag:
git tag -a v1.1.0 -m "envaudit v1.1.0"
git push origin v1.1.0envaudit ships as a Homebrew tap. Once the tap is set up:
brew tap sapiuwu/envaudit
brew install envauditThe formula lives at
contrib/homebrew/envaudit.rb and is
regenerated automatically on every v* tag by
.github/workflows/formula.yml, so its url/sha256 always match the
latest release. To create the tap:
- Create a GitHub repository named
homebrew-envaudit(same account/org as this project). - Copy
contrib/homebrew/envaudit.rbinto it asFormula/envaudit.rbon the default branch. - Done —
brew install sapiuwu/envaudit/envauditnow works.
To refresh the formula locally for a specific version:
./scripts/update-formula.sh 1.1.0-
envaudit install-hook— one-command pre-commit hook installer - Baseline/allowlist file for accepted false positives
- Publish to the GitHub Actions Marketplace
- Homebrew tap
Issues and PRs welcome — especially new secret patterns
(internal/rules/patterns.go) for services not yet covered.
MIT — see LICENSE.