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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - sapiuwu/envaudit: Lint your environment & secret hygiene before it leaks. · GitHub
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 filerule: not-gitignored
- file: .envline: 5# silence one line
- file: .envkey: 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.0ignore_patterns:
- "*.env.example"
- "*.env.sample"
- "*.env.template"custom_secret_patterns:
- name: internal_service_tokenregex: "^svc_[a-zA-Z0-9]{32}$"allowlist:
- file: .env.example.deployrule: not-gitignoredseverity_exit_code: high
KeyDescriptionDefault
entropy_thresholdShannon entropy (bits/char) above which a value is considered random enough to be a real secret4.0
ignore_patternsFilename globs to skip entirely*.env.example, *.env.sample, *.env.template
custom_secret_patternsExtra regex rules, checked alongside the built-insnone
allowlistSuppress findings that match all of a given entry's fields (file, rule, line, key) — the checked-in baseline for accepted false positivesnone
severity_exit_codeMinimum severity (low/medium/high/critical) that causes a non-zero exithigh

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: envauditon: [push, pull_request]jobs:
scan:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: sapiuwu/envaudit@v1with:
path: "."# optional, default "."strict: "true"# optional, default "true"config: ".envaudit.yml"# optional

See examples/consumer-workflow.yml.

InputDescriptionDefault
pathDirectory to scan.
configPath to config file.envaudit.yml
strictFail the step on findings ≥ configured severitytrue
OutputDescription
findings-countTotal findings across all severities
highest-severityok, low, medium, high, or critical
exit-codeThe 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