DEV Community

Alex Spinov
Alex Spinov

Posted on

Stop Using API Keys in Environment Variables — Here's What to Do Instead

I recently audited a friend's startup codebase. They had 23 API keys in .env files. Three of those keys had been committed to git history. One was a production Stripe key.

This is more common than you think. Let's talk about what to do instead.

The Problem With .env Files

.env files are convenient but dangerous:

  1. They get committed — even with .gitignore, someone eventually runs git add .
  2. They're shared insecurely — Slack messages, emails, shared drives
  3. They're not encrypted — plaintext on every developer's machine
  4. They don't rotate — most teams never change API keys until they leak

A 2024 GitGuardian report found 12.8 million new secrets exposed in public GitHub repos. That's 35,000 per day.

Level 1: Secret Scanning (Free, 5 Minutes)

Before fixing your key management, find what's already leaked:

# GitHub's built-in secret scanning (free for public repos)
# Go to repo Settings → Code security → Secret scanning

# Or use gitleaks locally:
brew install gitleaks
gitleaks detect --source . --verbose
Enter fullscreen mode Exit fullscreen mode

Trufflehog scans git history too:

trufflehog git file://. --only-verified
Enter fullscreen mode Exit fullscreen mode

Level 2: Secrets Manager (Free Tier Available)

Move secrets out of files into a proper secrets manager:

AWS Secrets Manager (free for first 30 days):

import boto3

client = boto3.client('secretsmanager')
secret = client.get_secret_value(SecretId='prod/stripe/api-key')
stripe_key = secret['SecretString']
Enter fullscreen mode Exit fullscreen mode

HashiCorp Vault (free, self-hosted):

import hvac

client = hvac.Client(url='http://vault:8200', token='...')
secret = client.secrets.kv.v2.read_secret_version(path='stripe')
stripe_key = secret['data']['data']['api_key']
Enter fullscreen mode Exit fullscreen mode

1Password CLI (great for small teams):

# In your script or CI:
export STRIPE_KEY=$(op read "op://Production/Stripe/api-key")
Enter fullscreen mode Exit fullscreen mode

Level 3: Short-Lived Tokens

The best API key is one that expires in minutes:

# Instead of static API keys, use OAuth2 client credentials
import requests

token_response = requests.post('https://api.example.com/oauth/token', data={
    'grant_type': 'client_credentials',
    'client_id': os.environ['CLIENT_ID'],  # Only 2 secrets to manage
    'client_secret': os.environ['CLIENT_SECRET'],
})
access_token = token_response.json()['access_token']
# This token expires in 1 hour — even if leaked, damage is limited
Enter fullscreen mode Exit fullscreen mode

Level 4: Workload Identity (Zero Secrets)

The ultimate goal — no secrets at all:

# GitHub Actions — no API keys needed for AWS
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/deploy
    aws-region: us-east-1
# Uses OIDC federation — GitHub proves who you are to AWS
Enter fullscreen mode Exit fullscreen mode

Quick Wins You Can Do Today

  1. Run gitleaks detect on your repo right now
  2. Enable GitHub secret scanning (Settings → Code security)
  3. Rotate any key that's ever been in git history — assume it's compromised
  4. Use .env.example with placeholder values, never real keys

What's Your Setup?

How does your team manage API keys? Still using .env files? Secrets manager? Something else?

I'm genuinely curious — I think most teams are still at Level 1 (or Level 0: raw .env files everywhere).


I write about API security and developer tools. My latest: npm typosquat scanner and PyPI typosquat scanner.

Top comments (0)