Cover: vault with key and shield

Where to Keep Your App's Keys: Secrets Managers and Scanners

API keys sitting in a .env file travel through Git history, browser JavaScript and any local program. Secrets managers, automatic scanners and rotation: the practical guide.

Jonathan Canales
audio-thumbnail
Where to Keep Your App's Keys: Secrets Managers and Scanners
0:00
/826
Listen on Spotify

A widespread habit deserves a thorough review: keeping an application's keys in a .env file. That file gathers every key in plain text, and the industry's accumulated experience shows those keys end up appearing in more places than planned. The good news: the ecosystem already has a mature answer, with secrets managers, automatic scanners and rotation routines that any team can adopt within a week. This guide walks the full picture: where keys travel, who looks for them, how the defense gets built piece by piece, and which routine keeps it current.

Three paths keys travel

The first is Git history. A commit containing the .env stays recorded permanently: deleting the file in the next commit leaves the previous version intact, and anyone with repository access can recover it. Public repositories multiply the reach, and even a private repository shared with collaborators widens the circle of copies.

The second path is the front end. Environment variables used by the browser end up compiled into the JavaScript downloaded on every visit. Any visitor can open developer tools and read them. The practical rule: every variable that reaches the browser is public by design, and deserves that treatment from day one.

The third is the computer itself. Any program running with the user's permissions can read that file: dependencies, build scripts, editor extensions. A .env file concentrates the entire treasure at a single well-known address, and every installed tool widens the list of potential readers.

Who takes advantage of those leaks

The search is automatic. Specialized programs sweep public commits and test the keys they find within minutes of publication. Unit 42, the Palo Alto Networks research team, documented the phenomenon with a controlled experiment: they published AWS keys on GitHub and recorded usage attempts within minutes, as part of a campaign they named Elektra-Leak. The central finding: exploitation speed is measured in minutes, and defense is measured in habits.

The typical use of a captured key is immediate and measurable: cryptocurrency mining billed to the account, access to stored data, charges that escalate within hours. Unit 42 recorded full operations built on exposed keys, with automation to find them, test them and exploit them. The practical lesson belongs to the calendar: every minute a key stays published counts, and fast rotation shrinks the window to its minimum.

Scanning goes beyond GitHub as well. Campaigns sweep thousands of servers looking for web-accessible .env files, and malicious packages published in dependency registries search the developer's computer for secrets upon installation. The supply chain turns every install into a trust decision, and good dependency hygiene complements the secrets manager.

The mature answer: a secrets manager

A secrets manager concentrates keys in a service designed to custody them. OpenBao, the community fork of Vault, Infisical and Doppler are three active, well-maintained options. The pattern is the same across all three: keys live encrypted in one place, each person and each service receives access to exactly what they need, and the application receives its values at startup, injected into memory at run time.

The model change organizes the whole operation. A single controlled version circulates, with an access log: who read what, when and from where. When someone changes teams, revoking their access is a one-step action instead of chasing copies across laptops and chat channels. And rotating a key becomes a centralized operation that propagates to every service using it.

Step by step: setting up the manager

OpenBao

OpenBao is the community fork of Vault, open-licensed and developed in public. The fast way to try it is development mode on your own machine: install the binary from openbao.org, start the test server and store the first secret.

bao server -dev -dev-root-token-id=root
export BAO_ADDR=http://127.0.0.1:8200
bao kv put secret/my-app STRIPE_KEY=sk_live_example OPENAI_KEY=sk-proj-example
bao kv get secret/my-app

The fourth command returns the values instantly. In production the server runs with sealed, encrypted storage, and each service receives a minimum-scope token through policies: the sales app reads only the secret/sales path, and the audit log records every read. The application picks up its values at startup through the CLI, the injector agent or the client libraries, and the official container makes deployment on your own infrastructure straightforward.

Infisical

Infisical combines a managed cloud and a self-hosted option, all open source. The flow starts by creating a project in the web interface, and the CLI links the local folder to that project in a few commands.

brew install infisical/get-cli/infisical
infisical login
infisical init
infisical run -- npm run dev

The last command starts the app with the variables injected into memory: values travel encrypted from the service and are handed to the process at run time. Each environment (development, staging, production) keeps its own set of values inside the same project, and the interface shows the change history value by value, with a rollback to any earlier version.

Doppler

Doppler bets on user experience. After creating the account, every project holds per-environment configurations, and the CLI follows the same pattern: authenticate, link the folder, and start the app with injection.

brew install dopplerhq/cli/doppler
doppler login
doppler setup
doppler run -- npm run dev

Sync with cloud providers and deployment platforms comes ready-made: updating a value in Doppler pushes the change to connected environments, and later deploys carry it along. For teams that value a short adoption curve, the package feels very direct.

How to choose between the options

OpenBao installs on your own infrastructure: full control of data and operations, ideal for teams with the capacity to administer services. Infisical is open source and offers both modes, self-hosted or managed cloud, with a modern interface. Doppler bets on user experience: environment synchronization, ready-made integrations and a very short adoption curve. All three handle runtime injection, per-person access control and audit logs.

The decision criteria are three: where the data should live, how much operation the team wants to carry, and which integrations the current workflow needs. A small project moves fast with the managed cloud; an operation with data-residency requirements leans toward self-hosting. A pilot with one project and one environment answers the fit question within a week, before any broader rollout.

Magnifying glass scanning code lines with one line flagged

Step by step: the scanner watching every push

The second piece of the defense is automatic: a secrets scanner reviews every commit before it reaches the repository. Gitleaks and TruffleHog are the two reference tools, both open and free, and they recognize hundreds of patterns: AWS keys, GitHub tokens, OpenAI keys, database connection strings.

Gitleaks

Gitleaks audits the whole repository and watches every new change. The first run sweeps the entire history in seconds, and the pre-commit hook filters every commit right on the laptop.

brew install gitleaks
gitleaks git -v .
brew install pre-commit
pre-commit install

The .pre-commit-config.yaml file looks like this, with the tag adjusted to the project's current version (pre-commit autoupdate refreshes it):

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.3
    hooks:
      - id: gitleaks

The same filter runs in the pipeline as an automatic step on every push, through the official gitleaks-action for GitHub or the binary on any other CI provider. A key attempting to enter the history gets stopped on the laptop or in the pipeline, with the file and line pointed out for immediate correction.

name: gitleaks
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2

On GitHub the pipeline step is a short workflow file: checkout with full history, then the official action. Every pull request carries the scan result in its checks list, and a finding holds the merge until the key is removed and rotated.

TruffleHog

TruffleHog complements with active verification: it tests each finding against the matching provider and reports only the credentials still live. That filter focuses attention on what requires immediate action.

brew install trufflesecurity/trufflehog/trufflehog
trufflehog git file://$(pwd) --only-verified

A monthly run over the full history keeps the certainty that the past stays clean, and verified mode clearly marks any active key that surfaces. Between the pre-commit hook, the pipeline step and the monthly audit, the repository stays watched at all three moments: before the commit, on every push and across its history.

Shield with checkmark, key and rotation arrows

Rotation as routine

Periodic rotation bounds each key's useful life: even if a copy started circulating, its window of usefulness measures in days. The usual cadence is quarterly for external service keys and monthly for the highest-reach ones, with immediate rotation after any detected exposure.

The dual-key technique keeps the service available through the whole change: the provider issues the new key, services adopt it while the previous one stays accepted, and the old key's revocation comes last, once the switch is confirmed. Managers centralize the operation: the new value is published at its path and every service picks it up on its next start or reload.

The rotation cadence belongs in the team calendar, attached to the same ritual as dependency updates. A recurring reminder with the checklist inside turns a policy line into a fifteen-minute task, and the manager's audit log answers the follow-up question of when each key last changed.

The .env file keeps its place

The recommendation targets the content, and the format stays useful for local development. The healthy convention: a .env.example file inside the repository with variable names and sample values, and the real .env outside version control, listed in .gitignore from the first commit. Real values reach the local environment from the manager, through a command that injects them when the app starts.

# .env.example
STRIPE_KEY=paste_value_here
OPENAI_KEY=paste_value_here

The sample file documents every variable the app expects, so preparing a fresh setup means copying the template and asking the manager for the real values.

If a key already went public

The correct response is immediate rotation: revoke the exposed key and generate a new one. Deleting the commit leaves history intact, and bots already copied the value within the first minutes, so repository cleanup takes second place once the door is secured. Cloud and API providers offer revocation from the dashboard in seconds, and several even automatically revoke keys they detect as published.

After rotation, a brief review pays off. The provider's usage logs show whether the key saw outside activity, and the scanner confirms the rest of the repository is free of sensitive values. The incident becomes routine practice: measure, rotate, verify, document.

A written response runbook shortens that first reaction. It fits on one page: who revokes keys at each provider, where the usage logs live, who tells the team, and how the incident gets documented. Teams that rehearse the runbook once a quarter answer an exposure in minutes, with the same calm as deploying on a Tuesday.

The shift also changes daily work for the better. Onboarding a developer means granting scoped access in the manager, and offboarding means revoking it, with every read recorded in between. New environments come up with the right values injected from day one, and the current version of every key lives in one place, queryable and audited.

Checklist

  • .env listed in .gitignore from the first commit, and .env.example with names and sample values
  • Secrets manager chosen, with project and environments created
  • Manager CLI installed on every laptop, with injection at app startup
  • Scanner installed as a pre-commit hook and as a pipeline step
  • Full history audited with Gitleaks and TruffleHog in verified mode
  • Keys that circulated through files or chats rotated and replaced
  • Per-person and per-service access with minimum scope
  • One-page exposure response runbook, written and rehearsed
  • Quarterly rotation on the team calendar

A one-week routine

The full plan fits in five days. Day one: add .env to .gitignore and move current keys into a manager. Day two: install the scanner as a local hook and as a pipeline step. Day three: rotate every key that ever circulated through a file or a chat. Day four: define who accesses what, with least privilege per person and per service. Day five: document the quarterly rotation routine and the exposure response. The result: keys stop depending on individual care and start depending on the system.

The one-line version to share with the team: keys live in a manager, the scanner watches every push, and every exposure ends in immediate rotation.

Sources

Was this article helpful?

Technology and tools