Skip to content

ADR 0004 — Public repo secret hygiene

  • Status: Accepted (policy), with open violations tracked in known_issues.md
  • Date: 2026-04-16

Context

github.com/callmehetch/h5h is a public repository. Anyone on the internet can read every file and every commit. This is a deliberate choice (portfolio visibility, community contributions), but it shifts the burden of secret hygiene from "git-gated" to "never type a secret in a tracked file".

Decision

The following rules are binding for anything that lands on main:

  1. No real secrets in any committed file. Real secrets live only in code/docker/.env (git-ignored) or in GitHub Actions secrets.
  2. Every secret has a placeholder in .env.example with a prefix like changeme_. generate-secrets.sh replaces a subset automatically.
  3. Homepage widgets must read API keys via {{HOMEPAGE_VAR_*}} Mustache placeholders — never inline the literal key.
  4. ACME / service emails are env vars, not hardcoded constants.
  5. Let's Encrypt private key (data/traefik/acme/acme.json) is never committed — .gitignore handles this.
  6. Git history is public too. Rotating a credential by editing the current file does not remove it from history. Rotation in the upstream system (Cloudflare, CrowdSec, Google, etc.) is mandatory.

Checklist before every push

  • [ ] git status — no .env, no acme.json, no *.key/*.pem.
  • [ ] git diff --cached — no values that look like tokens (40+ chars, base64-ish).
  • [ ] If a HOMEPAGE_VAR_* placeholder changed, does .env.example have the new var?

Reasoning

  1. Public visibility is an invariant, not a bug. Assume every commit is indexed by scanners within minutes.
  2. .gitignore only prevents adding. It does not prevent leaking something that was already committed.
  3. Rotation cost << compromise cost. Rotating a CrowdSec key takes 30 seconds; cleaning up a compromised instance takes days.

Known violations

See known_issues.md. At time of writing:

  • §1 — CrowdSec admin password committed in docker/homepage/services.yaml:122.
  • §2 — ACME email hardcoded in docker/traefik/traefik.yml:68.
  • §3 — Admin email hardcoded in docker/scripts/setup_rbac.py.

These are tracked rather than fixed in the April 2026 docs-only pass. The rotation + env-var-ization is a follow-up PR.

Automation (see Phase 2 of the docs plan)

  • CI job docs-check in .github/workflows/ci.yml runs a lightweight regex scan on docker/**/*.yaml for long base64-ish values not wrapped in Mustache placeholders.
  • The pull-request template forces an env-var sanity check.
  • Dependabot keeps GitHub Actions current.

MIT License