Skip to content

Adopt Cloudflare Secrets Store when GA

ADR-025: Adopt Cloudflare Secrets Store when GA

Status: Accepted (2026-04-24) — migration blocked on Secrets Store GA Supersedes: none Superseded-by: none

Context

Today V5 manages ~25 provider credentials (HubSpot, Salesforce, Google OAuth, Slack, Gong, DeepSeek, OpenAI, Anthropic, Cerebras, Groq, OpenRouter, SEMrush, AWS per-service IAM users, Apollo, etc.) via per-Worker wrangler secret put. That works for a single Worker but is already fraying at the edges:

  • Adding the scheduler Worker, preview Workers, a dev Worker, or the context worker means duplicating every secret across each wrangler.toml. Rotation becomes N-way.
  • Rotation requires a deploy per Worker. During a compromise, that’s unacceptable friction.
  • There’s no account-level audit or listing; visibility is per-Worker.

Cloudflare announced Secrets Store (overview page: https://developers.cloudflare.com/secrets-store/) — open beta as of 2026-04-16. Relevant properties:

Attributewrangler secret putSecrets Store
ScopePer-WorkerPer-account (one store, many bindings)
RotationManual per Worker + redeployRotate once, all bound Workers see new value (no redeploy)
Access in codeenv.SECRET_NAME synchronousawait env.BINDING.get() async
Dashboard visibilityPer-WorkerAccount-wide listing + audit
Reuse across WorkersDuplicate per WorkerSingle source of truth
AI Gateway BYOKNot supportedNative integration

wrangler.toml binding shape (from the docs):

[[secrets_store_secrets]]
binding = "MY_BINDING"
store_id = "<STORE_ID>"
secret_name = "MY_SECRET"

Worker access pattern:

// Async .get() on every request — one awaitable per secret touched
const apiKey = await env.HUBSPOT_TOKEN.get();

Research receipts (2026-04-24):

Decision

Adopt Cloudflare Secrets Store for all V5 + Context Worker secrets within 30 days of the Secrets Store GA announcement.

Until GA:

  1. Continue using wrangler secret put per Worker.
  2. Follow the rotation procedure in docs/runbooks/secret-rotation.md on a quarterly cadence and immediately on any suspected compromise.
  3. Keep the migration plan (below) current so the refactor is mechanical the day GA drops.

Rationale

Why adopt

  • Rotation-in-place is the feature we bleed for. wrangler secret put rotation is a 5-minute-per-Worker chore that compounds linearly as we add Workers. One-store rotation scales.
  • Account-wide audit closes a real compliance gap — we can’t today show a client “here is every credential we hold for you and the last time it was touched.”
  • AI Gateway BYOK (Task 3.2) natively consumes Secrets Store — keeping provider keys one hop away from the gateway.

Why wait for GA

  • Still open beta as of 2026-04-16. No SLA. Production credential plane on a beta is a real risk: if the backend has an incident, every Worker’s hot path breaks until the fetch recovers.
  • The await env.BINDING.get() async-on-read pattern is a non-trivial refactor across 50+ provider adapters (every tool using resolveTokenData(), every env.XXX_API_KEY synchronous read). It’s boring and mechanical — but it has to be done in ONE sweep so we don’t mix env.X (sync) and await env.X.get() (async) across the codebase.

Why not partial adoption now (e.g. only new secrets)

Creates a dual-world problem: one list of secrets in wrangler, another in the Store. That doubles the audit surface — the opposite of the goal. Wait, then do the sweep in one go.

Migration trigger

“Within 30 days of the Cloudflare Secrets Store GA announcement.”

When GA lands:

  1. Create the store: wrangler secrets-store store create ascend-prod.
  2. Import every secret via wrangler secrets-store secret put.
  3. Swap wrangler.toml — replace each bare secret reference with a [[secrets_store_secrets]] block. One-line change per secret.
  4. Refactor every env.XXX_API_KEY call site to await env.XXX_API_KEY.get(). There’s a deterministic pattern here — a codemod can cover 95% of sites.
  5. Update adapters’ resolveTokenData() to await once per request and reuse within the same request context (same memoization pattern we already use for D1 / KV reads).
  6. Run the full test suite + staging canary + production canary before removing the wrangler secret put fallback paths.
  7. Archive the migration as its own ADR closing this one out.

Consequences

Short-term (pre-GA): slight operational burden — manual rotation continues; every new Worker duplicates secrets. Documented + time-boxed.

Post-GA: one-time refactor of ~50 call sites. After that, rotation becomes a 30-second dashboard action and every new Worker just binds what it needs.

Risk: delay GA announcement → delay burden compounding. Reassess at 90 days — if no GA by then, reopen this ADR to consider a phased partial adoption (new-secrets-only in the Store, existing ones stay in wrangler) as a lesser-of-two-evils bridge.

  • Wave 3 Task 3.8 in docs/architecture/ASCEND-CLOUD-NATIVE-V2-ENGINEERING-PLAN.md
  • Rotation procedure: docs/runbooks/secret-rotation.md
  • AI Gateway Task 3.2 (prefers Secrets-Store-backed BYOK post-GA): src/lib/ai-gateway.ts