Secrets Management
Core Rules
- NEVER hardcode secrets, API keys, OAuth2 client IDs/secrets, tokens, passwords, or credentials in source code
- ALWAYS store secrets in
.envfiles (or platform-native equivalents likelocal.properties,.xcconfig) - ALWAYS load secrets from environment variables at runtime
- ALWAYS add
.envto.gitignorebefore first commit - ALWAYS provide a
.env.exampledocumenting required variables (with empty values)
Workflow
When Writing Code That Uses Secrets
- Detect the platform/framework from the project files
- Check if
.envand.gitignoreare set up — if not, create them - Load secrets from environment variables using the platform's standard pattern
- Never use string literals for secret values — always reference
process.env.*,os.getenv(), etc. - Add the variable name to
.env.examplewith an empty value and a descriptive comment - Run the scan script to verify no secrets leaked:
python3 scripts/scan_secrets.py .
When Setting Up a New Project
- Create
.envwith required variables - Create
.env.examplemirroring.envstructure with empty values (use env-example-template as a starting point) - Add secret-related entries to
.gitignore(use gitignore-secrets as reference) - Install the
.envloading library for the platform - Add loading code at the application entry point
When Reviewing Code
Run python3 scripts/scan_secrets.py <project-directory> to detect:
- Hardcoded API keys, tokens, and passwords
- OAuth2 client secrets in source
- AWS keys, Google API keys, Stripe keys, GitHub tokens
- Embedded private keys
- Connection strings with credentials
- Missing
.gitignoreentries for.env - Missing
.env.example
Quick Reference by Platform
For platform-specific .env loading patterns (install, load, access, framework variants), see references/platforms.md. Covers:
- JavaScript/TypeScript: Node.js, Next.js, Vite, React, Nuxt, Remix, Express, NestJS
- Python: Django, Flask, FastAPI
- Ruby: Rails
- Go: godotenv
- Java/Kotlin: Spring Boot
- PHP: Laravel
- Rust: dotenvy
- Swift/iOS: Xcode .xcconfig, Vapor
- Android/Kotlin: local.properties + BuildConfig
- Flutter/Dart: flutter_dotenv
- C#/.NET: DotNetEnv, User Secrets
- Docker: --env-file, docker-compose env_file
- CI/CD: GitHub Actions, GitLab CI, Vercel, Netlify, AWS, GCP, Azure
Anti-Patterns to Block
Never generate code like:
# BAD - hardcoded secrets
api_key = "sk-1234567890abcdef"
client_secret = "my-oauth-secret"
DATABASE_URL = "postgres://user:password@host/db"
const token = "ghp_xxxxxxxxxxxxxxxxxxxx";
Always generate code like:
# GOOD - loaded from environment
api_key = os.getenv("API_KEY")
const token = process.env.GITHUB_TOKEN;
Mobile Platform Notes
- iOS: Use
.xcconfigfiles (gitignored) referenced from Xcode build settings — not.envat runtime - Android: Use
local.properties(gitignored by default) injected viabuildConfigField— not.envat runtime - Flutter:
flutter_dotenvbundles.envinto the app binary. For truly sensitive secrets, use a backend proxy instead of embedding in the mobile app