Skip to content

0009 — Secrets hygiene: server-side generation, names-only in git

Status: accepted (2026-07-23)

Context

The repository's initial import (40d8e62) committed a real aim-backend/.env. The later cleanup (1120727) deleted the file, but a deletion does not unpublish it: the full contents remain readable in git history to anyone with a clone. The old scripts/ deploy helpers also took a server password from the environment. The rebuilt platform needs a posture where a repository leak cannot leak production credentials again.

Decision

  • Names only in git. Committed env files are names-only templates (aim-backend/.env.example and equivalents); every real .env pattern is gitignored. Documentation and workflows reference variable names, never values.
  • Secrets are generated on the server. deploy/gen-env.sh runs on the VPS and writes /opt/aim-store/deploy/.env, /opt/aim-store/deploy/portal.env.production, and /opt/aim-store/monitoring/.env (chmod 600), generating random values (DB passwords, JWT_SECRET, NEXTAUTH_SECRET, webhook secrets) and leaving provider credentials as fill-in placeholders. Deploy workflows copy configs but never env files.
  • CI holds only small-blast-radius credentials: an SSH deploy key and a read:packages GHCR pull token (ADR 0005).
  • Everything in the exposed .env is treated as compromised and must be rotated before the new stack goes live: database passwords, JWT_SECRET, Firebase service-account key, Entity Sport token, SMS/SMTP credentials, and any gateway keys it contained.

Consequences

  • A repository clone or leak yields variable names, not credentials.
  • The history itself is not rewritten — the repo has active clones and a rewrite would invalidate every checkout for cosmetic benefit; rotation, not erasure, is what actually revokes the exposed values.
  • Rotation is an operator task and is tracked as such; until it is done the historical exposure stands.
  • New components must ship a names-only template in the same PR that adds the component.