A deterministic CLI that lints your .env files: syncs them against
.env.example, validates syntax & hygiene, enforces an optional schema,
and detects hardcoded secrets (AWS, Stripe, GitHub, OpenAI, private keys and
more) — with a score, A–F grade and JSON/Markdown reports. Runs 100% locally;
your secrets never leave your machine.
envlint compares your .env to its example, flags missing/extra/duplicate
keys and value problems, and scans for real secrets accidentally committed —
all offline, no API key, no server.
.env files are where teams quietly hurt themselves:
- A teammate adds
STRIPE_SECRET_KEYbut forgets.env.example, so everyone else's local build breaks and prod is missing a variable at runtime. - Someone pastes a real AWS or Stripe key into
.envand it gets committed — now it's in git history, and rotating it is a fire drill. - Values drift: a
PORTthat isn't a number, a URL with an unquoted space that silently truncates, a placeholderchangemeshipped to staging.
These are mechanical, high-stakes problems that belong in a pre-commit hook or
CI gate, not in a code review someone skims. And because .env is sensitive,
you can't just paste it into an LLM. envlint is the deterministic, local check.
- 🔄 Sync against
.env.example— missing keys, extra/undocumented keys. - 🔐 Secret detection — provider patterns (AWS, Stripe, GitHub, Google, OpenAI, Anthropic, Slack, Twilio, SendGrid, npm, JWTs, private keys) plus a Shannon-entropy fallback for opaque tokens. Placeholders are ignored.
- 🧹 Syntax & hygiene — duplicate keys, invalid names, empty values, unquoted values with spaces, trailing whitespace, lowercase keys, placeholders.
- 📐 Optional schema —
type(string/number/boolean/url/email/port),enum,patternper key. - 📊 Score + A–F grade, per file and overall; secrets weigh heaviest.
- 📄 JSON & Markdown export, colored console output, CI gate exit codes.
- ⚙️ Config file, per-rule severities, ignore lists.
- 🔒 Zero network. Secrets are masked in output and never transmitted.
# run without installing
npx @didrod2539/envlint scan .env
# or install
npm install -g @didrod2539/envlint # global CLI (provides `envlint`)
npm install -D @didrod2539/envlint # project dev-dependency (for CI / hooks)Node ≥ 18. ESM + CJS + TypeScript types.
envlint scan .env.env 0/100 (F) 10 keys vs .env.example
✗ Missing key "SESSION_TIMEOUT" (present in .env.example)
⚠ Key "EXTRA_DEBUG" is not in .env.example:21
✗ Duplicate key "ENABLE_SIGNUP" (lines 19, 20):20
⚠ Unquoted value with spaces for "DATABASE_URL" (line 8):8
✗ Possible JSON Web Token in "JWT_SECRET" (line 11):11
✗ Possible Stripe Secret Key in "STRIPE_SECRET_KEY" (line 14):14
✗ Possible AWS Access Key ID in "AWS_ACCESS_KEY_ID" (line 15):15
Overall 0/100 (F) 1 file(s), 10 key(s), 3 secret(s) , 8 error(s), 3 warning(s), 2 info
(envlint auto-finds .env.example / .env.sample / .env.template next to your file.)
envlint scan [...targets] # lint .env files or directories
envlint report <input.json> # re-render a saved JSON report as Markdown
envlint init # scaffold envlint.config.json (with a schema)
envlint --help
envlint --versionscan options:
| Option | Description |
|---|---|
--config <file> |
Path to a config file (otherwise auto-detected) |
--example <file> |
Example/schema file to compare against |
--no-secrets |
Disable secret scanning |
--json <file> |
Write a JSON report |
--md <file> |
Write a Markdown report |
--min-score <n> |
Exit non-zero if the overall score < n (CI gate) |
--quiet |
Hide info-level issues in the console |
Pointed at a directory, scan finds .env, .env.local, .env.production,
etc. (skipping *.example/*.sample/*.template).
Full reports for the bundled sample files are in
examples/sample-report.md and
examples/sample-report.json.
📸 Screenshot / demo GIF placeholder:
./docs/screenshot.png— record the terminal runningnpx @didrod2539/envlint scan examples/.env.local.
Create envlint.config.json (or run envlint init):
{
"example": ".env.example",
"scanSecrets": true,
"entropyThreshold": 4.0,
"entropyMinLength": 20,
"minScore": 90,
"allowEmpty": ["OPTIONAL_FLAG"],
"ignoreKeys": ["LEGACY_*"],
"disableRules": [],
"ruleSeverity": { "lowercase-key": "warning" },
"schema": {
"PORT": { "type": "port" },
"NODE_ENV": { "enum": ["development", "production", "test"] },
"DATABASE_URL": { "type": "url" }
}
}| Field | Meaning |
|---|---|
example |
Example/schema file (auto-detected if null) |
scanSecrets |
Enable provider/entropy secret scanning |
entropyThreshold / entropyMinLength |
Tune the generic high-entropy check |
minScore |
CI gate threshold (overridable with --min-score) |
allowEmpty |
Keys allowed to have empty values |
ignoreKeys |
Keys to skip — exact, or trailing-* prefix wildcard |
disableRules |
Rule ids to turn off |
ruleSeverity |
Override severity per rule id |
schema |
Optional per-key type / enum / pattern constraints |
Rule ids: missing-keys, extra-keys, duplicate-key, invalid-key,
empty-value, placeholder-value, unquoted-spaces, trailing-whitespace,
lowercase-key, secret-detected, schema-*.
- Block secret leaks in a pre-commit hook. Add
envlint scan .env --min-score 100(or wire it into [husky]/lint-staged). A real AWS/Stripe key in.envfails the commit before it ever reaches git history. - Keep
.env.examplehonest in CI. Runenvlint scan .env.ci --example .env.example. A PR that adds an env var without documenting it in the example fails the build, so onboarding never breaks. - Audit a config you inherited.
envlint scan . --md audit.mdprofiles every.env*file in a repo and produces a shareable report of what's missing, malformed, or dangerously hardcoded.
import { analyze, buildReport, toMarkdown } from "@didrod2539/envlint";
const file = analyze({ source: ".env", content, reference: { source: ".env.example", content: ex } });
console.log(file.score, file.secrets, file.issues);
const report = buildReport([file], { version: "0.1.0" });
await fs.writeFile("report.md", toMarkdown(report));- A bundled pre-commit hook /
huskyrecipe and a GitHub Action. --fixto sync missing keys into.envand quote unquoted values.- More provider secret patterns (Azure, GCP service accounts, DigitalOcean…).
.env.vault/ multi-environment diffing (.envvs.env.production).- Allowlist file for known-safe values (e.g. test fixtures).
- Baseline mode to ignore pre-existing findings and only fail on new ones.
Does envlint send my .env anywhere?
No. It runs entirely on your machine — no API key, no telemetry, no uploads, no
network calls. Detected secrets are masked in all output.
How does secret detection work?
A curated set of provider regexes (AWS, Stripe, GitHub, OpenAI, etc.) plus a
Shannon-entropy fallback for long, random, opaque tokens. Obvious placeholders
(your-key-here, changeme, <token>) are deliberately ignored. See
src/secrets.ts.
Won't the entropy check have false positives?
It's conservative (length + character-class + entropy threshold, all tunable via
config), and you can --no-secrets or ignoreKeys anything. Prefer a missed
edge case over noise.
Is it safe to keep example secrets in the repo?
Yes — that's the point of .env.example: it should contain only placeholders.
envlint flags when a real-looking secret appears so the example stays clean.
Does it work with my framework?
Any project using dotenv-style files: Node, Next.js, Vite, Rails, Django, Docker
--env-file, etc. It parses the common .env grammar (quotes, export,
comments).
Is the score official?
No — it's a transparent metric (severity-weighted penalties, with a heavy extra
penalty per detected secret). Use it to track and gate. See src/score.ts.
Contributions welcome! Each check is a small, self-contained rule in
src/rules/, and secret patterns are a declarative table in src/secrets.ts.
See CONTRIBUTING.md and the
Code of Conduct.
git clone https://github.com/didrod205/envlint.git
cd envlint
npm install
npm test
npm run build
node dist/cli.js scan examples/.env.local --example examples/.env.exampleMIT © envlint contributors
envlint is free, MIT-licensed, and built in spare time. If it stopped a secret from hitting your git history, please consider supporting it:
- ⭐ Star this repo — free, and it helps others find it.
- 🍋 Sponsor via Lemon Squeezy — one-time or recurring.
Where your support goes: more secret patterns, a pre-commit hook + GitHub
Action, a --fix mode, multi-environment diffing, and fast issue responses.