PushBackLog

Secrets Management

Hard enforcement Complete by PushBackLog team
Topic: security Topic: quality Skillset: backend Skillset: devops Technology: generic Stage: execution Stage: deployment

Secrets Management

Status: Complete
Category: Security
Default enforcement: Hard
Author: PushBackLog team


Tags

  • Topic: security, quality
  • Skillset: backend, devops
  • Technology: generic
  • Stage: execution, deployment

Summary

Secrets (API keys, tokens, passwords, certificates) must never appear in source code, logs, or version control. They must be stored in a dedicated secrets management system and accessed at runtime via environment variables or a secrets API, with rotation, auditing, and least-privilege access enforced.


Rationale

Git is forever

Once a secret is committed to a git repository — even briefly, even on a branch, even if the commit is removed from history — it must be considered compromised. Attackers actively scan GitHub, GitLab, and public container registries for credential patterns. Tools like truffleHog and GitGuardian exist specifically to scan commit history for leaked secrets. A secret in a .env file committed “just for testing” and then deleted can be found in the reflog for years.

Secrets in code cannot be rotated on a per-consumer basis

When a secret is embedded in application code, every instance of the application shares it and cannot be individually audited or revoked. A secrets management system enables per-service access, per-environment scoping, audit trails, and seamless rotation — none of which are possible when secrets live in source files.

OWASP A04 and A02

Hardcoded credentials directly satisfy OWASP A04 (Cryptographic Failures) criteria and contribute to A02 (Security Misconfiguration). They are also among the most commonly exploited vectors in software supply chain attacks — a compromised package that reads process.env or equivalent will exfiltrate embedded secrets.


Guidance

Secret storage options

ToolTypeBest for
HashiCorp VaultSelf-hosted or cloud secret storeComplex secret routing, dynamic secrets, large teams
AWS Secrets ManagerCloud-nativeAWS workloads; native IAM-controlled access
AWS Parameter Store (SSM)Cloud-nativeSimple key-value secrets on AWS, cost-effective
GCP Secret ManagerCloud-nativeGCP workloads
Azure Key VaultCloud-nativeAzure workloads
Doppler / InfisicalSaaS secret managerCross-environment, developer-friendly
1Password Secrets AutomationSaaSTeams already on 1Password

The runtime injection pattern

Secrets should never be baked into build artefacts. The 12-Factor pattern requires that secrets are injected at runtime via environment variables or fetched from a secrets API at startup:

# Bad: in source code
const STRIPE_KEY = 'sk_live_abc123...';

# Bad: committed .env file
STRIPE_KEY=sk_live_abc123...

# Good: read from environment at runtime (value injected by platform)
const STRIPE_KEY = process.env.STRIPE_SECRET_KEY;
# The platform (Kubernetes secret, ECS task definition, Doppler sync) sets the value.

Pre-commit scanning

Install a pre-commit hook that scans for secret patterns before code is committed. This is the cheapest layer of defence:

# Using detect-secrets
pip install detect-secrets
detect-secrets scan > .secrets.baseline
# Add to .pre-commit-config.yaml

Other tools: gitleaks, trufflehog, git-secrets.

Rotation policy

Secret typeRotation frequency
CI/CD deploy tokensOn every team member departure; quarterly minimum
API keys to external servicesQuarterly; immediately on any suspected exposure
Database credentialsOn every incident; semi-annually otherwise
JWT signing keysAnnually minimum; more often if short-lived tokens are used
TLS certificatesBefore expiry (automate with Let’s Encrypt / ACM)

Examples

Pre-commit hook with gitleaks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks
pre-commit install
# Now gitleaks runs on every `git commit`

Kubernetes secret injection

# Kubernetes pod spec
env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: app-secrets
        key: database-url

The secret value is stored in Kubernetes Secrets (ideally backed by Vault or AWS Secrets Manager via the secrets store CSI driver) and injected at pod startup — never in the Dockerfile or application code.


Anti-patterns

1. .env files committed to git

The most common vector. .env must be in .gitignore and should be .env.example in source control (showing required keys without values).

2. Secrets hardcoded as string literals in source

const client = new StripeClient('sk_live_abc123...'); // Never do this

3. Sharing secrets via Slack, email, or shared documents

Uncontrolled channels leave secrets with no audit trail, no revocation mechanism, and permanent unencrypted copies in message history.

4. Same secret across environments

A development secret exposure should not compromise production. Every environment (dev, staging, prod) must have its own set of secrets, minimally scoped.

5. Logging request/response bodies that may contain secrets

Authorisation headers, request bodies including passwords or tokens, and API responses may contain sensitive values. Log sanitisation must strip known sensitive field names before writing to any log store.



Part of the PushBackLog Best Practices Library. Suggest improvements →