Audit environment variable configurations across .env, CI/CD, Docker, and Kubernetes for leaked secrets, cross-environment inconsistencies, and missing required variables.
Environment variable configuration is the #1 source of production incidents that shouldn't happen. Secrets get committed in .env files. Variables exist in dev but are missing in prod. Debug flags stay enabled in production. Connection strings contain hardcoded credentials.
These problems are scattered across multiple file formats -- .env files, GitHub Actions YAML, GitLab CI configs, Docker Compose files, Kubernetes manifests, and shell scripts. No single tool audits them all together.
envaudit solves this by:
- Parsing 6 different formats in a single scan
- Running 25 rules across 5 categories (secrets, consistency, naming, security, best practices)
- Outputting findings in text, JSON, or SARIF format for CI/CD integration
- Providing diff mode to detect regressions between audit snapshots
- Supporting configuration files for required variables, allowlists, and severity overrides
pip install envauditOr install from source:
git clone https://github.com/JSLEEKR/envaudit.git
cd envaudit
pip install -e .envaudit scan .envenvaudit scan ./config/envaudit scan .env docker-compose.yml k8s/envaudit scan . --format json -o audit-results.jsonenvaudit scan . --format sarif -o results.sarifScan environment variable files for issues.
envaudit scan <paths...> [OPTIONS]
Options:
--format [text|json|sarif] Output format (default: text)
--config PATH Path to envaudit.yaml config file
--severity [error|warning|info] Minimum severity to report (default: info)
--env-name NAME Environment name for scanned variables
-o, --output PATH Write output to file
Exit codes:
0-- No errors found1-- One or more error-severity findings
Compare two audit snapshots to detect regressions.
envaudit diff <old.json> <new.json> [OPTIONS]
Options:
--format [text|json] Output format (default: text)
Example workflow:
# Save baseline
envaudit scan . --format json -o baseline.json
# Make changes, then check for regressions
envaudit scan . --format json -o current.json
envaudit diff baseline.json current.jsonExit codes:
0-- No regression1-- Regression detected (new errors or increased risk score)
# List all rules
envaudit rules list
# List rules as JSON
envaudit rules list --format json
# Explain a specific rule
envaudit rules explain SEC001# Create default envaudit.yaml
envaudit config init
# Validate a config file
envaudit config validate envaudit.yaml| Rule | Description | Severity |
|---|---|---|
| SEC001 | Hardcoded password detected in value | ERROR |
| SEC002 | API key pattern detected in value | ERROR |
| SEC003 | Private key content detected in value | ERROR |
| SEC004 | Connection string with embedded credentials | ERROR |
| SEC005 | JWT token detected in value | ERROR |
| SEC006 | AWS access key pattern detected | ERROR |
| Rule | Description | Severity |
|---|---|---|
| CON001 | Variable present in one environment but missing in another | WARNING |
| CON002 | Variable value format differs across environments | INFO |
| CON003 | Required variable missing from environment | ERROR |
| CON004 | Variable defined but not referenced in other files | INFO |
| Rule | Description | Severity |
|---|---|---|
| NAM001 | Mixed case variable name (not ALL_UPPER_SNAKE_CASE) | INFO |
| NAM002 | Variable name collision (same name, different casing) | WARNING |
| NAM003 | Reserved prefix used (AWS_, GITHUB_, CI_, etc.) | INFO |
| NAM004 | Variable name too short (< 3 characters) | INFO |
| NAM005 | Non-alphanumeric character in variable name | WARNING |
| Rule | Description | Severity |
|---|---|---|
| SRK001 | Unencrypted secret in non-secret store | ERROR |
| SRK002 | Debug/development flag enabled in production config | ERROR |
| SRK003 | Overly permissive CORS origin (wildcard *) | WARNING |
| SRK004 | Insecure protocol (http://) in URL value | WARNING |
| SRK005 | Default/example credentials not replaced | WARNING |
| Rule | Description | Severity |
|---|---|---|
| BPR001 | Empty value for environment variable | INFO |
| BPR002 | Duplicate key in the same file | WARNING |
| BPR003 | Commented-out variable with value (potential leak) | INFO |
| BPR004 | Sensitive variable without placeholder or secret reference | WARNING |
| BPR005 | Value contains interpolation syntax from wrong format | WARNING |
| Format | File Patterns | What's Parsed |
|---|---|---|
| .env | .env, .env.*, *.env |
KEY=VALUE pairs, quoted values, export prefix |
| GitHub Actions | .github/workflows/*.yml |
env: blocks at workflow, job, and step level; secrets references |
| GitLab CI | .gitlab-ci.yml |
variables: blocks at top-level and job-level |
| Docker Compose | docker-compose*.yml, compose*.yaml |
environment: as list or dict in services |
| Kubernetes | *.yml with kind: ConfigMap|Secret|Deployment |
data, stringData, container env with value/valueFrom |
| Shell scripts | *.sh, *.bash, *.zsh, shebang detection |
export KEY=VALUE statements |
Create an envaudit.yaml file to customize behavior:
version: "1"
# Define environments and their associated files
environments:
- name: dev
files: [".env.dev", "docker-compose.dev.yml"]
- name: staging
files: [".env.staging"]
- name: prod
files: [".env.prod", "k8s/prod/"]
# Required variables per environment
required_vars:
all: [DATABASE_URL, APP_SECRET, LOG_LEVEL]
prod: [SENTRY_DSN, NEW_RELIC_KEY]
# Rule configuration
rules:
# Disable specific rules
disable: [NAM004]
# Override severity for specific rules
severity_overrides:
BPR001: warning
# Allowlist patterns
allowlist:
# Variable name patterns to ignore (supports glob)
variables: [PLACEHOLDER_*, TEST_*]
# Known placeholder values to skip
values: [changeme, your_value_here]envaudit scan . --config envaudit.yamlname: Environment Audit
on: [push, pull_request]
jobs:
envaudit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install envaudit
- run: envaudit scan . --format sarif -o results.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifenvaudit:
image: python:3.11
script:
- pip install envaudit
- envaudit scan . --format json -o audit.json
artifacts:
reports:
codequality: audit.json#!/bin/bash
envaudit scan . --severity errorenvaudit scan results
Files scanned: 4 | Variables found: 23 | Risk score: 30.0
Severity Rule Variable Message File
ERROR SEC001 DB_PASSWORD Hardcoded password detected .env:5
ERROR SEC004 DATABASE_URL Connection string with credentials .env:3
WARNING CON001 SENTRY_DSN Missing in prod environment -
WARNING SRK003 CORS_ORIGIN Wildcard '*' for CORS origins .env.prod:8
INFO NAM001 apiUrl Not ALL_UPPER_SNAKE_CASE .env:12
Total: 5 findings (2 errors, 2 warnings, 1 info)
{
"timestamp": "2026-04-12T10:00:00+00:00",
"files_scanned": [".env", ".env.prod"],
"variables_found": 23,
"risk_score": 30.0,
"findings": [
{
"rule_id": "SEC001",
"severity": "error",
"message": "Variable 'DB_PASSWORD' appears to contain a hardcoded password",
"file": ".env",
"line": 5,
"variable": "DB_PASSWORD",
"value_preview": "supe***",
"remediation": "Use a secret manager or reference"
}
]
}The risk score (0-100) is calculated from findings:
| Severity | Points per finding |
|---|---|
| ERROR | 10 |
| WARNING | 3 |
| INFO | 1 |
The score is capped at 100. A score of 0 means no findings.
envaudit never displays full secret values. Values are masked to show only the first 4 characters followed by ***. Values shorter than 5 characters are fully masked as ***.
src/envaudit/
cli.py Click CLI entry point
__main__.py python -m envaudit support
config.py envaudit.yaml configuration (Pydantic v2)
models.py Core data models (EnvVar, Finding, AuditResult)
scanner.py Orchestrates parsing + rule evaluation
diff.py Diff mode for snapshot comparison
parsers/ Format-specific parsers (6 formats)
rules/ Audit rules (25 rules in 5 categories)
output/ Output formatters (text, JSON, SARIF)
# Clone and install
git clone https://github.com/JSLEEKR/envaudit.git
cd envaudit
pip install -e .
# Run tests
python -m pytest tests/ -q
# Type check
mypy --strict src/
# Lint
ruff check src/ tests/MIT License - see LICENSE for details.
- Fork the repository
- Create a feature branch
- Write tests for your changes
- Ensure
ruff checkandmypy --strict src/pass - Submit a pull request