Skip to content

Secrets Audit And Inventory

Platform Secrets, API Tokens, Domains, and Deployment Pipeline Audit & Inventory

Source Issue: #686 (Epic #780 β€” Phase 1: Discovery & Audit)
Status: uningested (Point-in-time write-up pending Phase 2 review and /docs distill alignment)


1. Executive Summary & Audit Methodology

This report documents the comprehensive, platform-wide discovery and inventory audit of all secrets, API tokens, cryptographic keys, webhook URLs, environment variables, Cloudflare resource bindings, and domain scopes across the SiteSwarm monorepo and deployment control plane.

1.1 Audit Scope & Evidence Sources

  1. GitHub Actions Control Plane: Queried live repository secret metadata (gh secret list / GitHub Actions Secrets API), repository variables (gh variable list), and GitHub Environments (production and staging via repos/jptechnologysolutions/siteswarm/environments), and inspected all 8 workflow definitions under .github/workflows/.
  2. Cloudflare Workers & Horizontal Platform Services: Audited all 11 application manifests under apps/*/wrangler.jsonc (auto-repair, bakery, clk-construction, clk-construction-cms, command-center, docs, marketing, me-portfolio, rcc-stjohn, rcc-stpatrick, rcc-stpeter, software-agency) and all 6 platform service workers under services/*/wrangler.jsonc (cache-purge, critique, email-sender, event-platform, inquiries, watchdog), cross-referenced against platform.config.ts and platform.lock.json.
  3. Infrastructure-as-Code (infra/): Audited root Terraform modules (infra/main.tf, infra/providers.tf, infra/variables.tf, infra/outputs.tf), platform zone/Zero Trust definitions (infra/platform/main.tf, infra/platform/access.tf), and colocated application Terraform modules (apps/{auto-repair,bakery,marketing,software-agency}/infra/*.tf).
  4. Local Development Templates & Tooling Scripts: Audited root .env.example, root .dev.vars.example, all 10 apps/*/.dev.vars.example and apps/*/.env.example templates, services/{cache-purge,watchdog}/.dev.vars.example, and operational scripts (scripts/deploy-env-config.cjs, scripts/capture-demo.ts, scripts/browser-run-cdp.ts, scripts/setup-local.ts, scripts/staging-debounce.ts, scripts/verify-commit-checks.ts).

1.2 Key Headline Findings

  • Zero GitHub Environment Secret Isolation: Although staging and production GitHub Environments exist, 0 environment-scoped secrets are configured in either environment. All 6 active secrets reside at the Repository level, exposing the master CLOUDFLARE_API_TOKEN and COMMAND_CENTER_GITHUB_TOKEN to every workflow job (including PR previews and acceptance runs).
  • Single Over-Permissioned Cloudflare Omni-Token: A single repository-wide CLOUDFLARE_API_TOKEN performs Terraform state/DNS mutations, production and staging Worker deployments, dynamic D1/KV creation, ephemeral PR preview deployments/teardowns, R2 bucket object deletions, and Browser Rendering CDP sessions.
  • Plaintext wrangler.json vars Secret Injection in CI: Deployment pipelines (preview-deploy.yml, staging-deploy.yml, production-promote.yml) inject COMMAND_CENTER_GITHUB_TOKEN and COMMAND_CENTER_CLOUDFLARE_API_TOKEN into dist/server/wrangler.json under plaintext vars rather than encrypted Cloudflare Worker Secrets (wrangler secret put). Worse, preview-deploy.yml unconditionally injects COMMAND_CENTER_GITHUB_TOKEN into vars of every tenant preview Worker deployed on a PR.
  • Unset Secret Collisions & Test-Key Fallbacks: services/watchdog/wrangler.jsonc defines empty strings ("") in vars for BETTERSTACK_TOKEN, DISCORD_WEBHOOK_URL, and SLACK_WEBHOOK_URL across all environments, colliding with encrypted Worker secrets. Meanwhile, services/inquiries/src/domain.ts falls back to Cloudflare’s ALWAYS_PASSES test Turnstile secret (1x0000000000000000000000000000000AA) whenever TURNSTILE_SECRET_KEY is unset in staging or production.

2. Consolidated Platform Secrets & Token Inventory Matrix

All credentials below are cataloged strictly by identifier and metadata (zero raw secret values). Every entry is mapped to its storage location, category, target environment(s), caller/pipeline, resource/domain scope, current permissions, and Phase 2 action flag.

Secret / Identifier Storage Location(s) Category Target Environment(s) Caller / Pipeline / Runtime Consumer Resource / Domain Scope Current Permissions / Grant Scope Risk & Phase 2 Action Flag
CLOUDFLARE_API_TOKEN (CI Master Token) GitHub Repo Secret (Updated 2026-10-15); Root .env.example; infra/variables.tf (var.cloudflare_api_token) Cloudflare API Token (Deployment & Infra) Production, Staging, Client Preview, Ephemeral PR Preview, CI/CD, Local CLI production-promote.yml, staging-deploy.yml, preview-deploy.yml, preview-teardown.yml, client-preview-deploy.yml, acceptance-test.yml, demo-capture.yml, scripts/capture-demo.ts, scripts/browser-run-cdp.ts Account-wide across all Zones (jacobmiller22.com, siteswarm.dev, client domains), all Workers (apps/*, services/*), D1 (*-d1, siteswarm-*-db), KV (*-session, watchdog-state*), R2 (siteswarm-demos, *-media*), Access apps, Browser Run CDP Broad Account/Zone Admin (Workers Scripts:Edit, Workers KV:Edit, Workers Routes:Edit, D1:Edit, R2:Edit, Browser Rendering:Edit, Zero Trust/Access:Edit, Zone DNS:Edit, Account Settings:Read) πŸ”΄ CRITICAL β€” (3) Re-scope to Least Privilege & (4) Re-architect/Migrate Pattern: Shared across PR previews and prod; split per environment & function (#781)
CLOUDFLARE_ACCOUNT_ID GitHub Repo Secret (Created 2026-09-30); Root .env.example; services/watchdog/.dev.vars.example; services/watchdog/wrangler.jsonc (vars); infra/variables.tf Cloudflare Account Identifier Production, Staging, Client Preview, Ephemeral PR Preview, CI/CD, Local Dev All 7 Cloudflare workflows; services/watchdog (evaluator.ts); apps/command-center (cloudflare-quota.ts, cloudflare-graphql.ts, observability.astro); infra/ Single shared Cloudflare Account hosting platform and tenant Workers, D1, KV, R2, and Zones Identifier only (paired with CLOUDFLARE_API_TOKEN) 🟑 MEDIUM β€” (2) Consolidate & (4) Re-architect: Remove empty "" override in services/watchdog/wrangler.jsonc and fix TF_VAR_cloudflare_account_id wiring
COMMAND_CENTER_CLOUDFLARE_API_TOKEN GitHub Repo Secret (Created 2026-11-05); injected into dist/server/wrangler.json vars.CLOUDFLARE_API_TOKEN Cloudflare API Token (Telemetry & Quota) Staging (staging-deploy.yml only; omitted in production-promote.yml) staging-deploy.yml -> apps/command-center (cloudflare-quota.ts, cloudflare-graphql.ts) Cloudflare Account GraphQL Analytics API (/client/v4/graphql) and Account/Zone quota endpoints Read-Only (Account Analytics:Read, Workers Scripts:Read, D1:Read, Workers KV:Read, R2:Read, Zone Analytics:Read) πŸ”΄ HIGH β€” (4) Re-architect/Migrate Pattern: Injected into plaintext wrangler.json vars instead of encrypted Worker Secret; missing in production-promote.yml
COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID GitHub Repo Secret (Created 2026-11-05); injected into vars.CLOUDFLARE_ACCOUNT_ID Cloudflare Account Identifier Staging (staging-deploy.yml only) staging-deploy.yml -> apps/command-center (cloudflare-quota.ts, cloudflare-graphql.ts, observability.astro) Same Cloudflare Account as CLOUDFLARE_ACCOUNT_ID Identifier only 🟑 LOW β€” (2) Consolidate: Duplicate of CLOUDFLARE_ACCOUNT_ID; unify under non-secret platform config or Terraform output
COMMAND_CENTER_CLOUDFLARE_ZONE_ID GitHub Repo Secret (Created 2026-11-05); injected into vars.CLOUDFLARE_ZONE_ID Cloudflare Zone Identifier Staging (staging-deploy.yml only) staging-deploy.yml -> apps/command-center (cloudflare-quota.ts, index.astro) Primary platform zone jacobmiller22.com Identifier only 🟑 LOW β€” (2) Consolidate: Source from Terraform platform_zone_id output or platform.lock.json; wire into production promote
COMMAND_CENTER_GITHUB_TOKEN GitHub Repo Secret (Updated 2026-11-01); injected into dist/server/wrangler.json vars.GITHUB_TOKEN GitHub PAT / Fine-Grained Token Ephemeral PR Preview (preview-deploy.yml), Staging (staging-deploy.yml), Production (production-promote.yml) preview-deploy.yml (all apps!), staging-deploy.yml (command-center), production-promote.yml (command-center) -> blacksmith.ts, github-stages.ts GitHub repository jptechnologysolutions/siteswarm Actions & workflow run telemetry GitHub Actions Read (actions:read, metadata:read) πŸ”΄ CRITICAL β€” (4) Re-architect/Migrate Pattern: Stored in plaintext wrangler.json vars, and leaked into every tenant PR preview Worker in preview-deploy.yml
GITHUB_TOKEN / GH_TOKEN GitHub Actions built-in installation token (${{ secrets.GITHUB_TOKEN }} / ${{ github.token }}); Local gh CLI Ephemeral CI / GitHub App Token CI/CD Pipeline & Local CLI ci.yml (audit-pr-reviews.ts), staging-deploy.yml (verify-commit-checks.ts, staging-debounce.ts), production-promote.yml (verify-commit-checks.ts), preview-deploy.yml & preview-teardown.yml (github-script) Repository jptechnologysolutions/siteswarm commits, checks, deployments, PR comments Per-workflow permissions: (contents:read, pull-requests:write, issues:write, deployments:write, checks:read, statuses:read) 🟑 MEDIUM β€” (3) Re-scope to Least Privilege: Add explicit permissions: block to ci.yml, acceptance-test.yml, and demo-capture.yml
R2_ACCESS_KEY_ID & R2_SECRET_ACCESS_KEY (+ AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) Referenced in acceptance-test.yml, demo-capture.yml, scripts/capture-demo.ts; Unset in GitHub Secrets Cloudflare R2 S3-Compatible API Key Pair CI/CD Pipeline (demo-capture.yml, acceptance-test.yml), Local CLI (scripts/capture-demo.ts) scripts/capture-demo.ts (S3 PutObject and 7-day AWS SigV4 GetObject presigned URL signing) Private R2 bucket siteswarm-demos (WNAM) Object Read & Write on siteswarm-demos (currently falls back to CLOUDFLARE_API_TOKEN because unset) 🟑 MEDIUM β€” (3) Re-scope to Least Privilege: Provision bucket-scoped R2 credentials for siteswarm-demos so demo capture jobs do not require account-wide CLOUDFLARE_API_TOKEN
CF_WORKERS_DOMAIN Referenced as ${{ secrets.CF_WORKERS_DOMAIN }} in 4 workflows; Unset in GitHub Secrets Platform Configuration String Ephemeral PR Preview, Client Preview, Staging, Production preview-deploy.yml, client-preview-deploy.yml, staging-deploy.yml, production-promote.yml (falls back to jacobmillerdev.workers.dev) Account workers.dev subdomain (*.jacobmillerdev.workers.dev) Non-sensitive hostname suffix 🟒 LOW β€” (1) Deprecate/Delete (as Secret) & (2) Consolidate: Move to platform.config.ts / platform.lock.json
TURNSTILE_SECRET_KEY Root & 10 apps/*/.dev.vars.example; services/inquiries/wrangler.jsonc (vars); apps/clk-construction/wrangler.jsonc (vars in default/preview, Worker Secret in prod) Cloudflare Turnstile Bot Verification Secret Local Dev, Ephemeral PR Preview, Staging, Production services/inquiries/src/domain.ts, apps/clk-construction/src/lib/quote-handler.ts, @siteswarm/inquiries (handler.ts) Cloudflare Turnstile siteverify API for inquiries.jacobmiller22.com and clkconstructioninc.com Turnstile token verification (https://challenges.cloudflare.com/turnstile/v0/siteverify) πŸ”΄ HIGH β€” (4) Re-architect/Migrate Pattern & (1) Deprecate/Delete: Fix services/inquiries fallback to ALWAYS_PASSES test key in prod/staging; prune unused TURNSTILE_SECRET_KEY from 9 client .dev.vars.example files
TURNSTILE_SITE_KEY apps/clk-construction/wrangler.jsonc (vars in default & preview: 1x00000000000000000000AA) Cloudflare Turnstile Public Widget Key Local Dev, Ephemeral PR Preview, Staging, Production apps/clk-construction/src/pages/contact.astro (<QuoteForm />) Public client-side Turnstile widget rendering on clk-construction Public site key (non-secret) 🟒 LOW β€” (4) Re-architect: Populate production/staging site key in wrangler.jsonc vars at launch (#460)
ADMIN_API_KEY services/{inquiries,cache-purge,critique}/wrangler.jsonc (top-level vars: "dev-admin-key"; omitted in staging/production) Internal Service Bearer Token Local Dev (dev-admin-key), Staging & Production (Worker Secret) services/inquiries/src/router.ts (GET/PATCH /inquiries), services/cache-purge/src/admin.ts (/admin/*), services/critique/src/types.ts (unused in router.ts) Worker HTTP admin endpoints on inquiries.jacobmiller22.com, siteswarm-cache-purge, critique.jacobmiller22.com Privileged tenant inquiry listing/mutation and cache-purge ledger inspection 🟠 HIGH β€” (4) Re-architect/Migrate Pattern: Codify Worker secret provisioning or migrate to Cloudflare Access Service Tokens (#782); remove unused binding in services/critique or enforce in router.ts
CLOUDFLARE_API_TOKEN (Cache Purge Worker Runtime) services/cache-purge/.dev.vars.example; Worker Secret on siteswarm-cache-purge (staging & production) Cloudflare API Token (Zone Cache Purge) Local Dev (dev recorder when absent), Staging, Production services/cache-purge/src/consumer.ts & cloudflare.ts Tenant zones mapped in TENANT_ZONES (POST /client/v4/zones/:zone_id/purge_cache) Zone > Cache Purge:Purge only 🟠 MEDIUM β€” (3) Re-scope to Least Privilege: Rename to CACHE_PURGE_CF_API_TOKEN to prevent collision with CI master token and scope strictly to Zone > Cache Purge:Purge
ANALYTICS_READ_TOKEN (+ CLOUDFLARE_API_TOKEN fallback in Watchdog) services/watchdog/.dev.vars.example; Worker Secret on siteswarm-service-watchdog (staging & production) Cloudflare API Token (WAE SQL Reader) Local Dev, Staging, Production services/watchdog/src/evaluator.ts (env.CLOUDFLARE_API_TOKEN ?? env.ANALYTICS_READ_TOKEN) Cloudflare Account WAE SQL API (/client/v4/accounts/:id/analytics_engine/sql) for dataset siteswarm_domain_metrics Account > Account Analytics:Read only 🟑 MEDIUM β€” (2) Consolidate & (3) Re-scope: Remove CLOUDFLARE_API_TOKEN fallback in services/watchdog and standardize solely on least-privilege ANALYTICS_READ_TOKEN
BETTERSTACK_TOKEN (+ BETTERSTACK_API_URL) services/watchdog/.dev.vars.example; services/watchdog/wrangler.jsonc (vars: "" across all envs) Third-Party SaaS Token (BetterStack On-Call) Local Dev, Staging (scripts/drill-alerting.ts), Production services/watchdog/src/dispatch/betterstack.ts BetterStack Uptime API v2 (https://uptime.betterstack.com/api/v2/incidents) Create and resolve P1/P2 on-call incidents πŸ”΄ HIGH β€” (4) Re-architect/Migrate Pattern: Remove "BETTERSTACK_TOKEN": "" from services/watchdog/wrangler.jsonc vars (which overwrites Worker secrets) and provision via wrangler secret put / Terraform (#782)
DISCORD_WEBHOOK_URL & SLACK_WEBHOOK_URL services/watchdog/.dev.vars.example; services/watchdog/wrangler.jsonc (vars: "" across all envs) Third-Party SaaS Webhook URLs (Embedded Secret Tokens) Local Dev, Staging, Production services/watchdog/src/dispatch/webhooks.ts Discord & Slack incident alert channels Post P1/P2 firing and recovery alert embeds πŸ”΄ HIGH β€” (4) Re-architect/Migrate Pattern: Remove empty "" entries from services/watchdog/wrangler.jsonc vars and bind strictly as encrypted Worker Secrets (#782)
STAFF_GATE_USER, STAFF_GATE_PASSWORD, STAFF_GATE_ENABLED, ALLOW_DEV_AUTH Runtime env in apps/command-center/src/lib/middleware-handler.ts & @siteswarm/governance/security; Unset in wrangler.jsonc / CI HTTP Basic Auth Fallback Credentials Local Dev, Ephemeral PR Preview, Client Preview, Staging, Production apps/command-center/src/lib/middleware-handler.ts, packages/governance/src/security/client-preview.ts command-center.jacobmiller22.com, command-center-staging.jacobmiller22.com, *-preview.jacobmiller22.com/admin* Grants admin role in Command Center when Cf-Access-Jwt-Assertion is absent (defaults to "admin" / "admin" in code!) πŸ”΄ CRITICAL β€” (4) Re-architect/Migrate Pattern: Default "admin" / "admin" fallback combined with workers_dev: true and missing prod Access policy allows unauthenticated Zero Trust bypass
CF_ACCESS_AUD, CF_ACCESS_ISSUER, CF_ACCESS_TEAM_DOMAIN Runtime env in apps/command-center/src/lib/middleware-handler.ts & auth.ts; infra/variables.tf (cloudflare_team_domain = "siteswarm") Cloudflare Zero Trust OIDC / JWT Verification Config Staging, Production, Client Preview apps/command-center/src/lib/middleware-handler.ts -> authenticateAccessRequest Cloudflare Access JWT (Cf-Access-Jwt-Assertion) RS256 JWKS verification Cryptographic audience (aud) and issuer validation πŸ”΄ HIGH β€” (4) Re-architect/Migrate Pattern: CF_ACCESS_AUD and CF_ACCESS_TEAM_DOMAIN are not wired from Terraform (infra/outputs.tf) into apps/command-center/wrangler.jsonc
sk_bakery_pos_relay_secret Hardcoded string literal in apps/bakery/src/pages/api/catering-pos-relay.ts (line 18) Webhook Bearer Secret (Demo POS Relay) All apps/bakery environments apps/bakery/src/pages/api/catering-pos-relay.ts (POST /api/catering-pos-relay) green-leaf-bakery Worker /api/catering-pos-relay endpoint Validates Authorization: Bearer header only when header is present (unauthenticated requests without header pass!) 🟑 MEDIUM β€” (1) Deprecate/Delete or (4) Re-architect: Remove hardcoded literal secret and enforce fail-closed check or document as mock-only demo route
RESEND_API_KEY, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN Root .dev.vars.example (lines 10–12) Third-Party Email / SMS SaaS Keys (Legacy) Local Dev template only Unmapped / 0 Callers across apps/, services/, packages/, scripts/ None (superseded by Cloudflare Email Routing send_email bindings) Mock strings only 🟒 LOW β€” (1) Deprecate/Delete: Purge dead legacy placeholders from root .dev.vars.example
ADMIN_SECRET_PIN & SESSION_SECRET Root .dev.vars.example (7392, siteswarm_local_offline_session_secret_32chars_min); apps/bakery/.dev.vars.example (ADMIN_SECRET_PIN=7392) Legacy Admin PIN & Session Secret Local Dev template only Unmapped / 0 Callers (packages/capabilities/cms/src/server/auth.ts explicitly bans ambient process.env PINs) None Mock strings only 🟒 LOW β€” (1) Deprecate/Delete: Purge unused legacy secrets from .dev.vars.example and apps/bakery/.dev.vars.example
SCOUT_MODEL_API_KEY scripts/scout/model/adapter.ts (line 73) AI Model API Key (Offline CLI Harness) Local CLI / Developer Tooling scripts/scout/model/adapter.ts Local Scout model evaluation scripts External LLM inference when invoked locally 🟒 COMPLIANT β€” Retain: Isolated to local developer CLI tooling with hermetic test mocks

3. Environment & Pipeline Usage Mapping

3.1 GitHub Actions Secrets vs. GitHub Environments Topology

Although CI_DEPLOYMENT_ENVIRONMENTS.md specifies that deployment credentials (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID) are scoped per GitHub Environment (preview, staging, production), live inspection of the repository via the GitHub API reveals a flat repository-level credential store:

GitHub Scope Configured Secrets (gh secret list) Configured Variables (gh variable list) Workflows Executing in Scope Security Posture & Gap Analysis
Repository Level (repos/jptechnologysolutions/siteswarm/actions/secrets) CLOUDFLARE_ACCOUNT_ID
CLOUDFLARE_API_TOKEN
COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID
COMMAND_CENTER_CLOUDFLARE_API_TOKEN
COMMAND_CENTER_CLOUDFLARE_ZONE_ID
COMMAND_CENTER_GITHUB_TOKEN
0 variables configured All workflows (ci.yml, preview-deploy.yml, preview-teardown.yml, client-preview-deploy.yml, staging-deploy.yml, production-promote.yml, acceptance-test.yml, demo-capture.yml) πŸ”΄ Flat Blast Radius: Every workflow job in the repository inherits access to the production-capable CLOUDFLARE_API_TOKEN and COMMAND_CENTER_* secrets.
GitHub Environment: preview Environment not registered in GitHub API (referenced dynamically by client-preview-deploy.yml; omitted by preview-deploy.yml) 0 variables client-preview-deploy.yml (deploy-client-preview job declares environment: preview; preview-deploy.yml declares no environment: key) 🟠 Inconsistent Environment Binding: preview-deploy.yml and preview-teardown.yml run outside any GitHub Environment while consuming repository-level CLOUDFLARE_API_TOKEN.
GitHub Environment: staging 0 environment secrets configured 0 variables staging-deploy.yml (terraform-apply-staging, staging-deploy) 🟠 No Credential Separation: Relies entirely on repository-level CLOUDFLARE_API_TOKEN instead of a staging-scoped token.
GitHub Environment: production 0 environment secrets configured 0 variables production-promote.yml (terraform-apply-production, production-promote) πŸ”΄ No Credential Separation: Production promotion uses the exact same CLOUDFLARE_API_TOKEN exposed to PR preview jobs.

3.2 Workflow-by-Workflow Secret & API Caller Matrix

flowchart LR
subgraph GH["GitHub Repository Secrets (Flat Scope)"]
CF_TOK["CLOUDFLARE_API_TOKEN\n(Master Omni-Token)"]
CC_GH["COMMAND_CENTER_GITHUB_TOKEN"]
CC_CF["COMMAND_CENTER_CLOUDFLARE_API_TOKEN"]
end
subgraph Workflows["GitHub Actions Workflows"]
PR["preview-deploy.yml &\npreview-teardown.yml"]
CP["client-preview-deploy.yml"]
STG["staging-deploy.yml"]
PROD["production-promote.yml"]
ACC["acceptance-test.yml &\ndemo-capture.yml"]
end
CF_TOK -->|"wrangler deploy/delete, KV CRUD,\ncustom domains, R2 DELETE"| PR
CF_TOK -->|"wrangler deploy/delete"| CP
CF_TOK -->|"terraform apply, D1 CRUD/migrate,\nKV CRUD, wrangler deploy"| STG
CF_TOK -->|"terraform apply, D1 CRUD/migrate,\nKV CRUD, wrangler deploy"| PROD
CF_TOK -->|"Browser Run CDP WebSocket &\nR2 siteswarm-demos upload"| ACC
CC_GH -->|"Leaked into vars.GITHUB_TOKEN\nof ALL PR preview apps!"| PR
CC_GH -->|"Plaintext vars.GITHUB_TOKEN\n(command-center only)"| STG
CC_GH -->|"Plaintext vars.GITHUB_TOKEN\n(command-center only)"| PROD
CC_CF -->|"Plaintext vars.CLOUDFLARE_API_TOKEN\n(command-center staging only)"| STG
Workflow File Job / Step Secrets Consumed Exact Cloudflare / External API Operations Invoked
.github/workflows/ci.yml gate (audit-pr-reviews.ts) github.token (GH_TOKEN) Reads GitHub PR reviews and commit status checks via scripts/audit-pr-reviews.ts. Zero Cloudflare secrets consumed (NODE_ENV=test hermetic unit tests).
.github/workflows/preview-deploy.yml preview-deploy & preview-acceptance (secrets: inherit) CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CF_WORKERS_DOMAIN (unset), COMMAND_CENTER_GITHUB_TOKEN 1. GET /client/v4/accounts/:id/workers/scripts (service binding pre-check)
2. GET & POST /client/v4/accounts/:id/storage/kv/namespaces (creates <worker>-session KV)
3. wrangler deploy --name <worker>-preview-pr-<N> (attaches <app>-preview-pr-<N>.jacobmiller22.com custom domain)
4. Injects COMMAND_CENTER_GITHUB_TOKEN into config.vars.GITHUB_TOKEN for all apps
5. Passes all secrets via secrets: inherit to acceptance-test.yml.
.github/workflows/preview-teardown.yml teardown-previews CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, github.token 1. wrangler delete --name <worker>-preview-pr-<N> --force
2. GET & DELETE /client/v4/accounts/:id/storage/kv/namespaces/:ns_id
3. GET & DELETE /client/v4/accounts/:id/workers/domains/:domain_id
4. DELETE /client/v4/accounts/:id/r2/buckets/siteswarm-demos/objects/:key.
.github/workflows/client-preview-deploy.yml deploy-client-preview CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CF_WORKERS_DOMAIN (unset) 1. wrangler deploy --name <app>-client-preview (binds <app>-preview.jacobmiller22.com & <app>.preview.jacobmiller22.com)
2. wrangler delete --name <app>-client-preview --force (when decommission=true).
.github/workflows/staging-deploy.yml preflight-debounce, terraform-apply-staging, staging-deploy, staging-acceptance, record-staging-deployment GITHUB_TOKEN, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CF_WORKERS_DOMAIN (unset), COMMAND_CENTER_GITHUB_TOKEN, COMMAND_CENTER_CLOUDFLARE_API_TOKEN, COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID, COMMAND_CENTER_CLOUDFLARE_ZONE_ID 1. terraform init & terraform apply in infra/
2. GET & POST /client/v4/accounts/:id/d1/database (auto-creates staging D1 DBs)
3. wrangler d1 execute <binding> --file schema.sql --env staging --remote
4. GET /client/v4/accounts/:id/workers/scripts
5. GET & POST /client/v4/accounts/:id/storage/kv/namespaces
6. wrangler deploy (with COMMAND_CENTER_* injected into plaintext distConfig.vars).
.github/workflows/production-promote.yml resolve-apps, terraform-apply-production, production-promote, production-acceptance GITHUB_TOKEN, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CF_WORKERS_DOMAIN (unset), COMMAND_CENTER_GITHUB_TOKEN 1. terraform init & terraform apply in infra/
2. GET & POST /client/v4/accounts/:id/d1/database (auto-creates prod D1 DBs)
3. wrangler d1 execute <binding> --file schema.sql --remote
4. GET & POST /client/v4/accounts/:id/storage/kv/namespaces
5. wrangler deploy to production custom domains.
.github/workflows/acceptance-test.yml edge-acceptance (workflow_call) CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, R2_ACCESS_KEY_ID (unset), R2_SECRET_ACCESS_KEY (unset) Connects Playwright over remote WebSocket to Cloudflare Browser Rendering CDP (wss://api.cloudflare.com/client/v4/accounts/:id/browser-rendering/cdp) when run_playwright == true in staging.
.github/workflows/demo-capture.yml capture-demo (workflow_dispatch) CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, R2_ACCESS_KEY_ID (unset), R2_SECRET_ACCESS_KEY (unset) Runs scripts/capture-demo.ts: connects to Cloudflare Browser Rendering CDP, captures dual-viewport MP4/WebP walkthroughs, uploads objects to private R2 bucket siteswarm-demos, and signs 7-day presigned URLs.

4. Domain, Zone & Cloudflare Resource Scope Inventory

4.1 Authoritative Cloudflare Zones & Domain Boundaries

Zone / Domain Zone Role & Management Bound Environments Workers & Hostname Patterns Covered Required Zone Permission Scope
jacobmiller22.com Primary Authoritative Platform Zone (platform.config.ts, infra/platform/main.tf, COMMAND_CENTER_CLOUDFLARE_ZONE_ID) Production (me-portfolio, command-center, docs, inquiries, critique, *-ingress), Staging (*-staging), Client Preview (*-preview, *.preview), Ephemeral PR Preview (*-preview-pr-<N>) β€’ Apex & WWW: jacobmiller22.com, www.jacobmiller22.com (me-portfolio)
β€’ Fallback Origin: fallback.jacobmiller22.com (192.0.2.1 proxied A record)
β€’ Wildcards: *.preview.jacobmiller22.com, *-preview.jacobmiller22.com
β€’ Platform Services: inquiries.jacobmiller22.com, critique.jacobmiller22.com, command-center.jacobmiller22.com, docs.jacobmiller22.com
β€’ Staging Fleet: <app>-staging.jacobmiller22.com, staging.jacobmiller22.com
β€’ Dual-Route Ingress: <app>-ingress.jacobmiller22.com
Zone > DNS:Edit, Zone > Workers Routes:Edit, Zone > SSL and Certificates:Edit (Custom Hostnames), Zone > Cache Purge:Purge
siteswarm.dev Secondary Platform / Marketing Front-Door Zone (apps/marketing/infra/main.tf via var.production_zone_id) Production (apps/marketing) β€’ siteswarm.dev, marketing.siteswarm.dev (siteswarm-marketing)
β€’ Note: apps/{auto-repair,bakery,software-agency}/swarm.config.ts still reference legacy staging.<app>.siteswarm.dev in deployment.stagingDomain while wrangler.jsonc routes to <app>-staging.jacobmiller22.com.
Zone > DNS:Edit, Zone > Workers Routes:Edit
Client Production Custom Domains External Client Domains / Custom Hostnames (apps/*/wrangler.jsonc env.production.routes) Production Only β€’ apexautoservice.com (siteswarm-app-auto-repair)
β€’ greenleafbakery.com (green-leaf-bakery)
β€’ clkconstructioninc.com (clk-construction)
β€’ stjohnscatholicchurch.org (rcc-stjohn)
β€’ saintpatrickchurchhill.org (rcc-stpatrick)
β€’ stpeterchurch1834.org (rcc-stpeter)
β€’ apexlabs.dev (apex-labs)
Custom Hostname Ingress via fallback.jacobmiller22.com or per-zone Workers Routes:Edit + Cache Purge:Purge
*.jacobmillerdev.workers.dev Account-Level Workers Dev Subdomain (CF_WORKERS_DOMAIN) Preview, Client Preview, Staging, and Production (via CI override) β€’ <worker>-preview-pr-<N>.jacobmillerdev.workers.dev
β€’ <app>-client-preview.jacobmillerdev.workers.dev
β€’ <worker>-staging.jacobmillerdev.workers.dev
β€’ <prod-worker>.jacobmillerdev.workers.dev (unintentionally forced on in production-promote.yml line 2082)
Account-level Workers Scripts:Edit

4.2 Worker-Level Resource, Binding & Secret Scope Matrix (apps/* & services/*)

Workspace Package / Path Production Worker Name Staging Worker Name Service Bindings Stateful Bindings (D1 / KV / R2 / Queues / Email) Runtime Secrets & Plaintext vars CI Deployment Status
apps/auto-repair siteswarm-app-auto-repair auto-repair-staging INQUIRIES_SERVICE None vars: ENVIRONMENT, COMMIT_SHA, PUBLIC_GIT_COMMIT_SHA Managed (staging-deploy.yml, production-promote.yml)
apps/bakery green-leaf-bakery green-leaf-bakery-staging INQUIRIES_SERVICE R2: MEDIA (green-leaf-bakery-media-{prod,staging,preview}) vars: ENVIRONMENT; Hardcoded sk_bakery_pos_relay_secret in catering-pos-relay.ts Managed
apps/clk-construction clk-construction clk-construction-staging CMS_SERVICE (clk-construction-cms), INQUIRIES_SERVICE Email: LEAD_EMAIL (send_email to quotes@clk-construction.test locally) Secret (Prod): TURNSTILE_SECRET_KEY; vars: APP_ID, ENVIRONMENT, TURNSTILE_SITE_KEY, LEAD_TO_EMAIL, LEAD_FROM_EMAIL Gated (deployable: false in swarm.config.ts)
apps/clk-construction-cms clk-construction-cms clk-construction-cms-staging EVENT_PLATFORM, INQUIRIES_SERVICE D1: DB (clk-construction-cms-{prod,staging}-d1); R2: MEDIA (clk-construction-media-{prod,staging}) vars: ENVIRONMENT, EMDASH_SITE_URL (workers_dev: false, internal Worker B only) Unmanaged by change-detector.ts unless explicitly targeted
apps/command-center siteswarm-command-center siteswarm-command-center-staging None KV: WATCHDOG_STATE (watchdog-state, watchdog-state-staging) Secrets (currently injected as plaintext vars in CI!): GITHUB_TOKEN, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_ZONE_ID; Unwired in CI: STAFF_GATE_USER, STAFF_GATE_PASSWORD, CF_ACCESS_AUD, CF_ACCESS_TEAM_DOMAIN Managed
apps/docs siteswarm-app-docs siteswarm-app-docs-staging None None vars: ENVIRONMENT Managed
apps/marketing siteswarm-marketing siteswarm-marketing-staging INQUIRIES_SERVICE None vars: ENVIRONMENT Managed
apps/me-portfolio me-portfolio me-portfolio-staging None Optional D1: DB (in env.d.ts, static mode in wrangler.jsonc) vars: ENVIRONMENT, EMDASH_SITE_URL Managed
apps/rcc-stjohn rcc-stjohn rcc-stjohn-staging INQUIRIES_SERVICE None vars: ENVIRONMENT, APP_ID, CLUSTER_ID Managed
apps/rcc-stpatrick rcc-stpatrick rcc-stpatrick-staging INQUIRIES_SERVICE (+ CRITIQUE_SERVICE in swarm.config.ts) None vars: ENVIRONMENT, APP_ID, CLUSTER_ID Managed
apps/rcc-stpeter rcc-stpeter rcc-stpeter-staging INQUIRIES_SERVICE None vars: ENVIRONMENT, APP_ID, CLUSTER_ID Managed
apps/software-agency apex-labs software-agency-staging INQUIRIES_SERVICE None vars: ENVIRONMENT Managed
services/inquiries siteswarm-service-inquiries siteswarm-service-inquiries-staging None D1: INQUIRIES_DB (siteswarm-inquiries-{staging-}db); Email: SEND_EMAIL; WAE: ANALYTICS (siteswarm_domain_metrics) Secrets: TURNSTILE_SECRET_KEY, ADMIN_API_KEY (only in top-level vars, missing in staging/prod); vars: OWNER_NOTIFICATION_EMAIL, NOTIFICATION_FROM_EMAIL πŸ”΄ Not in CI deploy matrix (scripts/change-detector.ts scans apps/* only)
services/cache-purge siteswarm-cache-purge siteswarm-cache-purge-staging None D1: DB (siteswarm-cache-purge-{staging-}db); KV: PURGE_KV (siteswarm-purge-kv); Queues: siteswarm-purge-queue, siteswarm-content-published, siteswarm-purge-dlq Secrets: CLOUDFLARE_API_TOKEN (Zone > Cache Purge), ADMIN_API_KEY; vars: TENANT_ZONES πŸ”΄ Not in CI deploy matrix
services/critique siteswarm-critique siteswarm-critique-staging None D1: CRITIQUE_DB (siteswarm-critique-{staging-}db) Secret: ADMIN_API_KEY (in top-level vars, unused in router.ts) πŸ”΄ Not in CI deploy matrix
services/email-sender siteswarm-service-email-sender siteswarm-service-email-sender-staging None D1: DB (siteswarm-events-{staging-}db); Queues: siteswarm-email-queue{-staging}, siteswarm-dlq{-staging}; Email: SEND_EMAIL vars: NOTIFICATION_FROM_EMAIL πŸ”΄ Not in CI deploy matrix
services/event-platform siteswarm-service-event-platform siteswarm-service-event-platform-staging None D1: EVENT_DB (siteswarm-events-{staging-}db); Queues: siteswarm-{inquiry-created,content-published,purge-queue,email-queue,dlq}{-staging}; WAE: ANALYTICS None (R2_ARCHIVE optional in types.ts) πŸ”΄ Not in CI deploy matrix
services/watchdog siteswarm-service-watchdog siteswarm-service-watchdog-staging None KV: WATCHDOG_STATE (watchdog-state{-staging}); Cron: */2 * * * * Secrets: ANALYTICS_READ_TOKEN / CLOUDFLARE_API_TOKEN, BETTERSTACK_TOKEN, DISCORD_WEBHOOK_URL, SLACK_WEBHOOK_URL (all 3 alerting secrets currently shadowed by "" in wrangler.jsonc vars!) πŸ”΄ Not in CI deploy matrix

5. Critical Security Red Flags & Pipeline Anti-Patterns

The audit surfaced 9 concrete security and operational anti-patterns requiring remediation in Phase 2/3 (Issues #781 and #782):

  1. [CRITICAL] Flat Repository Secret Scope & Omni-Token Exposure (CLOUDFLARE_API_TOKEN):

    • Finding: gh secret list --env production and gh secret list --env staging return zero secrets. All workflows consume the single repository-level CLOUDFLARE_API_TOKEN.
    • Impact: Ephemeral PR preview runs (preview-deploy.yml), PR teardown runs (preview-teardown.yml), and Browser Rendering CDP test runs (acceptance-test.yml) possess full write credentials to mutate production Workers, production D1 databases, and production DNS records.
  2. [CRITICAL] Cross-Tenant Secret Leakage in preview-deploy.yml (COMMAND_CENTER_GITHUB_TOKEN):

    • Finding: In .github/workflows/preview-deploy.yml (lines 1119–1126), the inline Node script injects GITHUB_TOKEN: process.env.COMMAND_CENTER_GITHUB_TOKEN into dist/server/wrangler.json config.vars without checking if (appId === 'command-center').
    • Impact: Every ephemeral PR preview Worker deployed for any client app (bakery, auto-repair, software-agency, clk-construction, etc.) has COMMAND_CENTER_GITHUB_TOKEN baked into its plaintext Worker vars.
  3. [HIGH] Plaintext wrangler.json vars Injection for Command Center Tokens:

    • Finding: Both staging-deploy.yml (lines 2700–2721) and production-promote.yml (lines 2083–2091) pass COMMAND_CENTER_GITHUB_TOKEN and COMMAND_CENTER_CLOUDFLARE_API_TOKEN into deployEnvConfig.applyEnvironmentScoped(distConfig, envConfig, deployVars), which writes them directly to distConfig.vars in dist/server/wrangler.json prior to wrangler deploy.
    • Impact: Cloudflare stores vars in wrangler.json as plaintext environment variables visible in dashboard/API metadata rather than encrypted Worker Secrets (secret_text bindings). Furthermore, production-promote.yml omits COMMAND_CENTER_CLOUDFLARE_API_TOKEN, COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID, and COMMAND_CENTER_CLOUDFLARE_ZONE_ID, leaving production Command Center telemetry unconfigured.
  4. [CRITICAL] Command Center Zero Trust Bypass via workers_dev: true & Default "admin" / "admin" Staff Gate:

    • Finding:
      1. apps/command-center/wrangler.jsonc sets "workers_dev": true (and staging-deploy.yml line 2680 / production-promote.yml line 2082 unconditionally set distConfig.workers_dev = true for all apps).
      2. infra/platform/access.tf defines Cloudflare Access Applications for *-staging.jacobmiller22.com, *-preview-pr-*.jacobmiller22.com, *-preview.jacobmiller22.com/admin*, and *.preview.jacobmiller22.com/admin*, but defines no Access Application for production command-center.jacobmiller22.com.
      3. When a request reaches apps/command-center without a Cf-Access-Jwt-Assertion header (either via siteswarm-command-center.jacobmillerdev.workers.dev or unprotected command-center.jacobmiller22.com), handleCommandCenterMiddleware (apps/command-center/src/lib/middleware-handler.ts lines 103–147) falls back to HTTP Basic Auth with hardcoded default credentials STAFF_GATE_USER = "admin" and STAFF_GATE_PASSWORD = "admin" and grants role = "admin".
      4. Even when Cf-Access-Jwt-Assertion is present, CF_ACCESS_AUD and CF_ACCESS_TEAM_DOMAIN are never injected into apps/command-center/wrangler.jsonc in staging or production.
  5. [HIGH] Turnstile ALWAYS_PASSES Test Secret Fallback in Production services/inquiries:

    • Finding: In services/inquiries/src/domain.ts (lines 160–162), secretKey resolves as this.env.TURNSTILE_SECRET_KEY || TURNSTILE_TEST_SECRETS.ALWAYS_PASSES. In services/inquiries/wrangler.jsonc, TURNSTILE_SECRET_KEY is defined only in top-level vars (for local dev) and omitted in env.staging and env.production.
    • Impact: Unless TURNSTILE_SECRET_KEY is manually provisioned via wrangler secret put on siteswarm-service-inquiries, production and staging inquiry submissions validate against Cloudflare’s dummy test key (1x0000000000000000000000000000000AA), allowing trivial bot bypass.
  6. [HIGH] Empty String vars Shadowing Encrypted Secrets in services/watchdog/wrangler.jsonc:

    • Finding: services/watchdog/wrangler.jsonc explicitly sets "CLOUDFLARE_ACCOUNT_ID": "", "BETTERSTACK_TOKEN": "", "DISCORD_WEBHOOK_URL": "", and "SLACK_WEBHOOK_URL": "" in vars for top-level, env.staging, and env.production.
    • Impact: In Cloudflare Workers, plaintext vars in wrangler.jsonc collide with or overwrite encrypted secrets of the same name upon deployment. Secrets must never be declared as empty strings in wrangler.jsonc vars.
  7. [MEDIUM] Imperative D1 & KV Provisioning Inside CI Workflows:

    • Finding: staging-deploy.yml, production-promote.yml, and preview-deploy.yml embed ~80-line inline Node.js scripts that call POST /client/v4/accounts/:id/d1/database and POST /client/v4/accounts/:id/storage/kv/namespaces at deploy time, and placeholder UUIDs (00000000-0000-0000-0000-000000000000) are committed across wrangler.jsonc files.
    • Impact: Forces CI deployment tokens to carry account-wide D1:Edit and Workers KV Storage:Edit creation permissions on every run, and creates stateful Cloudflare resources outside Terraform (infra/) state tracking.
  8. [MEDIUM] Terraform Environment Variable & Backend Configuration Drift in infra/:

    • Finding: staging-deploy.yml and production-promote.yml pass CLOUDFLARE_ACCOUNT_ID to terraform apply, but Terraform only binds input variables prefixed with TF_VAR_ (TF_VAR_cloudflare_account_id), causing var.cloudflare_account_id in infra/variables.tf to fall back to the dummy default "0123456789abcdef0123456789abcdef" unless a .tfvars file is present. Additionally, infra/providers.tf contains a literal <account_id> placeholder in endpoint = "https://<account_id>.r2.cloudflarestorage.com" and hardcodes key = "siteswarm/staging/terraform.tfstate" for both staging and production runs.
  9. [MEDIUM] Unmanaged Platform Service Workers (services/*) in CI/CD:

    • Finding: scripts/change-detector.ts discovers deployable targets exclusively under apps/*. None of the 6 horizontal platform services under services/* (inquiries, cache-purge, critique, email-sender, event-platform, watchdog) have automated CI deployment or secret synchronization pipelines.

6. Categorized Phase 2 Candidate Actions

To prepare for the Phase 2 User Review & Alignment Gate and subsequent execution in #781 (CI/CD Least-Privilege Token Reconciliation) and #782 (Terraform IaC Secret & Access Codification), all remediation recommendations are grouped into four actionable categories:

Category 1: Deprecate / Delete

  1. Purge Dead Local Template Secrets in .dev.vars.example:
    • Remove RESEND_API_KEY, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, ADMIN_SECRET_PIN, and SESSION_SECRET from root .dev.vars.example and apps/bakery/.dev.vars.example (0 callers across the monorepo).
  2. Prune Redundant TURNSTILE_SECRET_KEY from Non-Consumer App .dev.vars.example Files:
    • Remove TURNSTILE_SECRET_KEY from apps/{auto-repair,bakery,command-center,marketing,me-portfolio,rcc-stjohn,rcc-stpatrick,rcc-stpeter,software-agency}/.dev.vars.example where Turnstile verification is handled by services/inquiries (keeping it only in services/inquiries and apps/clk-construction).
  3. Remove ${{ secrets.CF_WORKERS_DOMAIN }} Secret References:
    • Delete ${{ secrets.CF_WORKERS_DOMAIN }} from preview-deploy.yml, client-preview-deploy.yml, staging-deploy.yml, and production-promote.yml, replacing it with the canonical domain from platform.config.ts / platform.lock.json.
  4. Eliminate Hardcoded sk_bakery_pos_relay_secret in apps/bakery:
    • Remove the hardcoded bearer secret literal in apps/bakery/src/pages/api/catering-pos-relay.ts (or explicitly convert the endpoint to a documented mock-only fixture that does not simulate bearer secret validation).

Category 2: Consolidate

  1. Unify CLOUDFLARE_ACCOUNT_ID & COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID / ZONE_ID:
    • Consolidate COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID into CLOUDFLARE_ACCOUNT_ID, and source CLOUDFLARE_ZONE_ID deterministically from Terraform outputs (platform_zone_id) or platform.lock.json rather than maintaining duplicate GitHub Secrets.
  2. Standardize Watchdog WAE Token on ANALYTICS_READ_TOKEN:
    • Remove the env.CLOUDFLARE_API_TOKEN fallback in services/watchdog/src/evaluator.ts (line 111) so services/watchdog exclusively consumes ANALYTICS_READ_TOKEN, eliminating naming ambiguity with the CI deploy token.
  3. Reconcile Legacy staging.<app>.siteswarm.dev Domains in swarm.config.ts:
    • Update deployment.stagingDomain in apps/{auto-repair,bakery,software-agency}/swarm.config.ts to match their actual <app>-staging.jacobmiller22.com routes in wrangler.jsonc.

Category 3: Re-Scope to Least Privilege (Target Matrix for Issue #781)

  1. Migrate from Flat Repository Secrets to GitHub Environment-Scoped Secrets:
    • Register the preview GitHub Environment alongside staging and production, attach environment: preview to preview-deploy.yml and preview-teardown.yml, and move deployment tokens out of Repository Secrets into their respective GitHub Environments.
  2. Split CLOUDFLARE_API_TOKEN into Purpose- and Environment-Scoped Tokens:
Proposed Token Identifier GitHub Environment / Runtime Host Granted Cloudflare Permissions Resource / Zone / Worker Scope
CLOUDFLARE_API_TOKEN (preview env) GitHub Env: preview (preview-deploy.yml, preview-teardown.yml, client-preview-deploy.yml) Account > Workers Scripts:Edit, Account > Workers KV Storage:Edit, Zone > Workers Routes:Edit Scoped strictly to jacobmiller22.com zone; no D1 edit, no Zero Trust edit, and no production custom domain access
CLOUDFLARE_API_TOKEN (staging env) GitHub Env: staging (staging-deploy.yml) Account > Workers Scripts:Edit, Account > D1:Edit, Account > Workers KV Storage:Edit, Account > R2:Edit, Zone > Workers Routes:Edit Scoped to jacobmiller22.com staging routes and staging databases
CLOUDFLARE_API_TOKEN (production env) GitHub Env: production (production-promote.yml) Account > Workers Scripts:Edit, Account > D1:Edit, Account > Workers KV Storage:Edit, Account > R2:Edit, Zone > Workers Routes:Edit, Zone > DNS:Edit Scoped to production zones (jacobmiller22.com, siteswarm.dev, client zones) and gated behind production GitHub Environment protection rules
CLOUDFLARE_BROWSER_RUN_TOKEN Repository or staging/preview Env (acceptance-test.yml, demo-capture.yml) Account > Browser Rendering:Edit Account Browser Rendering CDP endpoint only (zero Worker, D1, KV, or DNS permissions)
R2_ACCESS_KEY_ID & R2_SECRET_ACCESS_KEY Repository or staging/preview Env (demo-capture.yml, preview-teardown.yml) R2 Object Read & Write Scoped exclusively to bucket siteswarm-demos
CACHE_PURGE_CF_API_TOKEN Worker Secret on siteswarm-cache-purge (staging & production) Zone > Cache Purge:Purge Scoped exclusively to active tenant zones in TENANT_ZONES
ANALYTICS_READ_TOKEN Worker Secret on siteswarm-service-watchdog (staging & production) Account > Account Analytics:Read Read-only WAE SQL query access
  1. Add Explicit Least-Privilege permissions: Blocks to All Workflows:
    • Add top-level permissions: { contents: read } to .github/workflows/ci.yml, .github/workflows/acceptance-test.yml, and .github/workflows/demo-capture.yml.

Category 4: Re-Architect / Migrate Pattern (Target Matrix for Issue #782)

  1. Stop Injecting Secrets into Plaintext wrangler.json vars in CI:
    • Remove COMMAND_CENTER_GITHUB_TOKEN from preview-deploy.yml (fixing the cross-tenant leak to all PR preview apps).
    • Remove COMMAND_CENTER_GITHUB_TOKEN and COMMAND_CENTER_CLOUDFLARE_API_TOKEN from deployVars in staging-deploy.yml and production-promote.yml, and provision them as encrypted Cloudflare Worker Secrets (wrangler secret bulk / wrangler secret put or Terraform cloudflare_worker_secret in #782).
  2. Harden Command Center Zero Trust Perimeter & Eliminate Default "admin" / "admin" Prod Fallback:
    • Codify a dedicated cloudflare_zero_trust_access_application for production command-center.jacobmiller22.com in infra/platform/access.tf and wire CF_ACCESS_AUD and CF_ACCESS_TEAM_DOMAIN into apps/command-center/wrangler.jsonc.
    • Set "workers_dev": false on apps/command-center/wrangler.jsonc and stop forcing distConfig.workers_dev = true in production-promote.yml so production Workers cannot be reached via *.jacobmillerdev.workers.dev outside Cloudflare Access.
    • Require explicit STAFF_GATE_PASSWORD secret in non-local environments (failing closed with HTTP 403/503 if STAFF_GATE_PASSWORD is unset and Cf-Access-Jwt-Assertion is absent).
  3. Remove Empty String Secret Placeholders from services/watchdog/wrangler.jsonc vars:
    • Delete "BETTERSTACK_TOKEN": "", "DISCORD_WEBHOOK_URL": "", "SLACK_WEBHOOK_URL": "", and "CLOUDFLARE_ACCOUNT_ID": "" from services/watchdog/wrangler.jsonc vars so encrypted Worker Secrets are not overwritten on deploy.
  4. Enforce Fail-Closed Turnstile Secret Validation in services/inquiries:
    • Update services/inquiries/src/domain.ts so TURNSTILE_TEST_SECRETS.ALWAYS_PASSES is only permitted when ENVIRONMENT === "local" or mockTestSecrets === true; fail closed in staging and production if TURNSTILE_SECRET_KEY is missing.
  5. Codify Cloudflare Access Service Tokens, D1/KV Resources & Worker Secret Bindings in Terraform (infra/):
    • Model cloudflare_zero_trust_access_service_token for machine-to-machine CI/service auth (replacing static ADMIN_API_KEY bearer strings where applicable), codify D1 databases and KV namespaces in Terraform rather than inline CI curl scripts, and pass TF_VAR_cloudflare_account_id with environment-specific backend state keys (siteswarm/staging/terraform.tfstate vs. siteswarm/production/terraform.tfstate).

7. Candidate Canonical Updates

Following Phase 2 founder review and remediation in #781 and #782, the insights in this write-up are candidates for distillation (/docs distill) into:


Decisions Intentionally Deferred

  • Live Token Rotation & Revocation Execution: Per Issue #686 guardrails, no live GitHub Secrets or Cloudflare API tokens were rotated, modified, or revoked during this discovery audit; live credential cutover is deferred to Phase 3 (Issues #781 and #782) after Phase 2 user review.
  • GitHub OIDC Federation to Cloudflare: Native OIDC token exchange from GitHub Actions directly to Cloudflare APIs without long-lived API tokens is deferred until Cloudflare’s IAM OIDC workload identity support is generally available on standard plans; fine-grained environment-scoped Cloudflare API tokens with automated rotation will be used in Phase 3.
  • External Secrets Manager (Doppler / HashiCorp Vault) Integration: Introducing a third-party external secrets synchronization SaaS is deferred in favor of native GitHub Environment Secrets, Cloudflare Worker Secrets, and Terraform state management (infra/).