guide
How to secure your .env file
Environment variables are where almost every real secret lives — database passwords, API
keys, signing keys. Keeping them secure is not about a better .env.example. It
is about knowing which values are sensitive, and then making sure no sensitive value sits in
plaintext where it can be committed, logged, bundled, or read into a model’s context. Here is
the short version, then the details.
Want it graded first? Paste your file into the rater →
the method
Five steps to a secure .env
- Stop committing sensitive values in plaintext. Gitignore your value files (.env, .env.local, .env.*.local). Files with only non-sensitive values can stay committed. If a real secret ever landed in git, rotate it — it is in history for good.
- Commit a schema, not an example. Replace .env.example with a .env.schema that declares each variable and marks which ones are sensitive.
- Mark what is sensitive. Tag credentials @sensitive. That is what lets a loader mask them in logs, CI output and agent context — and lets every sensitive value be forced out of plaintext.
- Declare types and required-ness. Give each variable a @type and mark whether it is @required, so a bad or missing value fails fast at load time instead of at runtime.
- Keep sensitive values out of plaintext. Load them from a gitignored .env.local or a secrets provider (1Password, AWS, Vault), or commit them encrypted, and validate everything up front with `varlock load`.
npx varlock init # turns your .env.example into a .env.schema
npx varlock load # validates every value, masks the secrets
npx varlock run -- npm dev # injects them into your app at runtime what "secure" actually means here
Declare what is sensitive, then get secrets out of plaintext
The single move that makes a .env secure is declaring which values are
sensitive. Once the schema knows that, every sensitive value can be pulled out of
plaintext — encrypted in place, or replaced with a declarative reference to where it is
fetched — while ordinary non-sensitive config stays readable and committable. Here is what
one looks like: the port and URL are committed as-is, the Stripe key is a reference.
# @currentEnv=$APP_ENV
# @defaultSensitive=false
# ---
# @type=port
PORT=3000
# @type=url
API_URL=http://localhost:4000
# Required outside local dev. Value comes from a provider, not this file.
# @sensitive @required=not(forEnv(development))
# @docs(https://docs.stripe.com/keys)
STRIPE_SECRET_KEY=op(op://engineering/stripe/secret-key) faq
Questions people actually search for
How do I secure my .env file?
The key move is declaring which values are sensitive. Once a committed schema marks that, every sensitive value can be pulled out of plaintext — kept in a gitignored file like .env.local, replaced by a declarative reference to a secrets provider (op(...), exec(...)), or committed encrypted (varlock("local:<encrypted>")). Non-sensitive config can stay in plaintext, and can be committed. A loader then validates everything and masks anything sensitive before it reaches a log, CI output, or an AI agent. varlock does this in one file.
Should I commit my .env file to git?
It depends what is in it. A file that holds only non-sensitive values is commonly and safely committed — a .env.production with public config, for example. What must never be committed is a sensitive value in plaintext, because once it is in git history rotating the key is the only fix. A schema makes this safe either way: it marks which items are sensitive, so those are kept in a gitignored file, referenced from a secrets provider, or committed encrypted, while everything else stays readable.
What is the difference between .env.example and .env.schema?
.env.example is a plain text list of variable names with placeholder values, kept in sync by hand and enforced by nothing. .env.schema is that list plus @decorator comments that declare types, required-ness, sensitivity and per-environment rules, so a loader can validate the environment and mask secrets. The example file can only describe; the schema is enforced.
How do AI coding agents leak secrets from .env files?
An agent runs cat .env, greps for a variable, or includes an open file in its context window. Once a secret is in a prompt it is in a request body and a provider’s logs. The fix is structural: mark which values are sensitive in a committed schema, and have the agent read .env.schema or run `varlock load --agent`, which returns the environment as JSON with sensitive values redacted.
Is there a free tool to audit my .env file for security issues?
Yes. The rater on this site is a free .env security audit that runs entirely in your browser — paste a .env or .env.example and it flags committed credentials, secrets exposed through public prefixes, missing types and undocumented variables, then generates the .env.schema it should have been. Nothing is uploaded; there is no server to send it to.
How do I keep secrets out of my frontend bundle?
Framework prefixes like NEXT_PUBLIC_, VITE_ and PUBLIC_ inline a variable into JavaScript shipped to every visitor. Never give a secret one of those names. Declaring a variable @sensitive in a schema lets the build catch the mistake before the bundle ships, instead of after.
next
Grade your file, then fix it
Paste your current .env or .env.example into the rater and get the
.env.schema it should have been — generated in your browser, nothing uploaded.