Skip to content

Repository files navigation

envaudit

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 .env file 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.

Install

go install github.com/sapiuwu/envaudit/cmd/envaudit@latest

Or clone and build locally:

git clone https://github.com/sapiuwu/envaudit
cd envaudit
go build -o envaudit ./cmd/envaudit

Usage

envaudit 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

Example output

$ 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 1

Install a pre-commit hook

The 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 hook

Suppressing false positives (allowlist)

Real 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 key

An entry suppresses a finding only when all the fields you set match, so the baseline stays precise.

Why not just use gitleaks / trufflehog?

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.

Configuration

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.

How value classification works

For each KEY=VALUE pair in a .env file:

  1. Placeholder check — common stand-ins (changeme, your_api_key_here, <insert-key>, empty strings, ${VAR}-style templates) are ignored.
  2. 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.
  3. 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.

GitHub Action

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"  # optional

See 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.

Testing

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.

Versioning

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/envaudit

To 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.0

Homebrew

envaudit ships as a Homebrew tap. Once the tap is set up:

brew tap sapiuwu/envaudit
brew install envaudit

The 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:

  1. Create a GitHub repository named homebrew-envaudit (same account/org as this project).
  2. Copy contrib/homebrew/envaudit.rb into it as Formula/envaudit.rb on the default branch.
  3. Done — brew install sapiuwu/envaudit/envaudit now works.

To refresh the formula locally for a specific version:

./scripts/update-formula.sh 1.1.0

Roadmap

  • envaudit install-hook — one-command pre-commit hook installer
  • Baseline/allowlist file for accepted false positives
  • Publish to the GitHub Actions Marketplace
  • Homebrew tap

Contributing

Issues and PRs welcome — especially new secret patterns (internal/rules/patterns.go) for services not yet covered.

License

MIT — see LICENSE.

About

Lint your environment & secret hygiene before it leaks.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages