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

  1. 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.
  2. Commit a schema, not an example. Replace .env.example with a .env.schema that declares each variable and marks which ones are sensitive.
  3. 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.
  4. 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.
  5. 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`.
the whole thing, in three commands
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.

.env.schema — sensitive values never in plaintext
# @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.