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.
Runtime read source
Section titled “Runtime read source”Render Environment Groups carry runtime secrets. Apps read at startup via Pydantic Settings. Two environment groups exist:
spectral-staging-runtimespectral-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).
Provisioning script
Section titled “Provisioning script”tools/provision/setup.sh is the canonical deploy-tier orchestrator.
- Modes:
init/update/rotate/verify(with--dry-runrequired on mutating modes). - Target environment:
--env=staging|productionrequired. - 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.
CI secrets
Section titled “CI secrets”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.
Leakage scanning
Section titled “Leakage scanning”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) blocksAuthorization: Bearer ...patterns in committed cassettes; activates with the first cassette commit per ADR-061 D8.
Rotation cadence
Section titled “Rotation cadence”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.
See also
Section titled “See also”- 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 runbookdocs/runbooks/ci-secrets.md— CI rotation runbook