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.
| Secret | Source | Used by |
|---|---|---|
SUPABASE_STAGING_ANON_KEY | Supabase project console | integration tests against staging |
SUPABASE_STAGING_SERVICE_ROLE_KEY | Supabase project console | integration tests + Management API |
SUPABASE_MGMT_API_TOKEN | Supabase organization settings | pre-merge dry-run branches (per ADR-053 D4) |
CLOUDFLARE_API_TOKEN | Cloudflare → API tokens | deploy workflows (wrangler Worker/Container deploys; per ADR-109/110) |
CF_API_TOKEN | Cloudflare → API tokens | edge workflows (per ADR-052 D6) |
GOOGLE_OAUTH_TEST_CLIENT_* | Google Cloud → OAuth credentials | OAuth integration tests |
GITHUB_OAUTH_TEST_CLIENT_* | GitHub OAuth Apps | OAuth 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
| Trigger | Secret access | Workflows |
|---|---|---|
pull_request | None | ci.yml (mock-first; testcontainers + cassettes) |
push to main | staging | integration-staging.yml |
workflow_dispatch | per-workflow | ops dispatches |
Tag push (prod-*, v*) | production | deploy 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 family | Cadence | Procedure |
|---|---|---|
| Cloudflare API token | Quarterly | CF → API tokens → mint scoped token → update GH Environments → revoke old |
| Supabase service-role | Quarterly | Supabase project → settings → API → reset → update GH Environments |
| Supabase Management API token | Quarterly | Supabase organization → settings → tokens → mint → update → revoke |
| OAuth test app secrets | Quarterly | Provider 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:
- GH Actions auto-redaction — registered GitHub secrets are automatically redacted in workflow logs (built-in). Never bypass by echoing transforms of secret values.
- 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.tomlallowlists. - Structlog redaction filter — per ADR-036, the substrate redacts known
sensitive field names (
api_key,authorization,password,token,secret) from structured logs at emission. - Cassette content lint —
tools/quality/check_cassette_redaction.pyblocksAuthorization: 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 insideenv: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.
Related runbooks
secrets-management.md— platform operational secret provisioning and rotationdeployment.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)