Skip to main content

GitHub Secrets Inventory

This is the single source of truth for which GitHub secrets exist, where they live, and which workflows consume them. If a future agent or engineer asks "can I delete this secret?" or "why does this exist?" — the answer is here, not buried across seven repos and many workflow YAMLs.

Maintenance rule: when you add a secrets.* reference to any workflow YAML, add a row here in the same PR. When the last reference is removed, mark the row Deletable here in the same PR. The audit-drift command at the bottom catches divergence.

How GitHub secret scoping works in sv0

Secrets attach at one of three scopes:

ScopeApplies toWhen to use
RepositoryEvery workflow run in the repoCross-environment values (CF Access service tokens used by both deploy and visual-review, GHCR auth)
Environment: devWorkflow runs that declare environment: devAnything specific to dev / PR-preview deploys
Environment: prodWorkflow runs that declare environment: prodAnything specific to production deploys

A secret named identically at both Environment scopes (e.g. WORKOS_API_KEY) holds two separate values — they don't share a backing store. This is intentional: prod and dev's WorkOS projects are different.

STAGING_* / PROD_* prefix convention

deploy-dev.yml reads only STAGING_*-prefixed secrets from the dev Environment. deploy-prod.yml reads only PROD_*-prefixed secrets from the prod Environment. Each workflow only ever consumes its own prefix from its own Environment scope.

The prefix exists because both names end up co-mounted into the same container .env file via docker-compose.deploy.yml's passthrough block (compose forwards every host env var literally, with ${VAR:-} defaulting unset values to empty string). The application code reads only the prefix that matches NODE_ENV. Without the prefix, dev's compose passthrough would clash with prod's at the var-name level.

When a STAGING_/PROD_ pair is collapsed into an unprefixed name, the deploy YAML changes from "always pass both prefixed pairs" to "Environment scope provides the unprefixed name; one pass to one slot."

Secret scope follows the resource, not the workflow count

The trap to avoid: "reused in N workflows" sounding like "shared across tiers." Those are different axes.

  • Machine-bound or tier-bound (an SSH key that unlocks one VM, per-tier WorkOS credentials, per-tier connector creds) → environment-level, even if reused across multiple workflows that target that same tier.
  • Cross-tier shared resource (one Cloudflare zone serving all tiers, one GHCR token, one shared metrics endpoint) → repo-level.

A dev-only SSH key reused in three dev-tier workflows is still a dev-tier secret. A Cloudflare API token whose zone covers prod + staging + dev is a repo-level secret. This rule was locked in 2026-05-15 after the sv0-platform#927#944 cycle, where moving the Hetzner SSH triple to repo level (briefly, in #940) was reverted because the key unlocks one machine, not all tiers.


External identities

GH secrets carry credentials, but the identities themselves (WorkOS Connect Apps, Atlas DB users, Azure user-assigned identities, federated credentials) live in external systems. This table is the lookup "does this thing already exist, and if so, what's it called?" for cases where someone is about to provision a new identity but should reuse an existing one.

Maintenance rule for this section: every PR that creates a WorkOS App, an Atlas DB user, or an Azure UAA / federated credential adds the row here. The PR description must name the identity. Without this surface, the engineer's only signal is grepping for the object's auto-generated ID, which they don't know yet.

WorkOS — Connect Apps & M2M

Identity (WorkOS dashboard name)TypeWorkOS workspaceclient_idBacking GH secret(s)Created
main user-session app (AuthKit)OAuth (PKCE)staging + prodclient_… (varies per env)WORKOS_CLIENT_ID (dev + prod env scopes)original
claude-code-agentOAuth (device_code)stagingclient_01KQFZTMWXZ48RF2CXPQHN5179STAGING_WORKOS_APP_CLAUDECODE_CLIENT_ID / _CLIENT_SECRET (dev env scope)2026-04-30 (sv0-platform#715)
claude-code-agent (prod)OAuth (device_code)prodnot yet provisionedPROD_WORKOS_APP_CLAUDECODE_CLIENT_ID / _CLIENT_SECRET (prod env scope)pending
ci-staging-m2mM2M (client_credentials)stagingclient_01KQHNSDZXKWQ0BR1JV248R80ZSTAGING_CI_M2M_CLIENT_ID / _CLIENT_SECRET (repo scope)2026-05-01 (sv0-platform#733)
ci-prod-m2mM2M (client_credentials)prodnot yet provisionedplanned: PROD_CI_M2M_*pending

Scope mapping in registry / runtime:

  • claude-code-agent is registered in agent-clients.ts as a delegated_agent (introspection-gated; staff CLI device_code flow).
  • ci-staging-m2m is NOT in agent-clients.ts — service-principal M2M JWTs are validated by the platform JWKS verifier directly, not the agent-client registry (see 13-authentication-and-user-management.md).
  • The main user-session app is the OAuth audience for every WORKOS_* JWT minted via the AuthKit cookie flow.

Atlas — DB users

IdentityAtlas projectClusterGranted DBsUsed byCreated
sv0_app (legacy)sv0-prodprod M10sv0_prod (only — sv0_dev / sv0_staging dropped in #93)Hetzner prod (until DNS cutover); deletion deferred to Hetzner decommission per ADR-025original
sv0_prod_appsv0-prodprod M10sv0_prodAzure prod (when stood up; sits idle until then)2026-05-18 (sv0-infrastructure#93)
sv0_staging_appsv0-staging (separate Atlas project)sv0-staging Flexsv0_stagingAzure staging deploy workflow (composed MONGODB_URI from TFC outputs)2026-05-18 (sv0-infrastructure#97)
sv0_dev_app (dev VM-internal Mongo)n/a — container-localn/an/adev-azure container Mongo (no Atlas user; auth disabled in container)n/a

Atlas password rotation: all of these passwords are TF-managed sensitive outputs (atlas_*_app_password). Never set manually in Atlas UI — drift will revert on next TFC apply. Pull from TFC API after apply; mirror into the matching MONGODB_URI GH secret per env.

Azure — User-assigned identities (UAA) & federated credentials

IdentityResourceSubscription RBACFederated subjectsBacking repo variableCreated
gha-sv0-platform-deployUAA 35d3196e-7934-483e-945d-3a7645b7da6fContributor on rg-sv0-dev; Contributor on rg-sv0-staging (added #91)repo:SecurityV0/sv0-platform:environment:dev; :environment:stagingAZURE_GHA_DEPLOY_CLIENT_ID (dev + staging env scopes; same client_id, different fed subjects)dev 2026-05-13 (#74); staging 2026-05-18 (#91)
gha-sv0-platform-deploy-prod (planned)future UAAContributor on rg-sv0-prodrepo:SecurityV0/sv0-platform:environment:prodAZURE_GHA_DEPLOY_CLIENT_ID (prod env scope, different value per ADR-024 §2 one-app-per-tier)pending — Azure prod migration
tfc-sv0-infrastructureUAA c8ac2f77-215f-4bc1-978f-b9e7ec34da68Owner on subscription (broad); UAA on subscription (RBAC management)TFC workspaces sv0-bootstrap, sv0-dev, sv0-staging, sv0-shared, sv0-prod, each plan + apply phase(consumed by TFC, not GitHub) — see TFC_AZURE_RUN_CLIENT_ID workspace env varoriginal (#62)

Why three deploy UAAs (one per blast-radius tier) instead of one with three subjects: ADR-024 §2 — federated credentials union RBAC at the SP, so one leaked GHA token would inherit every tier's blast radius if all three were on the same app. The pattern is one UAA per tier; within each, multiple repo:OWNER/REPO:environment:NAME federated subjects.


sv0-platform secrets

WorkOS — application auth

SecretScopeWorkflows / code that read itPurposeStatus
WORKOS_API_KEYdev + proddeploy-{dev,prod}.yml → API container env → env.ts (WorkOSEnv.apiKey) → workos-provider.tsServer-side WorkOS REST API auth (org lookups, membership, webhooks, introspection)Active
WORKOS_CLIENT_IDdev + proddeploy-{dev,prod}.ymlWorkOSEnv.clientIdMain user-session OAuth app (AuthKit). Audience claim in every WorkOS-issued JWT.Active
WORKOS_AUTHKIT_DOMAINdev + proddeploy-{dev,prod}.ymlWorkOSEnv.authkitDomainM2M JWT issuer + JWKS endpoint (e.g. securityv0.authkit.app). Required for any AUTH_PROVIDER=workos deploy — used by both device_code and client_credentials grants.Active
WORKOS_REDIRECT_URI_ALLOWED_HOSTSdev + proddeploy-{dev,prod}.ymlWorkOSEnv.redirectUriAllowedHostsPer-request redirect URI allowlist. Comma-separated list of hostnames; dev uses *.securityv0.com (single-label wildcard), prod uses app.securityv0.com.Active
SESSION_COOKIE_PASSWORDdev + proddeploy-{dev,prod}.ymlenv.ts → iron-session sealCookie seal for sv0_session. Single source of truth for the seal password.Active
WORKOS_SUPER_ADMIN_ORG_IDdev + proddeploy-{dev,prod}.ymlauth.ts callback route + bearer JIT-upsertWorkOS org whose active members are super-admin. The only super-admin signal. Required in prod.Active
WORKOS_WEBHOOK_SECRETdev + proddeploy-{dev,prod}.ymlenv.ts (placeholder)WorkOS webhook receiver auth. Receiver not yet wired.Active stub — keep until receiver lands

WorkOS — agent / M2M Connect Apps

These are separate WorkOS Connect Apps, not the main user-session app. Each holds its own client_id / client_secret pair.

SecretScopeWorkflows / code that read itPurposeStatus
STAGING_WORKOS_APP_CLAUDECODE_CLIENT_IDdevdeploy-dev.ymlagent-clients.ts (registry) → bearer middleware introspectionThe claude-code Connect App (device_code grant). Lets staff CLI mint delegated_agent tokens.Active
STAGING_WORKOS_APP_CLAUDECODE_CLIENT_SECRETdevdeploy-dev.yml → agent-clients.ts → introspection HTTP BasicPair of the above.Active
PROD_WORKOS_APP_CLAUDECODE_CLIENT_IDproddeploy-prod.yml → agent-clients.tsSame, prod Connect App.Active
PROD_WORKOS_APP_CLAUDECODE_CLIENT_SECRETproddeploy-prod.yml → agent-clients.tsSame, prod.Active
STAGING_CI_M2M_CLIENT_IDrepodeploy-dev.yml, pr-preview-admin.yml, visual-review.yml (inline curl to mint M2M token)Service-principal Connect App used by CI for non-interactive API calls (visual-review, PR-preview admin tasks). Repo-level because visual-review.yml doesn't declare an environment — env scope would make the secret unreachable from that workflow.Active
STAGING_CI_M2M_CLIENT_SECRETrepo(same workflows)Pair of the above.Active

No PROD_CI_M2M_* pair today. Prod CI auth has not been needed. If introduced, add a PROD_CI_M2M_* pair at the prod Environment scope and document it here.

Deploy + infrastructure

SecretScopeWorkflows that read itPurposeStatus
DEPLOY_HOSTdev + proddeploy-dev.yml, deploy-dev-cleanup.yml, deploy-prod.yml, pr-preview-admin.ymlSSH host for the deploy target VM.Active
DEPLOY_HOST_KEYdev + prod(same)known_hosts entry.Active
DEPLOY_SSH_KEYdev + prod(same)SSH private key used by the runner.Active
METRICS_BEARER_TOKENdev + proddeploy-{dev,prod}.ymldocker-compose.deploy.yml → API container envBearer token gating the /metrics Prometheus endpoint.Active
GITHUB_TOKEN(built-in)ci.yml, deploy-{dev,prod}.ymlBuilt-in scoped token; not user-managed.Active (built-in)

Cloudflare — Access service tokens, DNS, Pages

SecretScopeWorkflows that read itPurposeStatus
CF_ACCESS_CLIENT_ID_DEPLOYrepodeploy-{dev,prod}.yml, token-health.ymlCF Access service token (Origin: app.securityv0.com, dev.securityv0.com, pr-N-dev.securityv0.com) — bypasses the login page for CI.Active
CF_ACCESS_CLIENT_SECRET_DEPLOYrepo(same)Pair of the above.Active
CF_ACCESS_CLIENT_ID_VISUALrepovisual-review.yml, token-health.ymlService token for visual-review's headless Playwright runs. Distinct from _DEPLOY so it can be rotated independently.Active
CF_ACCESS_CLIENT_SECRET_VISUALrepo(same)Pair of the above.Active
CLOUDFLARE_ACCOUNT_IDrepobootstrap-cf-access.yml, token-health.yml, visual-review.yml, visual-review-cleanup.yml, visual-review-stale-cleanup.ymlAccount ID for sv0-reviews.pages.dev (PR visual-review site) deploys + CF Access app management.Active
CLOUDFLARE_API_TOKENrepobootstrap-cf-access.yml, deploy-dev.yml, deploy-dev-cleanup.yml, visual-review.yml, visual-review-cleanup.yml, visual-review-stale-cleanup.ymlCloudflare API token. Two unrelated uses: (a) DNS record management for securityv0.com (PR-preview CNAME create/delete in deploy-dev / cleanup, CF Access app bootstrap) and (b) wrangler Pages publishing for pr-N.sv0-reviews.pages.dev. Same token works for both because Pages and Zone:DNS:Edit are both scoped to the SecurityV0 account.Active
CLOUDFLARE_ZONE_IDrepodeploy-dev.yml, deploy-dev-cleanup.ymlZone ID for securityv0.com (732bc7b588772c6cd1b5b39516a63243). Public identifier, not secret-shaped, but kept in secrets for naming consistency with CLOUDFLARE_*.Active
CLOUDFLARE_API_TOKEN_ZERO_TRUSTrepotoken-health.ymlAPI token scoped to the Zero Trust org (separate from the general API token) for CF Access app bootstrap and policy management. See #575.Active

sv0-connectors secrets

SecretRead byPurposeStatus
ENTRA_SERVICENOW_AZURE_CLIENT_IDentra-servicenow-scan.ymlAzure App Registration client_id for the entra-servicenow connector's Microsoft Graph access.Active
ENTRA_SERVICENOW_AZURE_CLIENT_SECRETentra-servicenow-scan.ymlPair of the above.Active
ENTRA_SERVICENOW_AZURE_TENANT_IDentra-servicenow-scan.ymlAzure tenant ID.Active
ENTRA_SERVICENOW_SNOW_INSTANCEentra-servicenow-scan.ymlServiceNow instance hostname.Active
ENTRA_SERVICENOW_SNOW_USERNAMEentra-servicenow-scan.ymlServiceNow basic-auth username.Active
ENTRA_SERVICENOW_SNOW_PASSWORDentra-servicenow-scan.ymlServiceNow basic-auth password.Active
GITHUB_TOKENconnectors-publish.ymlBuilt-in scoped token.Active (built-in)

Where is the entra-servicenow-ci.yml workflow? It exists in .github/workflows/ but reads no secrets — it's a unit-test-only workflow that runs without live credentials. Live integration tests run via entra-servicenow-scan.yml against a long-lived dev tenant.

Previously here, now deleted: CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET (set 2026-04-02). No workflow ever consumed them — leftover from an early connector spike. Removed 2026-05-15 after audit. The connector runtime reads CF Access tokens from a .env file on the connector host, not from GHA.


sv0-website secrets

SecretRead byPurposeStatus
CLOUDFLARE_ACCOUNT_IDdeploy-prod.yml, deploy.yml, staging.yml, report.yml, visual-review.yml, visual-review-cleanup.ymlAccount ID for securityv0.com Pages deploys.Active
CLOUDFLARE_API_TOKEN(same workflows)API token for wrangler to publish the marketing site.Active
SLACK_WEBHOOKnotify-slack.ymlSlack incoming webhook URL for engineering channel — fires on key release events.Active
GITHUB_TOKENauto-merge-routine-prs.yml, staging.ymlBuilt-in scoped token.Active (built-in)

sv0-intelligence secrets

SecretRead byPurposeStatus
ANTHROPIC_API_KEYweekly-incident.ymlAnthropic API auth for scripts that call Claude (incident summarization, weekly PM cleanup, etc.).Active
GH_TOKENweekly-incident.ymlUser-managed GitHub token with cross-repo write permission. Distinct from the built-in GITHUB_TOKEN because that one is scoped to the run's own repo only.Active

sv0-documentation secrets

SecretRead byPurposeStatus
CLOUDFLARE_ACCOUNT_IDdocs-ci.ymlAccount ID for docs.securityv0.com Pages deploy.Active
CLOUDFLARE_API_TOKENdocs-ci.ymlAPI token for wrangler to publish the mkdocs build.Active

Repos with no workflow secrets

sv0-skills and sv0-demo-labs ship workflows that don't reference any secrets.* entries today. If that changes, add a section above.


VM ↔ secret mapping (2026-05-15)

Cross-reference of which secrets back which deploy target. Maintain alongside the per-name tables above; this view answers the operational question "when we retire VM X, which secrets become deletable?".

VM / targetTierAuth mechanismSecrets backing itSunset trigger
Hetzner VPS 178.156.217.150dev (current)SSHdev.DEPLOY_HOST, dev.DEPLOY_HOST_KEY, dev.DEPLOY_SSH_KEYDNS flip of dev.securityv0.com to Azure
Hetzner prod VMprod (current)SSHprod.DEPLOY_HOST, prod.DEPLOY_HOST_KEY, prod.DEPLOY_SSH_KEYProd migrates to Azure (no firm timeline)
Azure vm-sv0-dev-1 (ADR-024)dev demo (bootstrap-only — no app stack yet)OIDC federation, Entra app gha-sv0-platform-deployNone — RBAC, not a secret. Public-ish app client_id lives in repo variable AZURE_GHA_DEPLOY_CLIENT_ID.n/a (the canonical path)
Future Azure dev (long-running, full app stack)dev (target)OIDC, same Entra appNone plannedn/a
Future Azure stagingstaging (per ADR-022 §3)OIDC, separate Entra app gha-sv0-platform-deploy-stagingTier-specific WorkOS pair in staging env scope (when env exists)n/a
Future Azure prodprod (per ADR-022)OIDC, separate Entra app gha-sv0-platform-deploy-prodExisting prod.* WorkOS scoped to that envn/a

Why three Entra apps: ADR-024 §2 rejects reusing one app across tiers — federated credentials union RBAC at the SP, so one leaked GHA token would inherit every tier's blast radius. The pattern is one Entra app per blast-radius tier, multiple federated credentials within (each tied to a repo:OWNER/REPO:environment:NAME subject).

Cross-tier secrets that survive every migration (because the underlying resource is one):

  • CLOUDFLARE_API_TOKEN — one Cloudflare zone (securityv0.com) serves all tiers
  • CLOUDFLARE_ZONE_ID — same
  • CLOUDFLARE_ACCOUNT_ID — same
  • CLOUDFLARE_API_TOKEN_ZERO_TRUST — one Zero Trust org
  • CF_ACCESS_CLIENT_ID_DEPLOY / _SECRET_DEPLOY — service tokens valid for all CF Access apps (dev, prod, PR-preview)
  • CF_ACCESS_CLIENT_ID_VISUAL / _SECRET_VISUAL — same shape, different token

Deferred cleanup checklist (do not act on these until the trigger fires):

  • When dev DNS flips to Azure: delete dev.DEPLOY_* triple, retire deploy-dev.yml, decide fate of pr-preview-admin.yml (Hetzner-tied helper) and bootstrap-cf-access.yml (Hetzner-perimeter CF Access app)
  • When prod migrates to Azure: delete prod.DEPLOY_* triple, retire deploy-prod.yml
  • When staging tier exists: add staging GitHub Environment, set staging.WORKOS_* and staging.SESSION_COOKIE_PASSWORD, register the new Entra app's federated credential

Adding a new secret — checklist

When you add a secrets.* reference to a workflow:

  1. Scope it correctly. Use the most-restrictive scope that works. Environment scope is preferred over repo scope when the value differs per env. Repo scope is fine for cross-environment values (e.g. CF Access service tokens used by both deploy and visual-review).
  2. Document the value's provenance. If it came from WorkOS / Cloudflare / Azure, link the dashboard URL or runbook entry that says where to regenerate it. Put that link in the runbook (workos-production-configuration.md, cf-access-service-token-setup.md), not in this inventory — this doc covers the name → consumer → purpose mapping; runbooks own value provenance and rotation.
  3. Add a row to this inventory in the same PR that adds the workflow reference. Inventory drift is the failure mode this doc exists to prevent.
  4. Match the prefix convention. Use STAGING_ / PROD_ only when the value has a per-environment runtime difference AND will be co-mounted into the same container alongside its sibling (the docker-compose pattern). For repo-scope values that are the same across environments, no prefix.
  5. Add a deletion plan in the Status column when the secret supports a deprecated code path. Include the PR number that removes it, and the pre-condition that must be true before deletion is safe.

Composite-action blind spot: the audit-drift command below scans secrets.* references in workflow YAMLs only. A reusable workflow or composite action that consumes secrets via secrets: inherit would not appear in the grep output. As of this writing, no sv0 repo uses that pattern, so the audit is exhaustive. If we adopt composite actions later, augment the audit to follow uses: references.


Local .env credentials (agent discovery surface)

The tables above cover GitHub-side secrets (consumed by Actions workflows). The parallel local-side store is <repo>/.env — gitignored, per-repo, holds the values an engineer or authorized agent needs when running scripts, hitting deployed APIs, or developing locally.

This section answers the question every fresh-context agent asks: "Do I have to bother the user for this credential, or does it already live somewhere on disk?" The answer for every key below is: on disk in <repo>/.env — check there first.

Authorization: authorized agents (Claude Code, Codex) may read .env values. The values themselves come from 1Password — that's the user's bootstrap path, not an agent discovery surface (agents have no 1Password access; ask if a key is missing).

Rules for agents reading .env:

  • Never commit .env or paste its values into PR descriptions, commit messages, issue comments, session notes, or logs.
  • Never write .env values to .scratch/ or .claude/session-notes/.
  • If a key is missing or appears stale, ask the user — don't invent or fall back silently.
  • Treat <repo>/.env.example as the schema-of-record. If a key is in .env but not in .env.example, that's drift worth fixing in the same PR you discover it.

sv0-platform .env (canonical store for platform-runtime + agent toolkit)

Two categories of keys coexist in one file. Platform-runtime keys are read by src/shared/config/env.ts at API boot. Agent-toolkit keys are read by scripts and ad-hoc agent automation.

Platform runtime

KeyPurposeGH-secret mirror
NODE_ENV, PORT, LOG_LEVEL, CORS_ALLOWED_ORIGINS, TENANT_HEADER, UI_PORTStandard runtime config.n/a
MONGODB_URI, MONGODB_DBLocal Mongo connection. MONGODB_DB overrides the URI path component — the Mongo client takes (uri, dbName) separately, so the URI path is ignored.n/a (CI uses ephemeral Mongo)
AUTH_PROVIDER, REQUIRE_AUTHAuth gate selection. dev provider for local; workos for deployed.n/a
WORKOS_API_KEY, WORKOS_CLIENT_ID, WORKOS_AUTHKIT_DOMAIN, WORKOS_REDIRECT_URI_ALLOWED_HOSTS, WORKOS_SUPER_ADMIN_ORG_ID, WORKOS_WEBHOOK_SECRETWorkOS staging tenant — server-side auth, super-admin signal, redirect allowlist, webhook receiver.dev env WORKOS_*
PROD_WORKOS_API_KEY, PROD_WORKOS_CLIENT_ID, PROD_WORKOS_AUTHKIT_DOMAINWorkOS prod tenant — only for prod-tier testing or cross-tenant migrations.prod env WORKOS_*
SESSION_COOKIE_PASSWORDiron-session seal for sv0_session.dev + prod env SESSION_COOKIE_PASSWORD
TUNNEL_TOKENCloudflare Tunnel token (only needed with --profile tunnel).n/a
STAGING_WORKOS_APP_CLAUDECODE_CLIENT_ID/_SECRETStaff CLI Connect App (device_code grant). Required for npm run auth:login.dev env (same names)
STAGING_M2M_SPIKE_CLIENT_ID/_SECRETService-principal Connect App pair. Used by ad-hoc M2M agent scripts.none — local-only
ALLOWED_API_KEYS, API_KEY_HEADERAPI-key auth (reserved, planned per sv0-platform#827). Inert today.n/a

Agent toolkit

KeyPurposeGH-secret mirror
CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRETCF Access perimeter bypass for deployed envs. Auto-loaded by scripts/lib/load-env.ts in every visual-*.ts script — no source .env needed.repo CF_ACCESS_CLIENT_ID_VISUAL / _DEPLOY
CLOUDFLARE_ACCOUNT_IDSecurityV0 Cloudflare account ID.repo CLOUDFLARE_ACCOUNT_ID
CLOUDFLARE_API_TOKENZone:DNS:Edit + Pages publish — same token works for both (account-scoped). Used for ad-hoc CF API curl from scripts.repo CLOUDFLARE_API_TOKEN
CLOUDFLARE_API_TOKEN_ZERO_TRUSTSeparate token scoped to the Zero Trust org — CF Access app + policy management.repo CLOUDFLARE_API_TOKEN_ZERO_TRUST
CLOUDFLARE_API_TOKEN_SV0_TERRAFORMTerraform-scoped CF token used by the TFC backend for sv0-infrastructure.none — used from TFC, not GHA
GH_PAT_ROUTINEPersonal access token with cross-repo write — needed for sprint reviews, weekly PM cleanup, cross-repo issue labelling. The built-in GITHUB_TOKEN in GHA is repo-scoped only.none — local-only
GRAFANA_API_TOKENGrafana Cloud read token — query Loki/Prom/Tempo from scripts, read dashboards/alerts.none — local-only
BETTER_STACK_CLAUDECODE_GLOBAL_API_TOKENBetterStack (Logs + Heartbeats) global API token, scoped to claudecode-* sources.none — local-only

Other repos

Repo.env keysPurpose
sv0-websiteCLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, GH_PAT_ROUTINEMarketing site build/deploy + automation scripts.
sv0-documentationNOTION_TOKEN, NOTION_TOKEN_SECURITYV0Notion API for documentation pipelines (research import).
sv0-connectors(no .env at repo root)Per-integration .env files live under integrations/<connector>/.env; see each connector's README.

Drift between .env and .env.example

<repo>/.env.example is the schema-of-record. The CI of each repo doesn't validate this, so drift accumulates silently. As of 2026-05-15, sv0-platform/.env.example covers every key in the live .env. If you add a new credential to .env, add it to .env.example (with a placeholder + comment) in the same PR.

To check drift locally:

diff \
<(grep -E "^[A-Z_][A-Z0-9_]*=" .env | sed 's/=.*//' | sort -u) \
<(grep -E "^#?\s*[A-Z_][A-Z0-9_]*=" .env.example | sed 's/=.*//' | sed 's/^# *//' | sort -u)

Empty output = clean.


Audit drift check

To verify this inventory matches the workflow YAML across every sv0 repo:

for repo in sv0-platform sv0-connectors sv0-documentation sv0-intelligence sv0-website sv0-skills sv0-demo-labs; do
echo "=== $repo ==="
find ~/dev/securityv0/repos/$repo/.github/workflows -name "*.yml" 2>/dev/null \
| xargs grep -lE "secrets\." 2>/dev/null | while read f; do
grep -oE "secrets\.[A-Z_][A-Z0-9_]+" "$f" | sort -u | while read s; do
echo " $s -> $(basename $f)"
done
done | sort -u
done

The output should match the Workflows that read it columns above. A secrets.* reference in the audit output that's missing from the inventory is documentation drift — open a PR to add the row. A row in the inventory that's absent from the audit output is dead — open a PR to delete the row (and the secret in GitHub if no other consumer exists).