# Production secrets in dedicated secrets manager

- **Pattern:** `ab-001260` (`environment-security.secrets-management.secrets-manager-used`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001260
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001260)

## Why it matters

Flat `.env` files synced to servers are a single point of compromise: one leaked file, one misconfigured S3 bucket, or one server breach exposes every production credential at once. Dedicated secrets managers — AWS Secrets Manager, HashiCorp Vault, Azure Key Vault — provide access control per secret, audit logs of every retrieval, automatic rotation, and versioned history. OWASP A05 (Security Misconfiguration) and NIST SC-12 both require protecting the integrity of keys and credentials throughout their lifecycle. PCI-DSS 4.0 Req-3.5 mandates protecting stored credentials, which flat files cannot satisfy for any cardholder data environment.

## Severity rationale

High because flat .env files on servers have no per-secret access control or audit trail, making breach detection and targeted rotation impossible when any one credential is exposed.

## Remediation

Replace flat `.env.production` files with a secrets manager appropriate to your hosting platform. Pull secrets at startup so no plaintext credentials ever land on disk.

**AWS Secrets Manager:**
```ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'
const client = new SecretsManagerClient({ region: 'us-east-1' })
const { SecretString } = await client.send(
  new GetSecretValueCommand({ SecretId: 'prod/stripe-key' })
)
```

**HashiCorp Vault:**
```ts
import vault from 'node-vault'
const client = vault({ endpoint: process.env.VAULT_ADDR, token: process.env.VAULT_TOKEN })
const { data } = await client.read('secret/data/prod/stripe-key')
```

After migrating, delete the `.env.production` file from all servers and revoke any credentials it contained.

## Detection

- **ID:** `secrets-manager-used`
- **Severity:** `high`
- **What to look for:** Check for evidence of a dedicated secrets manager: AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Secret Manager, or similar. Look for initialization code, dependencies, environment variable references to manager endpoints, or documentation.
- **Pass criteria:** Count all production secret references. Production secrets are stored in a dedicated secrets manager, not in flat .env files -- at least 100% of production secrets must use the manager's API for retrieval.
- **Fail criteria:** Production secrets stored in .env files that are synced to the server, hardcoded in config, or in any flat-file format.
- **Skip (N/A) when:** The project is development-only or a hobby project with no real production secrets (though this is rare).
- **Detail on fail:** Specify the current storage method. Example: `"Production secrets stored in .env.production file on server; no secrets manager detected"` or `"Only using .env files with no secrets manager integration"`.
- **Remediation:** Set up a secrets manager appropriate to your hosting platform:

  **AWS:** Use AWS Secrets Manager or AWS SSM Parameter Store. Initialize in your app:
  ```ts
  import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'
  const secret = await new SecretsManagerClient().send(
    new GetSecretValueCommand({ SecretId: 'stripe-key' })
  )
  ```

  **HashiCorp Vault:** Use Vault for generic secret storage across platforms:
  ```ts
  import vault from 'node-vault'
  const client = vault({ endpoint: process.env.VAULT_ADDR })
  const secret = await client.read('secret/stripe-key')
  ```

  **Azure / GCP:** Similar pattern using Azure Key Vault or Google Secret Manager SDKs.

## External references

- cwe CWE-522
- owasp A05
- nist SC-12
- pci-dss Req-3.5

Taxons: cryptography-and-secrets, operational-readiness

HTML version: https://auditbuffet.com/patterns/ab-001260
