Skip to content
GitHub
Reference

Secrets Management

Spectral has three secret classes: platform operational, CI, and local-dev. The architecture is a deployer-operated provisioning script as the deploy-time orchestrator, with Render Environment Groups as the runtime read source. Decision lineage in ADR-037; CI-specific in ADR-062. Operational runbooks: docs/runbooks/secrets-management.md and docs/runbooks/ci-secrets.md.

Render Environment Groups carry runtime secrets. Apps read at startup via Pydantic Settings. Two environment groups exist:

  • spectral-staging-runtime
  • spectral-production-runtime

Each carries the rotating key material for its environment. Code-coupled values — values whose correctness depends on the running image — live as per-service env vars set at deploy time, not in env groups (per the CD pipeline placement principle).

tools/provision/setup.sh is the canonical deploy-tier orchestrator.

  • Modes: init / update / rotate / verify (with --dry-run required on mutating modes).
  • Target environment: --env=staging|production required.
  • Scope annotation in .env.example: # @scope=shared / # @scope=staging,production.
  • Cache: .env.provision (gitignored; backed up to a shared vault after each run) with scope-prefixed entries.
  • Targets: one bash function per target — push_render_env_group, push_github_secret, push_supabase_secret, etc.
  • Provider-swap discipline: adding a new target is one new bash function plus dispatch wiring. .env.example, prompt flow, rotation semantics, and cache format are unchanged.

The target push functions are stubbed today (--dry-run works end-to-end; non-dry-run paths exit with implementation pending). They wire to the live Render / GitHub / Supabase APIs as the deploy substrate lands. The script is the canonical IaC for secret provisioning.

Three GitHub Environments hold scoped secrets, each with required-reviewer = self:

  • staging — Supabase staging keys, Render API key, Cloudflare API token, OAuth test app credentials.
  • production — production equivalents.
  • test-live — LLM provider keys (Anthropic / OpenAI / Google) on a dedicated daily-capped test account; used by the nightly LLM live-drift workflow.

Mock-first PR CI; no pull_request_target. Live-secret runs gate to push (merge-to-main), schedule, workflow_dispatch. Fork PRs never trigger live-secret workflows. See ADR-062.

Four defense-in-depth layers:

  • GitHub Actions auto-redaction (built-in).
  • gitleaks pre-commit.
  • Structlog redaction filter — known sensitive field names auto-redacted in structured logs.
  • Cassette content lint (tools/quality/check_cassette_redaction.py) blocks Authorization: Bearer ... patterns in committed cassettes; activates with the first cassette commit per ADR-061 D8.

Quarterly + on-incident. Applies to LLM provider API keys, the Render API key, the Cloudflare API token, Supabase service-role tokens, and OAuth provider test-app secrets. Rotation runs via setup.sh --mode=rotate --secret=<name> --env=<env>. The full procedure is in docs/runbooks/secrets-management.md.

Cofounder personal access keys are private and not in system docs.

Composite trail: git history (commits touching tools/provision/); shared-vault version history of .env.provision backups; Render audit; GitHub org audit log; Supabase PGAudit. No unified UI; quarterly founder review covers all surfaces.

  • ADR-037 — decision lineage
  • ADR-062 — CI secrets
  • Hosting — Render workspace + Environment Groups
  • CD pipeline overview — env-var placement principle
  • docs/runbooks/secrets-management.md — rotation runbook
  • docs/runbooks/ci-secrets.md — CI rotation runbook