Safety

Secrets Management

Keeping credentials, API keys, and tokens out of source control and away from the logs.

Overview

Secrets management covers how credentials, API keys, private keys, and database connection strings are stored, rotated, and accessed. Secrets must never appear in source code, logs, error messages, or environment variable lists that can be read by unauthorized processes. Tools like HashiCorp Vault, AWS Secrets Manager, and Doppler provide centralised secret lifecycle management.

Origin

The practise of hardcoding credentials in source code was common in the early internet era. GitHub's 2013 credential scanner discovered tens of thousands of AWS keys in public repositories. The GitGuardian report (2024) found over 12 million secrets leaked on GitHub in 2023. HashiCorp Vault (2015) established the dynamic secrets and leasing model that influenced the field.

Examples

Environment variable management with dotenv and validation

import { z } from 'zod';

const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']),
  DATABASE_URL: z.string().url().startsWith('postgresql://'),
  JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  SENDGRID_API_KEY: z.string().startsWith('SG.'),
  REDIS_URL: z.string().url(),
  PORT: z.coerce.number().int().min(1024).default(3000),
});

function loadEnv() {
  const result = EnvSchema.safeParse(process.env);
  if (!result.success) {
    console.error('Invalid environment configuration:');
    console.error(result.error.flatten().fieldErrors);
    process.exit(1); // Fail fast; do not start with a misconfigured environment
  }
  return result.data;
}

export const env = loadEnv();
// env.DATABASE_URL is typed as string; accessing undefined env vars is a type error

Validating env vars at startup causes immediate, clear failure rather than a cryptic runtime error hours later. The schema documents required secrets explicitly. process.exit(1) on startup failure is intentional; a misconfigured process should not start.

Secret rotation with AWS Secrets Manager in Ruby

require 'aws-sdk-secretsmanager'

class SecretsManager
  CACHE_TTL = 300 # 5 minutes: balance freshness with API call cost

  def initialize
    @client = Aws::SecretsManager::Client.new(region: ENV.fetch('AWS_REGION'))
    @cache = {}
  end

  def get(secret_name)
    cached = @cache[secret_name]
    return cached[:value] if cached && Time.now - cached[:fetched_at] < CACHE_TTL

    response = @client.get_secret_value(secret_id: secret_name)
    value = response.secret_string
    @cache[secret_name] = { value: value, fetched_at: Time.now }
    value
  rescue Aws::SecretsManager::Errors::ResourceNotFoundException
    raise KeyError, "Secret not found: #{secret_name}"
  end
end

# Usage: secrets fetched at runtime, not baked into ENV at deploy time
$secrets = SecretsManager.new
db_url = $secrets.get('production/app/database-url')
ActiveRecord::Base.establish_connection(db_url)

Runtime secret fetching means a rotated secret takes effect within CACHE_TTL seconds without redeployment. AWS Secrets Manager natively supports automatic rotation for RDS credentials via a Lambda function, enabling zero-downtime rotation.

Use Cases

  • 01API keys for third-party services (Stripe, SendGrid, Twilio) stored in a secrets manager and injected at runtime
  • 02Database credentials rotated automatically by the secrets manager every 30 days without application redeployment
  • 03CI/CD pipeline secrets stored in GitHub Actions encrypted secrets, Vault, or cloud provider secret stores, never in .env files committed to the repo
  • 04Private TLS certificates and signing keys stored in hardware security modules (HSMs) or cloud KMS, with the application referencing key IDs, not raw key material

When Not to Use

  • //Do not store secrets in .env files committed to version control, even in private repositories
  • //Do not log secrets in error messages, request logs, or crash reports; mask sensitive fields in logging middleware
  • //Do not pass secrets as command-line arguments; they appear in process listings (ps aux) visible to all users on the host

Technical Notes

  • git-secrets (AWS Labs) and detect-secrets (Yelp) scan commits and repository history for credential patterns. gitleaks (open source) is commonly used in CI pre-commit hooks
  • Environment variables are visible to child processes and in /proc/PID/environ on Linux; use a secrets manager with in-memory injection for highly sensitive credentials
  • HashiCorp Vault dynamic secrets generate short-lived database credentials on demand; the application never holds a long-lived password, limiting the blast radius of a credential leak
  • AWS Parameter Store is free for standard parameters and cheaper than Secrets Manager for non-rotating configuration values; use Secrets Manager specifically for credentials that require rotation