Skip to content

title: GitHub Environments + Secrets Audit (2026-06-11) status: snapshot last-updated: 2026-06-11 authors: [@amalkrsihna] audience: [operator, ops] abstract: > Measured audit of GitHub Environments, env-scoped secrets / variables, and repo-level secrets / variables across the five JetaFutures repos. Cross-referenced with what deploy-staging.yml and deploy-prod.yml reference. Gaps + fix steps for prod cutover. companion: [prod-snapshot-2026-06-11, prod-launch-runbook]


GitHub Environments + Secrets Audit (2026-06-11)

Companion: prod-snapshot-2026-06-11, prod-launch-runbook, prod-deployment-runbook.

All data via gh api read-only this session. No mutations.

1. Scope

Five repos under JetaFutures org:

ftl-backend, ftl-frontend, ftl-docs, ftl-infra, ftl-support

For each: list environments, env-scoped secrets + variables, repo-level secrets + variables, and protection rules (required reviewers, branch restrictions, wait timer).

2. Per-repo summary

Repo Environments Env Secrets Env Vars Repo Secrets Repo Vars
ftl-backend staging, production 0 3 each (AZURE_*_ID trio) 0 0
ftl-frontend staging, production 2 each 3 each (AZURE_*_ID trio) 1 (VITE_APPLICATIONINSIGHTS_CONNECTION_STRING) 0
ftl-docs none β€” β€” 0 0
ftl-infra none β€” β€” 1 (AZURE_CREDENTIALS, legacy SP JSON) 0
ftl-support none β€” β€” 0 0

3. Per-environment detail

3.1 ftl-backend / staging

  • Secrets: none.
  • Variables:
  • AZURE_CLIENT_ID = 7ff5a7b6-edb3-4452-94ef-b2279dc72f5f
  • AZURE_SUBSCRIPTION_ID = 8c48ba4e-7384-4579-a6e2-9aeb6c5a5de9
  • AZURE_TENANT_ID = 599ce68b-…
  • Protection: custom branch policy. No required reviewers.

3.2 ftl-backend / production

  • Secrets: none.
  • Variables: identical to staging (same client ID, sub, tenant).
  • Protection: custom branch policy. No required reviewers β€” critical gap; see Β§5.

3.3 ftl-frontend / staging

  • Secrets:
  • AZURE_STATIC_WEB_APPS_API_TOKEN
  • VITE_GOOGLE_CLIENT_ID
  • Variables: Azure trio (same as ftl-backend).
  • Protection: custom branch policy. No required reviewers.

3.4 ftl-frontend / production

  • Secrets:
  • AZURE_STATIC_WEB_APPS_API_TOKEN
  • VITE_GOOGLE_CLIENT_ID
  • Variables: Azure trio.
  • Protection: custom branch policy. No required reviewers.

3.5 ftl-docs

No environments. Builds via Cloudflare Pages directly from main. No GitHub Actions secrets needed.

3.6 ftl-infra

No environments. AZURE_CREDENTIALS is a repo-level secret holding a legacy service-principal JSON. Used by manual az deploy scripts under ftl-infra/scripts/, not by any workflow.

3.7 ftl-support

No environments. No secrets.

4. Workflow-to-secret cross-reference

4.1 ftl-backend deploy-staging.yml + deploy-prod.yml

Both workflows use OIDC via azure/login@v2. No secrets referenced; only vars:

Reference Source Staging Production
vars.AZURE_CLIENT_ID env var βœ… PRESENT βœ… PRESENT
vars.AZURE_TENANT_ID env var βœ… PRESENT βœ… PRESENT
vars.AZURE_SUBSCRIPTION_ID env var βœ… PRESENT βœ… PRESENT

No secrets.X references. appinsights-conn is set as a Container App secret on the resource itself via a separate manual step (see deploy-staging.yml lines 191–215 conditional injection logic), not via GitHub secrets.

4.2 ftl-frontend deploy-staging.yml

Reference Source Status
secrets.VITE_GOOGLE_CLIENT_ID env secret βœ… PRESENT
secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING repo secret βœ… PRESENT (envβ†’repo fallback)
secrets.AZURE_STATIC_WEB_APPS_API_TOKEN env secret βœ… PRESENT
secrets.GITHUB_TOKEN auto-provided βœ…

4.3 ftl-frontend deploy-prod.yml

Reference Source Status
secrets.VITE_GOOGLE_CLIENT_ID env secret βœ… PRESENT (production env)
secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD env secret 🚨 MISSING β€” does NOT exist anywhere
secrets.AZURE_STATIC_WEB_APPS_API_TOKEN env secret βœ… PRESENT (production env)
secrets.GITHUB_TOKEN auto-provided βœ…

5. Critical gaps for prod cutover

5.1 🚨 Missing VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD

Symptom: frontend prod build reads secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD; this secret does not exist as a production-env secret OR a repo-level secret. The repo-level VITE_APPLICATIONINSIGHTS_CONNECTION_STRING (without the _PROD suffix) is set but is not what the prod workflow reads.

Effect at prod deploy: GitHub passes an empty string for secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD. The Vite build bakes empty string into the bundle. Production browser-side AppInsights JS SDK fails to initialize β†’ zero frontend telemetry, zero exception capture, zero page-view counts.

Two fix options:

# Get the same AppInsights connection string used for staging
AZURE_CONFIG_DIR=~/.azure-ftl az monitor app-insights component show \
  -g ftl-prd-rg-cin --app ftl-prd-ai-cin \
  --query 'connectionString' -o tsv

# Set it as a secret on ftl-frontend / production
gh secret set VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD \
  --repo JetaFutures/ftl-frontend \
  --env production \
  --body '<paste connection string>'

Edit ftl-frontend/.github/workflows/deploy-prod.yml; change every secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD to secrets.VITE_APPLICATIONINSIGHTS_CONNECTION_STRING.

This uses the existing repo-level value for prod too. Simpler, but loses the ability to point prod at a different AppInsights resource if you later separate them.

Recommendation: Option A. Aligns with the env-scoped secrets pattern already in use for the Google client ID + SWA token.

5.2 🚨 No required reviewers on production environment

Both ftl-backend/production and ftl-frontend/production GitHub Environments have no required reviewers. Any push to release/prod will auto-deploy without operator approval. This is the most dangerous gap in the prod setup today.

The deploy-prod.yml header comment in both repos explicitly says:

"Configure required reviewers on the production GitHub Environment so each release/prod push pauses for human approval before the build, migrate, and roll jobs run."

This was never set.

Fix:

PAT=$(...)  # your PAT
YOUR_USER_ID=$(GH_TOKEN="$PAT" gh api user --jq '.id')

for repo in ftl-backend ftl-frontend; do
  GH_TOKEN="$PAT" gh api \
    --method PUT "repos/JetaFutures/$repo/environments/production" \
    -F "reviewers[][type]=User" -F "reviewers[][id]=$YOUR_USER_ID" \
    -F 'wait_timer=0'
done

For each of ftl-backend and ftl-frontend:

  1. https://github.com/JetaFutures//settings/environments
  2. Click production.
  3. Tick Required reviewers β†’ add @amalkrsihna (+ ideally a second operator).
  4. Tick Deployment branches β†’ restrict to release/prod.
  5. Wait timer: 0.
  6. Save protection rules.

Risk if not done: a stray PR merge to release/prod triggers an unreviewed prod deploy. Could ship a known-broken commit. The four-branch flow in prod-deployment-runbook Β§5 depends on this gate to pause for approval β€” without it, the PR merge IS the deploy.

5.3 Legacy AZURE_CREDENTIALS on ftl-infra

The ftl-infra repo has a repo-level secret AZURE_CREDENTIALS β€” service-principal JSON, classic az ad sp create-for-rbac output. Not referenced by any workflow in the repos checked. Probably used by a manual deploy script under ftl-infra/scripts/.

Recommendation: After full OIDC migration on the deploy workflows, revoke this credential and remove the secret. Holding an unrotated SP JSON is a long-tail risk per CLAUDE.md pre-launch security checklist #1.

Verify before revoke:

grep -rn 'AZURE_CREDENTIALS' ftl-infra/scripts/ ftl-infra/.github/workflows/ 2>/dev/null

If any hit: rotate the SP, plan migration to OIDC, then delete.

5.4 Backend env values do NOT differentiate staging vs prod

AZURE_CLIENT_ID, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID are identical on both environments. This is correct for the current single-sub OIDC + Managed Identity setup, but means:

  • A misconfigured service principal fails identically in both envs.
  • Cannot rotate the SP independently per env.
  • Cannot scope prod permissions tighter than staging.

Not a blocker for launch. Consider creating a prod-only federated credential post-tournament if least-privilege scoping is required.

6. Action checklist before prod cutover

[ ] Fix VITE_APPLICATIONINSIGHTS_CONNECTION_STRING_PROD (Β§5.1)
[ ] Enable required reviewers on production env in BOTH repos (Β§5.2)
[ ] (Optional) Revoke AZURE_CREDENTIALS on ftl-infra if unused (Β§5.3)
[ ] Re-verify by re-running the gh api list commands in Β§7 below

7. Re-run the audit (read-only)

PAT=$(...)

for repo in ftl-backend ftl-frontend ftl-docs ftl-infra ftl-support; do
  echo "=== $repo ==="
  echo "--- environments ---"
  GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/environments --jq '.environments[].name' 2>/dev/null
  echo "--- repo secrets ---"
  GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/actions/secrets --jq '.secrets[].name' 2>/dev/null
  echo "--- repo vars ---"
  GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/actions/variables --jq '.variables[] | {name, value}' 2>/dev/null
done

for repo in ftl-backend ftl-frontend; do
  for env in staging production; do
    echo "=== $repo / $env ==="
    echo "--- env secrets ---"
    GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/environments/$env/secrets --jq '.secrets[].name' 2>/dev/null
    echo "--- env vars ---"
    GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/environments/$env/variables --jq '.variables[] | {name, value}' 2>/dev/null
    echo "--- env protection ---"
    GH_TOKEN="$PAT" gh api repos/JetaFutures/$repo/environments/$env --jq '{protection_rules, reviewers}' 2>/dev/null
  done
done

8. Confidence

  • Β§2 / Β§3 / Β§4 / Β§5: [Measured] β€” direct gh api calls this session.
  • Β§6: action checklist derived from [Measured] gaps; effort labels [Estimated].