Env Files and Secrets Without Leaking Keys

The token is in a screenshot, and the repo is about to go public. That Stripe API key you pasted for a 10-second test last week, or the Firebase service account token you copied into a comment to debug a CORS error, will be scraped by automated bots within 15 minutes of your repo going public if you don’t catch it first. Leaked keys can lead to unexpected cloud bills, unauthorized access to user data, and account suspensions on third-party services you rely on for your project. The steps below walk you through actionable, repeatable processes to keep your secrets locked down before you ever hit publish.

Names you put in .env.example with empty values

Your .env.example file is the public reference for all environment variables required to run your project, with zero real secret values included. This file is committed to your repo so collaborators, contributors, or people who fork your project know exactly what keys they need to add to their own local .env file to get the project running. The table below maps common variable names, their use cases, and placeholder values you can copy directly into your .env.example file:

Env Files and Secrets Without Leaking Keys desk detail
Desk detail for this page — not a measured lab photo.
Variable Name Use Case Required for Production Example Placeholder Value
`DATABASE_URL` Connection string for your primary production database Yes `postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public`
`NEXTAUTH_SECRET` Encryption key for user session tokens in NextAuth.js Yes
`STRIPE_SECRET_KEY` Server-side authentication for Stripe payment processing Yes `sk_live_XXXXXXXXXXXXXXXXXXXXXXXX`
`STRIPE_PUBLISHABLE_KEY` Client-side authentication for Stripe payment elements Yes `pk_live_XXXXXXXXXXXXXXXXXXXXXXXX`
`AWS_ACCESS_KEY_ID` Access ID for AWS S3 file storage, SES email, or other AWS services No (only if using AWS)
`AWS_SECRET_ACCESS_KEY` Secret key paired with the AWS access ID No (only if using AWS)
`SMTP_PASSWORD` Authentication for transactional email sending services No (only if sending email)
`VITE_PUBLIC_APP_URL` Public root URL of your deployed app, exposed to client-side code Yes `https://your-app-name.com`

Never add real values to this file, even temporarily. If you accidentally paste a real key into .env.example, delete it immediately and run a full git history scan to confirm it was never committed.

Gitignore line that must exist before the first key

You must add secret-related entries to your .gitignore file *before* you create a local .env file with real values. If you create the .env file first, git may track it even after you add .gitignore rules later, leaving your keys exposed in old commit logs. The following checklist covers all required .gitignore entries for common secret file types:

  • `.env`: Blocks the core local secrets file from being tracked
  • `.env.*`: Catches environment-specific env files including `.env.local`, `.env.development`, and `.env.production`
  • `!.env.example`: Explicitly allows the public, placeholder-filled example file to be committed
  • `*.pem`, `*.key`, `*.p12`: Blocks private key files used for SSH, SSL, and service account access
  • `credentials.json`: Blocks common service account credential files for Google Cloud, Firebase, and AWS
  • `.secret`: Blocks generic secret files and folders generated by framework CLI tools

If you already committed a .env file before adding these rules, you cannot just delete the file and move on: the key will still exist in your git history. You will need to purge the file from your history using a tool like BFG Repo-Cleaner, then rotate any keys that were present in the committed file.

Illustrative card for Env Files and Secrets Without Leaking Keys
Illustrative worksheet for this topic. Treat numbers as examples.

Scan you run before a public push

Even with strict gitignore rules, it is easy to accidentally hardcode a key into a comment, a debug log line, or a screenshot you added to your README. Run the following pre-push scan checklist in order every time you push to a public repo, or before making a private repo public:

  1. Run `git diff –cached` to manually review every line staged for commit, scanning for hardcoded keys, tokens, or personal identifiable information
  2. Run a local Gitleaks scan with `gitleaks detect –source . –no-git` to catch secrets in untracked files, followed by `gitleaks detect –source .` to scan your full commit history for past leaks
  3. Manually inspect all image files (screenshots, demo diagrams) in your commit for visible code snippets that include keys, and use ExifTool to strip all metadata from images to remove hidden embedded data
  4. Run a full codebase grep search for common secret patterns: `grep -r “sk_|pk_|secret|token|_key” . –include=”*.js” –include=”*.ts” –include=”*.py” –include=”*.md” –exclude-dir=node_modules –exclude-dir=.git`
  5. Audit all CI/CD workflow files (in `.github/workflows`, `.gitlab-ci.yml`, etc.) to confirm no hardcoded secrets are present, and all secret references use your Git host’s built-in encrypted secret store.

Illustrative example: This full scan takes 90 seconds to run for a typical small project with 10-50 code files, and can be added as a pre-commit hook to run automatically without manual input.

Rotation note if a key did hit the log

If you find a key in your commit log after you have pushed to a public repo, act immediately, no exceptions. First, revoke the key directly from the service provider’s dashboard: bots scrape public repos every 60 seconds for exposed keys, so even if you delete the commit minutes later, the key is already compromised. Next, purge the key from your git history using `git filter-repo` or BFG Repo-Cleaner, so the key is not visible in old commits for users who may have already forked or cloned your repo. Third, update the key in your local .env file, your deployment platform’s encrypted secret store, and any CI/CD workflows that use the key to restore functionality. Finally, monitor the associated service account for unusual activity for 72 hours after the leak to catch any unauthorized access attempts. For small solo projects with no user data, these steps are usually sufficient, but if the leaked key grants access to sensitive user information, you may need to follow applicable data breach notification rules for your region.

Demo mode that runs with fake values

You can eliminate most leak risk entirely by using fake, non-functional keys for all local development and public demo versions of your project. For example, use a local SQLite database instead of a production PostgreSQL instance so you do not need a real `DATABASE_URL` with credentials for testing. Use Stripe’s publicly available test mode keys, which do not charge real cards and cannot be used to access your live Stripe account, for all local payment testing. Use a mock SMTP server like MailHog to test email sending without needing a real SMTP password tied to a paid email service. For third-party APIs that do not offer a dedicated test mode, build a lightweight mock client that returns hardcoded fake responses so you do not need to call the real API at all during development. You can even commit a `demo.env` file with all fake values to your repo so users can spin up a fully functional local demo of your project in 2 minutes without needing to sign up for any third-party services.

Before you make your next public repo push, spend 5 minutes right now adding the required gitignore entries for your project, building out a complete .env.example file using the table above, and installing Gitleaks as a pre-commit hook to catch leaks automatically.

Written by the Build Next Stack editors.