You've just built your first app that uses the OpenAI API, a payment gateway, or a cloud service. Everything works perfectly on your machine. You're proud of your code, so you push it to GitHub to show off your work.
Three hours later, you get an email: "Unusual activity detected on your account." Your $200 API credit is gone. Someone in a different continent is running crypto mining operations using your key.
Sound like a nightmare? It happens more often than you think. In 2023 alone, thousands of API keys were leaked on GitHub, costing developers and companies millions of dollars.
The good news? Most of these leaks are completely preventable. Let's walk through the 10 most common mistakes beginners make with API keys and how to fix them before they cost you.
1. Hardcoding Keys Directly in Your Code
The Mistake:
# main.py
import openai
openai.api_key = "sk-proj-abc123xyz789..." # DON'T DO THIS!
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello!"}]
)
This is the number one rookie mistake. Your API key is sitting right there in your source code, visible to anyone who has access to your repository.
The Fix:
Use environment variables with a .env file:
# main.py
import os
from dotenv import load_dotenv
import openai
load_dotenv() # Load environment variables from .env file
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "Hello!"}]
)
Create a .env file in your project root:
# .env
OPENAI_API_KEY=sk-proj-abc123xyz789...
DATABASE_URL=postgresql://user:pass@localhost/db
STRIPE_SECRET_KEY=sk_test_abc123...
Install the python-dotenv package:
pip install python-dotenv
For Node.js projects, use the dotenv package:
require('dotenv').config();
const apiKey = process.env.OPENAI_API_KEY;
2. Forgetting to Add .env to .gitignore
The Mistake:
You created a .env file (great!), but you forgot to tell Git to ignore it. Now your secret keys are in your commit history forever.
Even if you delete the file later, it remains in your Git history. Anyone who clones your repo can see every commit you've ever made.
The Fix:
Create or update your .gitignore file before your first commit:
# .gitignore
.env
.env.local
.env.*.local
*.env
# Also ignore these common secret files
secrets.yml
config/secrets.yml
.credentials
Already committed your .env file? You need to remove it from Git history:
# Remove from current commit
git rm --cached .env
# Remove from entire history (use with caution!)
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
Then immediately rotate (change) any exposed API keys.
3. Using the Same API Key for Development and Production
The Mistake:
You're using your production API key while testing locally. During development, you make 1,000 test requests, hit rate limits, or accidentally delete production data.
The Fix:
Always maintain separate keys for different environments:
# .env.development
STRIPE_KEY=sk_test_abc123...
DATABASE_URL=postgresql://localhost/myapp_dev
# .env.production
STRIPE_KEY=sk_live_xyz789...
DATABASE_URL=postgresql://prod-server/myapp_prod
Most API providers offer test/sandbox keys specifically for development:
-
Stripe:
sk_test_...vssk_live_... - OpenAI: Separate API keys with different rate limits
- AWS: Different IAM users for dev/staging/production
- Twilio: Test credentials that don't send real SMS
This practice also helps you:
- Avoid accidentally charging real credit cards during testing
- Keep your production rate limits intact
- Separate development costs from production costs
- Test safely without fear of breaking production
4. Granting "All Access" Permissions Instead of Least Privilege
The Mistake:
When creating an API key, you select "Full Access" or "Admin" permissions because it's easier than figuring out what you actually need.
If that key leaks, an attacker has complete control over your account: they can delete data, modify settings, or rack up huge bills.
The Fix:
Follow the principle of least privilege: only grant the minimum permissions required for the task.
Example with AWS IAM:
Instead of:
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
Use:
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
Example with GitHub Personal Access Tokens:
Don't select all scopes. If you're just reading repository data, only enable:
repo:statuspublic_repo
Example with Database Users:
-- Bad: Full admin access
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'localhost';
-- Good: Only what's needed
GRANT SELECT, INSERT, UPDATE ON myapp_db.* TO 'app_user'@'localhost';
Many services also offer read-only keys. Use them whenever you're only fetching data.
5. Storing Keys in Plain Text in Notion, Trello, or Docs
The Mistake:
You keep a Notion page or Google Doc titled "API Keys and Passwords" where you paste all your credentials for "easy access." Anyone with access to that doc (current or former team members, people you've shared links with) can see everything.
These documents also:
- Sync to cloud services
- Appear in search results
- Get cached in your browser
- Can be accidentally shared via public links
The Fix:
Use a proper password manager or secret management tool:
For Personal Projects:
- 1Password: Has a developer-friendly CLI tool
- Bitwarden: Open-source password manager
- LastPass: Popular option with team features
For Team Projects:
- HashiCorp Vault: Industry standard for secret management
- AWS Secrets Manager: If you're on AWS
- Azure Key Vault: For Azure users
- Google Secret Manager: For GCP users
Example with 1Password CLI:
# Store a secret
op item create --category=login \
--title="OpenAI API Key" \
password="sk-proj-abc123..."
# Retrieve in your script
export OPENAI_API_KEY=$(op read "op://Private/OpenAI API Key/password")
If you must document keys temporarily (during onboarding, for example), use encrypted storage or time-limited secret sharing services like OneTimeSecret.
6. Exposing Keys in Client-Side JavaScript
The Mistake:
You're building a web app and include your API key directly in your JavaScript because the frontend needs to make API calls:
// DON'T DO THIS!
const apiKey = "sk-proj-abc123...";
fetch('https://api.openai.com/v1/chat/completions', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
Anyone can open DevTools, view your source code, and copy your API key in seconds.
The Fix:
Never put secret keys in client-side code. Instead:
Option 1: Use a Backend Proxy
Create an API route on your server that makes the actual API call:
// Frontend (safe)
fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: 'Hello' })
});
// Backend (Node.js/Express)
app.post('/api/chat', async (req, res) => {
const apiKey = process.env.OPENAI_API_KEY; // Secure!
const response = await fetch('https://api.openai.com/v1/chat/completions', {
headers: { 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify({...})
});
res.json(await response.json());
});
Option 2: Use Restricted Public Keys
Some services offer client-side keys with restrictions:
- Firebase: Restrict by domain
- Google Maps: Restrict by HTTP referrer or IP
-
Stripe: Use publishable keys (
pk_) for client-side
Option 3: Use Serverless Functions
Deploy API calls as serverless functions (Vercel, Netlify, AWS Lambda) that keep secrets server-side.
7. Never Rotating Your API Keys
The Mistake:
You created an API key two years ago and have been using it ever since. You've shared it with contractors, used it on multiple machines, and included it in old projects you've forgotten about.
The Fix:
Rotate your API keys regularly, especially:
- Every 90 days as a standard practice
- Immediately when an employee leaves
- After any suspected compromise
- When decommissioning old projects
- After sharing keys in demos or screenshots
How to Rotate Safely:
- Generate a new key
- Update your production environment with the new key
- Test thoroughly
- Delete the old key
- Update documentation
Most platforms let you have multiple active keys simultaneously, making rotation seamless:
# Have both old and new keys active
OPENAI_KEY_OLD=sk-proj-abc123...
OPENAI_KEY_NEW=sk-proj-xyz789...
# Deploy new key to production
# Verify it works
# Delete old key
Set reminders or use tools like AWS IAM Access Analyzer to identify unused keys.
8. Logging API Keys in Error Messages or Debug Logs
The Mistake:
Your application logs everything for debugging purposes, including the full request headers or environment variables:
import logging
logging.debug(f"Making API call with headers: {headers}")
# Logs: Making API call with headers: {'Authorization': 'Bearer sk-proj-abc123...'}
logging.error(f"Environment: {os.environ}")
# Logs ALL environment variables including secrets!
These logs often end up in:
- Log aggregation services (Datadog, Splunk, CloudWatch)
- Error tracking tools (Sentry, Rollbar)
- Shared with support teams
- Committed to files in your repository
The Fix:
Sanitize sensitive data before logging:
import logging
import re
def sanitize_headers(headers):
safe_headers = headers.copy()
if 'Authorization' in safe_headers:
safe_headers['Authorization'] = 'Bearer [REDACTED]'
return safe_headers
logging.debug(f"Making API call with headers: {sanitize_headers(headers)}")
# For environment variables
def log_safe_env():
safe_env = {k: v for k, v in os.environ.items()
if not any(secret in k.upper()
for secret in ['KEY', 'SECRET', 'PASSWORD', 'TOKEN'])}
return safe_env
Configure your logging framework to automatically redact secrets:
# Python logging config
class SensitiveDataFilter(logging.Filter):
def filter(self, record):
record.msg = re.sub(r'sk-[a-zA-Z0-9]{48}', '[REDACTED]', str(record.msg))
return True
logger.addFilter(SensitiveDataFilter())
For error tracking services like Sentry:
import sentry_sdk
sentry_sdk.init(
dsn="your-dsn",
before_send=lambda event, hint: scrub_sensitive_data(event)
)
9. Accidentally Including Keys in Screenshots or Recordings
The Mistake:
You're creating a tutorial, recording a demo for your team, or taking a screenshot to report a bug. Your API key is visible in your code editor, terminal, or browser DevTools.
Once that image or video is online, it's nearly impossible to fully remove. People download it, share it, and archive it.
The Fix:
Before recording or screenshotting:
- Use placeholder values:
# Instead of real key
OPENAI_API_KEY = "sk-proj-abc123..."
# Use placeholder
OPENAI_API_KEY = "your-api-key-here"
- Use code comments:
# api_key = os.getenv("OPENAI_API_KEY") # Hidden for demo
api_key = "demo-key-not-real"
- Zoom in to hide sensitive areas
- Use screen recording software with blur features
- Edit screenshots to blur or redact keys before sharing
Tools to help:
- macOS: Built-in Screenshot markup tools
- Windows: Snipping Tool with pen/highlighter
- Linux: Flameshot (has blur/pixelate features)
- OBS Studio: Add blur filters for streaming
Pro tip: Create a separate "demo" environment with fake/limited keys specifically for recordings and presentations.
10. Not Using Environment-Specific Secret Management
The Mistake:
You're deploying to production and manually setting environment variables through your hosting provider's web dashboard. Team members don't know which keys are active, there's no audit trail, and updating keys requires manual work across multiple services.
The Fix:
Use proper secret management tools that integrate with your deployment pipeline:
For Docker:
# docker-compose.yml
services:
app:
env_file:
- .env.production
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
For Kubernetes:
apiVersion: v1
kind: Secret
metadata:
name: api-keys
type: Opaque
data:
openai-key: <base64-encoded-key>
---
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: api-keys
key: openai-key
For CI/CD Pipelines:
Use encrypted secrets in:
- GitHub Actions: Repository secrets
- GitLab CI: Masked variables
- CircleCI: Project environment variables (restricted)
- Jenkins: Credentials plugin
# GitHub Actions
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy
env:
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
run: ./deploy.sh
For Modern Platforms:
- Vercel: Environment variables per deployment environment
- Netlify: Build environment variables
- Railway: Service variables with automatic injection
- Render: Secret files and environment groups
These platforms provide:
- Automatic injection into your runtime
- Team access control
- Audit logs of who accessed what
- Easy rotation without redeployment
Bonus: What to Do If You've Already Leaked a Key
Don't panic, but act quickly:
-
Immediately rotate the key
- Generate a new key
- Delete the compromised key
- Update all services using it
-
Check for unauthorized usage
- Review your API usage dashboard
- Look for unexpected spikes
- Check for unfamiliar IP addresses
-
Assess the damage
- Review billing for unexpected charges
- Check if data was accessed or modified
- Look for new resources created
Remove from Git history (if applicable)
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
git push origin --force --all
-
Notify your team
- Inform relevant stakeholders
- Update documentation
- Review your security practices
-
Enable additional security
- Turn on two-factor authentication
- Enable IP allowlisting if available
- Set up billing alerts
- Configure rate limits
Key Takeaways
Securing API keys doesn't have to be complicated. Follow these core principles:
-
Never commit secrets to version control - Use
.envfiles and.gitignore - Use environment variables - Never hardcode credentials
- Follow least privilege - Grant only necessary permissions
- Separate environments - Different keys for dev, staging, and production
- Rotate regularly - Change keys periodically and after incidents
- Use proper tools - Password managers and secret managers exist for a reason
- Stay vigilant - Review logs, audit access, monitor usage
The few minutes you spend setting up proper secret management will save you from hours of panic and potentially thousands of dollars in damages.
Your future self (and your wallet) will thank you.
Have you ever accidentally exposed an API key? Share your story in the comments - we've all been there! And if you found this helpful, consider sharing it with someone who's just starting their development journey.
Want to learn more about security? Check out OWASP's API Security Top 10 for even more ways to protect your applications.
Top comments (0)