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 distillalignment)
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
- GitHub Actions Control Plane: Queried live repository secret metadata (
gh secret list/ GitHub Actions Secrets API), repository variables (gh variable list), and GitHub Environments (productionandstagingviarepos/jptechnologysolutions/siteswarm/environments), and inspected all 8 workflow definitions under.github/workflows/. - 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 underservices/*/wrangler.jsonc(cache-purge,critique,email-sender,event-platform,inquiries,watchdog), cross-referenced againstplatform.config.tsandplatform.lock.json. - 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). - Local Development Templates & Tooling Scripts: Audited root
.env.example, root.dev.vars.example, all 10apps/*/.dev.vars.exampleandapps/*/.env.exampletemplates,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
stagingandproductionGitHub Environments exist, 0 environment-scoped secrets are configured in either environment. All 6 active secrets reside at the Repository level, exposing the masterCLOUDFLARE_API_TOKENandCOMMAND_CENTER_GITHUB_TOKENto every workflow job (including PR previews and acceptance runs). - Single Over-Permissioned Cloudflare Omni-Token: A single repository-wide
CLOUDFLARE_API_TOKENperforms 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.jsonvarsSecret Injection in CI: Deployment pipelines (preview-deploy.yml,staging-deploy.yml,production-promote.yml) injectCOMMAND_CENTER_GITHUB_TOKENandCOMMAND_CENTER_CLOUDFLARE_API_TOKENintodist/server/wrangler.jsonunder plaintextvarsrather than encrypted Cloudflare Worker Secrets (wrangler secret put). Worse,preview-deploy.ymlunconditionally injectsCOMMAND_CENTER_GITHUB_TOKENintovarsof every tenant preview Worker deployed on a PR. - Unset Secret Collisions & Test-Key Fallbacks:
services/watchdog/wrangler.jsoncdefines empty strings ("") invarsforBETTERSTACK_TOKEN,DISCORD_WEBHOOK_URL, andSLACK_WEBHOOK_URLacross all environments, colliding with encrypted Worker secrets. Meanwhile,services/inquiries/src/domain.tsfalls back to CloudflareβsALWAYS_PASSEStest Turnstile secret (1x0000000000000000000000000000000AA) wheneverTURNSTILE_SECRET_KEYis unset instagingorproduction.
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_IDCLOUDFLARE_API_TOKENCOMMAND_CENTER_CLOUDFLARE_ACCOUNT_IDCOMMAND_CENTER_CLOUDFLARE_API_TOKENCOMMAND_CENTER_CLOUDFLARE_ZONE_IDCOMMAND_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 apps5. 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> --force2. GET & DELETE /client/v4/accounts/:id/storage/kv/namespaces/:ns_id3. GET & DELETE /client/v4/accounts/:id/workers/domains/:domain_id4. 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 --remote4. GET /client/v4/accounts/:id/workers/scripts5. GET & POST /client/v4/accounts/:id/storage/kv/namespaces6. 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 --remote4. GET & POST /client/v4/accounts/:id/storage/kv/namespaces5. 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):
-
[CRITICAL] Flat Repository Secret Scope & Omni-Token Exposure (
CLOUDFLARE_API_TOKEN):- Finding:
gh secret list --env productionandgh secret list --env stagingreturn zero secrets. All workflows consume the single repository-levelCLOUDFLARE_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.
- Finding:
-
[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 injectsGITHUB_TOKEN: process.env.COMMAND_CENTER_GITHUB_TOKENintodist/server/wrangler.jsonconfig.varswithout checkingif (appId === 'command-center'). - Impact: Every ephemeral PR preview Worker deployed for any client app (
bakery,auto-repair,software-agency,clk-construction, etc.) hasCOMMAND_CENTER_GITHUB_TOKENbaked into its plaintext Workervars.
- Finding: In
-
[HIGH] Plaintext
wrangler.jsonvarsInjection for Command Center Tokens:- Finding: Both
staging-deploy.yml(lines 2700β2721) andproduction-promote.yml(lines 2083β2091) passCOMMAND_CENTER_GITHUB_TOKENandCOMMAND_CENTER_CLOUDFLARE_API_TOKENintodeployEnvConfig.applyEnvironmentScoped(distConfig, envConfig, deployVars), which writes them directly todistConfig.varsindist/server/wrangler.jsonprior towrangler deploy. - Impact: Cloudflare stores
varsinwrangler.jsonas plaintext environment variables visible in dashboard/API metadata rather than encrypted Worker Secrets (secret_textbindings). Furthermore,production-promote.ymlomitsCOMMAND_CENTER_CLOUDFLARE_API_TOKEN,COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID, andCOMMAND_CENTER_CLOUDFLARE_ZONE_ID, leaving production Command Center telemetry unconfigured.
- Finding: Both
-
[CRITICAL] Command Center Zero Trust Bypass via
workers_dev: true& Default"admin"/"admin"Staff Gate:- Finding:
apps/command-center/wrangler.jsoncsets"workers_dev": true(andstaging-deploy.ymlline 2680 /production-promote.ymlline 2082 unconditionally setdistConfig.workers_dev = truefor all apps).infra/platform/access.tfdefines 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 productioncommand-center.jacobmiller22.com.- When a request reaches
apps/command-centerwithout aCf-Access-Jwt-Assertionheader (either viasiteswarm-command-center.jacobmillerdev.workers.devor unprotectedcommand-center.jacobmiller22.com),handleCommandCenterMiddleware(apps/command-center/src/lib/middleware-handler.tslines 103β147) falls back to HTTP Basic Auth with hardcoded default credentialsSTAFF_GATE_USER = "admin"andSTAFF_GATE_PASSWORD = "admin"and grantsrole = "admin". - Even when
Cf-Access-Jwt-Assertionis present,CF_ACCESS_AUDandCF_ACCESS_TEAM_DOMAINare never injected intoapps/command-center/wrangler.jsoncin staging or production.
- Finding:
-
[HIGH] Turnstile
ALWAYS_PASSESTest Secret Fallback in Productionservices/inquiries:- Finding: In
services/inquiries/src/domain.ts(lines 160β162),secretKeyresolves asthis.env.TURNSTILE_SECRET_KEY || TURNSTILE_TEST_SECRETS.ALWAYS_PASSES. Inservices/inquiries/wrangler.jsonc,TURNSTILE_SECRET_KEYis defined only in top-levelvars(for local dev) and omitted inenv.stagingandenv.production. - Impact: Unless
TURNSTILE_SECRET_KEYis manually provisioned viawrangler secret putonsiteswarm-service-inquiries, production and staging inquiry submissions validate against Cloudflareβs dummy test key (1x0000000000000000000000000000000AA), allowing trivial bot bypass.
- Finding: In
-
[HIGH] Empty String
varsShadowing Encrypted Secrets inservices/watchdog/wrangler.jsonc:- Finding:
services/watchdog/wrangler.jsoncexplicitly sets"CLOUDFLARE_ACCOUNT_ID": "","BETTERSTACK_TOKEN": "","DISCORD_WEBHOOK_URL": "", and"SLACK_WEBHOOK_URL": ""invarsfor top-level,env.staging, andenv.production. - Impact: In Cloudflare Workers, plaintext
varsinwrangler.jsonccollide with or overwrite encrypted secrets of the same name upon deployment. Secrets must never be declared as empty strings inwrangler.jsoncvars.
- Finding:
-
[MEDIUM] Imperative D1 & KV Provisioning Inside CI Workflows:
- Finding:
staging-deploy.yml,production-promote.yml, andpreview-deploy.ymlembed ~80-line inline Node.js scripts that callPOST /client/v4/accounts/:id/d1/databaseandPOST /client/v4/accounts/:id/storage/kv/namespacesat deploy time, and placeholder UUIDs (00000000-0000-0000-0000-000000000000) are committed acrosswrangler.jsoncfiles. - Impact: Forces CI deployment tokens to carry account-wide
D1:EditandWorkers KV Storage:Editcreation permissions on every run, and creates stateful Cloudflare resources outside Terraform (infra/) state tracking.
- Finding:
-
[MEDIUM] Terraform Environment Variable & Backend Configuration Drift in
infra/:- Finding:
staging-deploy.ymlandproduction-promote.ymlpassCLOUDFLARE_ACCOUNT_IDtoterraform apply, but Terraform only binds input variables prefixed withTF_VAR_(TF_VAR_cloudflare_account_id), causingvar.cloudflare_account_idininfra/variables.tfto fall back to the dummy default"0123456789abcdef0123456789abcdef"unless a.tfvarsfile is present. Additionally,infra/providers.tfcontains a literal<account_id>placeholder inendpoint = "https://<account_id>.r2.cloudflarestorage.com"and hardcodeskey = "siteswarm/staging/terraform.tfstate"for both staging and production runs.
- Finding:
-
[MEDIUM] Unmanaged Platform Service Workers (
services/*) in CI/CD:- Finding:
scripts/change-detector.tsdiscovers deployable targets exclusively underapps/*. None of the 6 horizontal platform services underservices/*(inquiries,cache-purge,critique,email-sender,event-platform,watchdog) have automated CI deployment or secret synchronization pipelines.
- Finding:
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
- Purge Dead Local Template Secrets in
.dev.vars.example:- Remove
RESEND_API_KEY,TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN,ADMIN_SECRET_PIN, andSESSION_SECRETfrom root.dev.vars.exampleandapps/bakery/.dev.vars.example(0 callers across the monorepo).
- Remove
- Prune Redundant
TURNSTILE_SECRET_KEYfrom Non-Consumer App.dev.vars.exampleFiles:- Remove
TURNSTILE_SECRET_KEYfromapps/{auto-repair,bakery,command-center,marketing,me-portfolio,rcc-stjohn,rcc-stpatrick,rcc-stpeter,software-agency}/.dev.vars.examplewhere Turnstile verification is handled byservices/inquiries(keeping it only inservices/inquiriesandapps/clk-construction).
- Remove
- Remove
${{ secrets.CF_WORKERS_DOMAIN }}Secret References:- Delete
${{ secrets.CF_WORKERS_DOMAIN }}frompreview-deploy.yml,client-preview-deploy.yml,staging-deploy.yml, andproduction-promote.yml, replacing it with the canonical domain fromplatform.config.ts/platform.lock.json.
- Delete
- Eliminate Hardcoded
sk_bakery_pos_relay_secretinapps/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).
- Remove the hardcoded bearer secret literal in
Category 2: Consolidate
- Unify
CLOUDFLARE_ACCOUNT_ID&COMMAND_CENTER_CLOUDFLARE_ACCOUNT_ID/ZONE_ID:- Consolidate
COMMAND_CENTER_CLOUDFLARE_ACCOUNT_IDintoCLOUDFLARE_ACCOUNT_ID, and sourceCLOUDFLARE_ZONE_IDdeterministically from Terraform outputs (platform_zone_id) orplatform.lock.jsonrather than maintaining duplicate GitHub Secrets.
- Consolidate
- Standardize Watchdog WAE Token on
ANALYTICS_READ_TOKEN:- Remove the
env.CLOUDFLARE_API_TOKENfallback inservices/watchdog/src/evaluator.ts(line 111) soservices/watchdogexclusively consumesANALYTICS_READ_TOKEN, eliminating naming ambiguity with the CI deploy token.
- Remove the
- Reconcile Legacy
staging.<app>.siteswarm.devDomains inswarm.config.ts:- Update
deployment.stagingDomaininapps/{auto-repair,bakery,software-agency}/swarm.config.tsto match their actual<app>-staging.jacobmiller22.comroutes inwrangler.jsonc.
- Update
Category 3: Re-Scope to Least Privilege (Target Matrix for Issue #781)
- Migrate from Flat Repository Secrets to GitHub Environment-Scoped Secrets:
- Register the
previewGitHub Environment alongsidestagingandproduction, attachenvironment: previewtopreview-deploy.ymlandpreview-teardown.yml, and move deployment tokens out of Repository Secrets into their respective GitHub Environments.
- Register the
- Split
CLOUDFLARE_API_TOKENinto 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 |
- 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.
- Add top-level
Category 4: Re-Architect / Migrate Pattern (Target Matrix for Issue #782)
- Stop Injecting Secrets into Plaintext
wrangler.jsonvarsin CI:- Remove
COMMAND_CENTER_GITHUB_TOKENfrompreview-deploy.yml(fixing the cross-tenant leak to all PR preview apps). - Remove
COMMAND_CENTER_GITHUB_TOKENandCOMMAND_CENTER_CLOUDFLARE_API_TOKENfromdeployVarsinstaging-deploy.ymlandproduction-promote.yml, and provision them as encrypted Cloudflare Worker Secrets (wrangler secret bulk/wrangler secret putor Terraformcloudflare_worker_secretin#782).
- Remove
- Harden Command Center Zero Trust Perimeter & Eliminate Default
"admin"/"admin"Prod Fallback:- Codify a dedicated
cloudflare_zero_trust_access_applicationfor productioncommand-center.jacobmiller22.comininfra/platform/access.tfand wireCF_ACCESS_AUDandCF_ACCESS_TEAM_DOMAINintoapps/command-center/wrangler.jsonc. - Set
"workers_dev": falseonapps/command-center/wrangler.jsoncand stop forcingdistConfig.workers_dev = trueinproduction-promote.ymlso production Workers cannot be reached via*.jacobmillerdev.workers.devoutside Cloudflare Access. - Require explicit
STAFF_GATE_PASSWORDsecret in non-local environments (failing closed with HTTP 403/503 ifSTAFF_GATE_PASSWORDis unset andCf-Access-Jwt-Assertionis absent).
- Codify a dedicated
- Remove Empty String Secret Placeholders from
services/watchdog/wrangler.jsoncvars:- Delete
"BETTERSTACK_TOKEN": "","DISCORD_WEBHOOK_URL": "","SLACK_WEBHOOK_URL": "", and"CLOUDFLARE_ACCOUNT_ID": ""fromservices/watchdog/wrangler.jsoncvarsso encrypted Worker Secrets are not overwritten on deploy.
- Delete
- Enforce Fail-Closed Turnstile Secret Validation in
services/inquiries:- Update
services/inquiries/src/domain.tssoTURNSTILE_TEST_SECRETS.ALWAYS_PASSESis only permitted whenENVIRONMENT === "local"ormockTestSecrets === true; fail closed instagingandproductionifTURNSTILE_SECRET_KEYis missing.
- Update
- Codify Cloudflare Access Service Tokens, D1/KV Resources & Worker Secret Bindings in Terraform (
infra/):- Model
cloudflare_zero_trust_access_service_tokenfor machine-to-machine CI/service auth (replacing staticADMIN_API_KEYbearer strings where applicable), codify D1 databases and KV namespaces in Terraform rather than inline CI curl scripts, and passTF_VAR_cloudflare_account_idwith environment-specific backend state keys (siteswarm/staging/terraform.tfstatevs.siteswarm/production/terraform.tfstate).
- Model
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:
docs/architecture/CI_DEPLOYMENT_ENVIRONMENTS.mdβ Environment-scoped secret matrix and least-privilege token separation acrosspreview,staging, andproduction.docs/architecture/CLOUDFLARE_FOR_PLATFORMS_AND_DOMAIN_INGRESS.mdβworkers_devdisablement on production/Zero-Trust workers and production Access perimeter coverage.docs/architecture/MULTI_WORKER_PLATFORM_SERVICES.mdβ Service-to-service authentication, Worker secret binding hygiene, and CI deployment coverage forservices/*.docs/runbooks/ALERTING_DRILL_RUNBOOK.mdβ Watchdog Worker secret provisioning (ANALYTICS_READ_TOKEN,BETTERSTACK_TOKEN,DISCORD_WEBHOOK_URL,SLACK_WEBHOOK_URL).
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/).