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 that resolves config from infra/environments.toml + 1Password and publishes it to the GitHub Environment; the deploy then applies the Environment to the container (the runtime secret hop). Decision lineage in ADR-037 and ADR-110; CI-specific in ADR-062. Operational runbooks: docs/runbooks/secrets-management.md and docs/runbooks/ci-secrets.md.

The GitHub Environment carries the rotating key material — one per environment (production today; staging when a Supabase stage project lands). At deploy, deploy.yml reads the matching Environment and sets its values on the container (secrets + non-secret --vars), which the container reads at startup. Code-coupled values — values whose correctness depends on the running image, such as SPECTRAL_GENERATION — are set as per-deploy container vars, never in the shared Environment (per the CD pipeline placement principle).

tools/provision/provision.sh is the canonical deploy-tier orchestrator (ADR-110). It resolves infra/environments.toml (whose values reference 1Password entries, read via the op CLI) and publishes the result to the GitHub Environment:

  • provision.sh --env production --dry-run — review the plan.
  • provision.sh --env production — publish (after the 1Password CLI is authenticated with op signin).

The GitHub substrate itself — the production Environment and the production-ff-only ruleset — is bootstrapped once by tools/provision/github_resources.sh. Committed config (infra/environments.toml) is the source of truth; the GitHub Environment is its reconciled projection; nothing is passed to the deploy by hand. Re-running provision.sh after a value changes in 1Password (or a key is added to environments.toml) republishes the Environment.

GitHub Environments hold scoped secrets:

  • production — the deploy contract: CLOUDFLARE_API_TOKEN, DATABASE_URL / SUPABASE_DB_URL, SUPABASE_SECRET_KEY. Gated by the Environment’s protection rule + the production-ff-only ruleset.

Mock-first PR CI; no pull_request_target. Live-secret runs gate to push (merge-to-main), schedule, and 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 Cloudflare API token, Supabase keys, and OAuth provider test-app secrets. Rotation updates the 1Password-backed value, then re-runs provision.sh to republish the Environment. 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/ and infra/environments.toml); 1Password item history; the GitHub org audit log + Environment history; Supabase PGAudit. No unified UI; quarterly founder review covers all surfaces.

  • ADR-037 — decision lineage
  • ADR-110 — provisioning + the GitHub Environment
  • ADR-062 — CI secrets
  • Hosting — configuration placement
  • CD pipeline overview — config placement principle
  • docs/runbooks/secrets-management.md — rotation runbook
  • docs/runbooks/ci-secrets.md — CI rotation runbook