[{"data":1,"prerenderedAt":365},["ShallowReactive",2],{"blog-best-practices/secrets":3},{"id":4,"title":5,"body":6,"category":341,"date":342,"dateModified":342,"description":343,"draft":344,"extension":345,"faq":346,"featured":344,"headerVariant":350,"image":351,"keywords":351,"meta":352,"navigation":353,"ogDescription":354,"ogTitle":351,"path":355,"readTime":356,"schemaOrg":357,"schemaType":358,"seo":359,"sitemap":360,"stem":361,"tags":362,"twitterCard":363,"__hash__":364},"blog/blog/best-practices/secrets.md","Secrets Management Best Practices: API Keys, Credentials, and Vaults",{"type":7,"value":8,"toc":330},"minimark",[9,16,25,30,33,48,52,55,64,73,77,80,162,171,175,178,187,191,194,213,222,226,229,238,244,249,271,275,278,299,318],[10,11,12],"tldr",{},[13,14,15],"p",{},"The #1 secrets management best practice is to never commit secrets to git. Use environment variables or secret managers (AWS Secrets Manager, HashiCorp Vault). Rotate secrets regularly and automatically. Scan code and commits for leaked secrets. Use different secrets per environment.",[17,18,19],"quotable-box",{},[20,21,22],"blockquote",{},[13,23,24],{},"\"A leaked secret is a permanent secret. The moment it hits version control, assume it's compromised and rotate immediately.\"",[26,27,29],"h2",{"id":28},"best-practice-1-never-hardcode-secrets-2-min","Best Practice 1: Never Hardcode Secrets 2 min",[13,31,32],{},"Secrets in code get leaked through git history, logs, and error messages:",[34,35,37],"code-block",{"label":36},"Wrong vs right way to handle secrets",[38,39,44],"pre",{"className":40,"code":42,"language":43},[41],"language-text","// WRONG: Hardcoded secrets\nconst stripe = new Stripe('sk_live_abc123xyz789');\nconst db = new Database('postgres://admin:password@db.example.com');\n\n// CORRECT: Environment variables\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY);\nconst db = new Database(process.env.DATABASE_URL);\n\n// BETTER: Secret manager with validation\nfunction getRequiredSecret(name) {\n  const value = process.env[name];\n  if (!value) {\n    throw new Error(`Missing required secret: ${name}`);\n  }\n  return value;\n}\n\nconst stripe = new Stripe(getRequiredSecret('STRIPE_SECRET_KEY'));\n","text",[45,46,42],"code",{"__ignoreMap":47},"",[26,49,51],{"id":50},"best-practice-2-use-git-hooks-to-prevent-leaks-5-min","Best Practice 2: Use Git Hooks to Prevent Leaks 5 min",[13,53,54],{},"Stop secrets before they enter version control:",[34,56,58],{"label":57},".gitignore essentials",[38,59,62],{"className":60,"code":61,"language":43},[41],"# Environment files\n.env\n.env.local\n.env.*.local\n.env.production\n\n# Key files\n*.pem\n*.key\n*.p12\n*.pfx\n\n# IDE secrets\n.idea/\n.vscode/settings.json\n\n# Cloud credentials\ncredentials.json\nservice-account.json\n.aws/credentials\n",[45,63,61],{"__ignoreMap":47},[34,65,67],{"label":66},"Pre-commit hook with gitleaks",[38,68,71],{"className":69,"code":70,"language":43},[41],"# Install gitleaks\nbrew install gitleaks  # macOS\n# or download from github.com/gitleaks/gitleaks\n\n# .pre-commit-config.yaml\nrepos:\n  - repo: https://github.com/gitleaks/gitleaks\n    rev: v8.18.0\n    hooks:\n      - id: gitleaks\n\n# Run manually\ngitleaks detect --source . --verbose\n\n# Scan git history\ngitleaks detect --source . --log-opts=\"--all\"\n",[45,72,70],{"__ignoreMap":47},[26,74,76],{"id":75},"best-practice-3-use-a-secret-manager-10-min","Best Practice 3: Use a Secret Manager 10 min",[13,78,79],{},"Environment variables are good; secret managers are better:",[81,82,83,102],"table",{},[84,85,86],"thead",{},[87,88,89,93,96,99],"tr",{},[90,91,92],"th",{},"Solution",[90,94,95],{},"Pros",[90,97,98],{},"Cons",[90,100,101],{},"Best For",[103,104,105,120,134,148],"tbody",{},[87,106,107,111,114,117],{},[108,109,110],"td",{},"AWS Secrets Manager",[108,112,113],{},"Rotation, audit, IAM",[108,115,116],{},"AWS lock-in",[108,118,119],{},"AWS deployments",[87,121,122,125,128,131],{},[108,123,124],{},"HashiCorp Vault",[108,126,127],{},"Full-featured, multi-cloud",[108,129,130],{},"Complex setup",[108,132,133],{},"Enterprise, multi-cloud",[87,135,136,139,142,145],{},[108,137,138],{},"GCP Secret Manager",[108,140,141],{},"GCP integration",[108,143,144],{},"GCP lock-in",[108,146,147],{},"GCP deployments",[87,149,150,153,156,159],{},[108,151,152],{},"Doppler/Infisical",[108,154,155],{},"Dev-friendly, sync",[108,157,158],{},"Third-party SaaS",[108,160,161],{},"Startups, small teams",[34,163,165],{"label":164},"AWS Secrets Manager example",[38,166,169],{"className":167,"code":168,"language":43},[41],"import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\n\nconst client = new SecretsManagerClient({ region: 'us-east-1' });\n\nasync function getSecret(secretName) {\n  const command = new GetSecretValueCommand({ SecretId: secretName });\n  const response = await client.send(command);\n\n  if (response.SecretString) {\n    return JSON.parse(response.SecretString);\n  }\n\n  throw new Error('Secret not found');\n}\n\n// Usage\nconst dbCreds = await getSecret('prod/database');\nconst db = new Database({\n  host: dbCreds.host,\n  user: dbCreds.username,\n  password: dbCreds.password,\n});\n",[45,170,168],{"__ignoreMap":47},[26,172,174],{"id":173},"best-practice-4-rotate-secrets-regularly-10-min","Best Practice 4: Rotate Secrets Regularly 10 min",[13,176,177],{},"Automated rotation limits exposure from compromised secrets:",[34,179,181],{"label":180},"AWS Secrets Manager rotation",[38,182,185],{"className":183,"code":184,"language":43},[41],"// Terraform: Enable automatic rotation\nresource \"aws_secretsmanager_secret_rotation\" \"db_password\" {\n  secret_id           = aws_secretsmanager_secret.db.id\n  rotation_lambda_arn = aws_lambda_function.rotate_db.arn\n\n  rotation_rules {\n    automatically_after_days = 30\n  }\n}\n\n// Application: Handle rotation gracefully\nclass DatabasePool {\n  constructor() {\n    this.pool = null;\n    this.lastCredentialFetch = 0;\n    this.credentialTTL = 5 * 60 * 1000; // 5 minutes\n  }\n\n  async getConnection() {\n    if (this.shouldRefreshCredentials()) {\n      await this.refreshPool();\n    }\n    return this.pool.getConnection();\n  }\n\n  shouldRefreshCredentials() {\n    return Date.now() - this.lastCredentialFetch > this.credentialTTL;\n  }\n\n  async refreshPool() {\n    const creds = await getSecret('prod/database');\n    // Gracefully transition to new credentials\n    const oldPool = this.pool;\n    this.pool = createPool(creds);\n    this.lastCredentialFetch = Date.now();\n    if (oldPool) oldPool.end();\n  }\n}\n",[45,186,184],{"__ignoreMap":47},[26,188,190],{"id":189},"best-practice-5-environment-separation-5-min","Best Practice 5: Environment Separation 5 min",[13,192,193],{},"Use different secrets for each environment:",[195,196,197,201,204,207,210],"ul",{},[198,199,200],"li",{},"Separate secrets per environment (dev, staging, prod)",[198,202,203],{},"Use prefixes or namespaces (prod/stripe, staging/stripe)",[198,205,206],{},"Restrict production secret access",[198,208,209],{},"Use less privileged keys in development",[198,211,212],{},"Never copy production secrets to development",[34,214,216],{"label":215},"Environment-based secret loading",[38,217,220],{"className":218,"code":219,"language":43},[41],"// Secret naming convention\nconst secretName = `${process.env.ENVIRONMENT}/api-keys`;\n// Results in: dev/api-keys, staging/api-keys, prod/api-keys\n\n// IAM policy restricts access\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"secretsmanager:GetSecretValue\",\n  \"Resource\": \"arn:aws:secretsmanager:*:*:secret:prod/*\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"aws:PrincipalTag/Environment\": \"production\"\n    }\n  }\n}\n",[45,221,219],{"__ignoreMap":47},[26,223,225],{"id":224},"best-practice-6-audit-and-monitor-secret-access-5-min","Best Practice 6: Audit and Monitor Secret Access 5 min",[13,227,228],{},"Track who accesses secrets and when:",[34,230,232],{"label":231},"CloudTrail monitoring for secrets",[38,233,236],{"className":234,"code":235,"language":43},[41],"// CloudWatch alarm for unusual secret access\n{\n  \"AlarmName\": \"UnusualSecretAccess\",\n  \"MetricName\": \"SecretAccess\",\n  \"Threshold\": 100,\n  \"EvaluationPeriods\": 1,\n  \"Period\": 300,\n  \"Statistic\": \"Sum\",\n  \"ComparisonOperator\": \"GreaterThanThreshold\"\n}\n\n// Log secret access in application\nasync function getSecretWithAudit(secretName, reason) {\n  logger.info('secret.access', {\n    secretName,\n    reason,\n    requestedBy: getCurrentUser(),\n    timestamp: new Date().toISOString(),\n  });\n\n  return getSecret(secretName);\n}\n",[45,237,235],{"__ignoreMap":47},[239,240,241],"info-box",{},[13,242,243],{},"Emergency Response:\nIf a secret is leaked, immediately rotate it. Then investigate how it was exposed, scan for unauthorized use, and implement controls to prevent recurrence. Have a documented incident response plan for secret leaks.",[239,245,246],{},[13,247,248],{},"External Resources:\nFor comprehensive secrets management guidance, see the\nOWASP Secrets Management Cheat Sheet\nand the\nCryptographic Storage Cheat Sheet\n. These resources provide industry-standard security recommendations for protecting sensitive credentials.",[250,251,252,259,265],"faq-section",{},[253,254,256],"faq-item",{"question":255},"What if I accidentally committed a secret?",[13,257,258],{},"Immediately rotate the secret (generate a new one and revoke the old). Then remove from git history using git filter-branch or BFG Repo-Cleaner. Treat the old secret as permanently compromised.",[253,260,262],{"question":261},"Should I encrypt secrets in environment variables?",[13,263,264],{},"Environment variables are typically visible to the process and its children. For high-security environments, use a secret manager that decrypts at runtime. For most applications, restricting access to the environment is sufficient.",[253,266,268],{"question":267},"How do I handle secrets in CI/CD?",[13,269,270],{},"Use your CI/CD platform's secret management (GitHub Secrets, GitLab CI Variables). Inject secrets at runtime, never store in config files. Use OIDC to assume roles instead of long-lived credentials when possible.",[26,272,274],{"id":273},"further-reading","Further Reading",[13,276,277],{},"Put these practices into action with our step-by-step guides.",[195,279,280,287,293],{},[198,281,282],{},[283,284,286],"a",{"href":285},"/blog/how-to/add-security-headers","Add security headers to your app",[198,288,289],{},[283,290,292],{"href":291},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[198,294,295],{},[283,296,298],{"href":297},"/blog/getting-started/first-scan","Run your first security scan",[300,301,302,308,313],"related-articles",{},[303,304],"related-card",{"description":305,"href":306,"title":307},"Env var best practices","/blog/best-practices/environment-variables","Environment Variables",[303,309],{"description":310,"href":311,"title":312},"Secure deployment practices","/blog/best-practices/deployment","Deployment Security",[303,314],{"description":315,"href":316,"title":317},"Vercel secrets handling","/blog/best-practices/vercel","Vercel Security",[319,320,323,327],"cta-box",{"href":321,"label":322},"/","Start Free Scan",[26,324,326],{"id":325},"scan-for-exposed-secrets","Scan for Exposed Secrets",[13,328,329],{},"Check your codebase for leaked API keys and credentials.",{"title":47,"searchDepth":331,"depth":331,"links":332},2,[333,334,335,336,337,338,339,340],{"id":28,"depth":331,"text":29},{"id":50,"depth":331,"text":51},{"id":75,"depth":331,"text":76},{"id":173,"depth":331,"text":174},{"id":189,"depth":331,"text":190},{"id":224,"depth":331,"text":225},{"id":273,"depth":331,"text":274},{"id":325,"depth":331,"text":326},"best-practices","2026-02-02","Secrets management best practices. Learn how to store API keys, rotate credentials, use secret vaults, and prevent secret leaks in code.",false,"md",[347,348,349],{"question":255,"answer":258},{"question":261,"answer":264},{"question":267,"answer":270},"vibe-green",null,{},true,"Secure your API keys and credentials with proper secrets management.","/blog/best-practices/secrets","13 min read","[object Object]","Article",{"title":5,"description":343},{"loc":355},"blog/best-practices/secrets",[],"summary_large_image","5vGkXzG1-uzFZD7xzNUWccbRZ-NeQnRo6mruciCrruQ",1775843918547]