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 apiread-only this session. No mutations.
1. Scope¶
Five repos under JetaFutures org:
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-b2279dc72f5fAZURE_SUBSCRIPTION_ID = 8c48ba4e-7384-4579-a6e2-9aeb6c5a5de9AZURE_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_TOKENVITE_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_TOKENVITE_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:
For each of ftl-backend and ftl-frontend:
- https://github.com/JetaFutures/
/settings/environments - Click production.
- Tick Required reviewers β add
@amalkrsihna(+ ideally a second operator). - Tick Deployment branches β restrict to
release/prod. - Wait timer:
0. - 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:
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]β directgh apicalls this session. - Β§6: action checklist derived from
[Measured]gaps; effort labels[Estimated].