Secret scanners look for things that match a known credential shape. A linter looks for things that are structurally wrong — an empty secret that will fail open, a placeholder that reached production, a NEXT_PUBLIC_ prefix on something that should never reach a browser. Those problems do not match any vendor pattern, and they cause outages and breaches just as reliably.
What the .env checks catch
Publicly exposed secrets. A variable prefixed NEXT_PUBLIC_, VITE_, REACT_APP_ or PUBLIC_ is inlined into your JavaScript bundle at build time. If it is also named like a secret, it is shipping to every visitor. This is reported as critical because it is: the value is already public, and rotation is the only fix.
Placeholders in production. changeme, xxx, your-key-here, <REPLACE_ME>. These fail at runtime in confusing ways — often as a 401 from a third party that gets misdiagnosed for hours.
Empty secret-named variables. A blank API_KEY= frequently fails open rather than closed. Code that does if (apiKey) skips authentication entirely rather than refusing to start.
Weak values. Any secret-named variable with under 64 bits of entropy is flagged. Hand-chosen secrets cluster far below that.
Parser hazards. Unquoted values containing spaces get truncated by some dotenv implementations. Lowercase variable names work but break the convention every other tool assumes.
What the source checks catch
Hardcoded assignments. A credential as a string literal. Even a placeholder here is a problem, because it is the pattern by which real keys get committed.
Secrets in logs. console.log with a token in the arguments. Logs get shipped to third-party aggregators, retained for years, and read by people who should not see production credentials.
Fallback defaults. process.env.API_KEY || "dev-key-123". A default secret is a secret everyone has, including everyone who has read your repository. Throw instead.
Disabled TLS verification. NODE_TLS_REJECT_UNAUTHORIZED=0, rejectUnauthorized: false, verify=False. Every credential sent over that connection is interceptable. This is always critical, and "it is only in development" is how it reaches production.
Fixing, in order
- Rotate anything real that was found. The value is compromised the moment it was committed.
- Move the value to a secrets manager and inject it at runtime.
- Add a startup assertion so a missing secret stops the process instead of failing open.
- Add the gitignore rules and a pre-commit guard so the next one is caught before it lands.
Frequently asked questions
Is my code sent anywhere when I paste it?
No. The linter runs in your browser. Load the page, go offline, and it still works — worth verifying before pasting anything from a private repository.
Why is a placeholder flagged as a problem?
Because placeholders reach production. A deployment with API_KEY=changeme authenticates as nobody and fails at the first request, usually somewhere far from the cause. Failing at startup is better.
Should .env.example be committed?
Yes — with keys and no values. It documents what the application needs and makes a missing variable obvious. The .env file generator produces both the populated file and a safe example.
My secret is in git history but removed from the current file. Is that fine?
No. Anyone who cloned the repository has it, and it remains in every fork and mirror. Rotate the credential — that is what protects you. Rewriting history with git filter-repo is worth doing afterwards, but it does not un-share what was already shared.