Command line tool

envdiff: three-way environment variable drift

One table for .env, .env.example and the environment that is actually deployed. Values are never printed. They are reduced to a keyed digest and compared by that, so the table can go in a pull request. It exits nonzero when the three disagree, which is what makes it useful in a pre-deploy hook.

This page renders its tables from data produced by a real run of the tool. If you are reading this sentence, that script did not run.

What a real run looks like

key.env.env.exampledeployedverdict

The same run, as the terminal prints it

This block is the tool's real stdout, captured when this page was built. Nothing in it was typed by hand.



The verdicts

verdictmeanswhy it matters

What vercel env ls cannot tell you

vercel env ls prints names and the word Encrypted. It never prints a value. So in that mode presence can be compared and equality cannot, and those keys are reported as present but not comparable rather than as agreement. The same fixture read with vercel env pull, which does return values, resolves them. Below is the same set of keys under both modes.

keypresence only (env ls)values readable (env pull)

A tool that reported those keys as matching would be green on a production secret that had drifted, which is the failure it exists to catch.

What a row discloses

A row carries the key name, whether the value is set, how long it is, and eight hexadecimal characters of an HMAC-SHA256 keyed with a salt that is regenerated on every run. Two rows with the same digest had the same value during that run. The digest means nothing in the next run and nothing in anybody else's.

The salt matters more than it looks. A plain SHA-256 of a short value is not a mask: true, 1, postgres and a few thousand other plausible settings can be hashed and looked up in under a second, so a table of unsalted digests hands over the values it claims to hide. --salt pins the salt for reproducible output, and the help text says what that costs.

Equality is decided on the full digest, never on the eight characters shown. Eight hex characters are 32 bits, and a few thousand keys make a collision likely enough to meet in practice. The test suite carries a pair of different values that produce the same visible prefix, and asserts the tool still calls them different.

Using it

npx envdiff-three-way                        # .env vs .env.example vs vercel env ls
envdiff --remote-source pull                 # compare values, not just presence
envdiff --no-remote                          # the local two-way diff, no Vercel needed
envdiff --remote-cmd "flyctl secrets list"   # any provider that can print dotenv
envdiff --format markdown >> "$GITHUB_STEP_SUMMARY"

Exit codes: 0 the three columns agree, 1 drift was found, 2 the comparison could not be made. The third one is the point. A checker that cannot reach the deployed environment and exits 0 has told a deploy script that everything is fine.