Skip to content
GitHub
Operator

CI secrets handling runbook

Operational runbook for GitHub Actions secret scoping, fork-PR posture, rotation cadence, and leakage scanning. Implements ADR-062 and composes with ADR-037 secrets-management baseline + ADR-053 GitHub Environment + protection-rule patterns.

This runbook covers CI integration tests and deployment workflows. For provisioning + rotation of platform operational secrets see secrets-management.md.

GitHub Environments

Secrets used by CI are scoped to GitHub Environments. Each Environment carries the narrowest branch policy that lets its workflow run. Production deploy safety comes from the production branch trigger plus the production-ff-only ruleset.

staging (deferred)

The staging Environment is still deferred until the Supabase stage project lands. When it exists, it will carry the staging equivalents of the production deploy and OAuth-test credentials.

SecretSourceUsed by
SUPABASE_STAGING_ANON_KEYSupabase project consoleintegration tests against staging
SUPABASE_STAGING_SERVICE_ROLE_KEYSupabase project consoleintegration tests + Management API
SUPABASE_MGMT_API_TOKENSupabase organization settingspre-merge dry-run branches (per ADR-053 D4)
CLOUDFLARE_API_TOKENCloudflare → API tokensdeploy workflows (wrangler Worker/Container deploys; per ADR-109/110)
CF_API_TOKENCloudflare → API tokensedge workflows (per ADR-052 D6)
GOOGLE_OAUTH_TEST_CLIENT_*Google Cloud → OAuth credentialsOAuth integration tests
GITHUB_OAUTH_TEST_CLIENT_*GitHub OAuth AppsOAuth integration tests

production

Production deploy and Cloudflare/Supabase credentials. Deployment access is limited by the production GitHub Environment’s branch policy and the production-ff-only repository ruleset.

Workflow trigger posture

TriggerSecret accessWorkflows
pull_requestNoneci.yml (mock-first; testcontainers + cassettes)
push to mainstagingintegration-staging.yml
workflow_dispatchper-workflowops dispatches
Tag push (prod-*, v*)productiondeploy workflows (per ADR-053)

pull_request_target is not used at any workflow at alpha. The blanket posture avoids the privilege-escalation class. If external contributors emerge and a specific PR-time secret-using workflow becomes necessary, the path is a manual-approval gate via GH Environment required-reviewer rule + workflow_run chain — not pull_request_target.

Rotation cadence

Quarterly + on-incident applies to every secret listed above.

Secret familyCadenceProcedure
Cloudflare API tokenQuarterlyCF → API tokens → mint scoped token → update GH Environments → revoke old
Supabase service-roleQuarterlySupabase project → settings → API → reset → update GH Environments
Supabase Management API tokenQuarterlySupabase organization → settings → tokens → mint → update → revoke
OAuth test app secretsQuarterlyProvider console → rotate client secret → update staging GH Environment

Rotation events recorded in operational log (cofounder discipline; outside system docs per ADR-037 D5). Triggered immediately on any leak suspicion.

Leakage scanning

Defense in depth across four layers:

  1. GH Actions auto-redaction — registered GitHub secrets are automatically redacted in workflow logs (built-in). Never bypass by echoing transforms of secret values.
  2. gitleaks pre-commit — the pre-commit config (per repo .pre-commit-config.yaml) runs gitleaks on every commit, blocking commits that contain detected secret patterns. False positives are tunable via .gitleaks.toml allowlists.
  3. Structlog redaction filter — per ADR-036, the substrate redacts known sensitive field names (api_key, authorization, password, token, secret) from structured logs at emission.
  4. Cassette content linttools/quality/check_cassette_redaction.py blocks Authorization: Bearer ... and similar patterns from being committed in cassette files. Wired into the pre-push gate per ADR-053; lands with the first cassette commit per ADR-061 D8 (until then a dead lint with no inputs).

Workflow-file discipline

Hard rules every CI workflow file must follow:

  • Reference secrets via ${{ secrets.NAME }} only inside env: blocks of jobs that actually need them. Never at workflow scope when a single job is the consumer.
  • Never echo secrets to logs (echo $SECRET); never include them in error messages or commit metadata.
  • Never pass secrets to third-party actions without explicit allowlist review. Trusted publishers: GitHub-verified actions; org-allowlisted actions only.
  • Pin action versions by SHA or tag. No @main, no @latest. Pinned tags (@v3.1.4) are acceptable; floating refs are not.
  • Workflow files reviewed for these rules before merge. Mechanical lint may land post-alpha.

Auth test credentials

OAuth provider test apps (Google + GitHub per ADR-039 D2) use dedicated test-tenant accounts scoped to test users. Test-app secrets live in the staging GH Environment with manual-approval gate.

JWT signing keys for asymmetric flow (per ADR-039 D4a JWKS-local) follow a strict separation: test fixtures hold dedicated test signing keys; staging and production signing keys are never reused as test fixtures. Test keys are committed to test-fixture directories (private key material is contained because the keys are test-only).

Audit

GitHub’s built-in audit log captures every secret access (workflow runs that access scoped secrets are logged). Founder reviews quarterly via GitHub organization audit log UI.

  • secrets-management.md — platform operational secret provisioning and rotation
  • deployment.md — deploy workflow secret usage (per ADR-053)
  • edge.md — Cloudflare API token usage (per ADR-052)
  • hosting.md — Cloudflare deploy credentials (per ADR-109/110)