Skip to content
downpipes docs

Secrets and Workers: high-assurance capture with deliberate restore limits

Secrets and Workers are the two sources whose restore is, on purpose, not a destructive write back into your account. A secret’s value is captured and sealed encrypted into the archive, but its restore is out of band only: the engine never writes a secret value back into your account. A Worker is captured as code, settings and a version inventory, with every secret binding redacted to a name-and-type checklist, and its restore is reprovision-only: the engine never blind-redeploys a script. This page explains both, and the honest assurance framing that sits behind them. It is written for an evaluator weighing what these two sources do and do not promise.

The reason to treat them together is that they share a discipline. Both capture wiring and content that are high-consequence to recover incorrectly, so both prove the snapshot is recoverable while refusing to perform the write that could do harm. A captured snapshot is verified recoverable, but the act of putting a secret back, or redeploying a Worker, stays in your hands.

Secrets are an explicit-bind source

The engine only ever sees the secrets it was explicitly granted. A secret is backed up only if the Worker is bound to it; there is no read-all over the store. The value is read at runtime through that binding and lives in isolate memory only until it is sealed; the plaintext is never logged, never written to a descriptor field, and never written to Durable Object state (SecretsSource.crawl, engine/src/sources/secrets.ts). What lands in the archive is the wiring and the value itself, the value sealed under the run’s encryption so the secret is recoverable. What never lands anywhere is the plaintext in the clear.

Captured Persisted to the backup?
The secret’s value Yes, sealed encrypted into the archive so the secret is recoverable. The plaintext is read at runtime, held in isolate memory only until it is sealed, and is never logged, never written to a descriptor field, and never written to Durable Object state.
The store and the scope Yes, as wiring, so a restore can identify which secret this was.
The worker and the binding variable Yes, for a per-Worker secret, as wiring.
An optional comment Yes, as wiring.

Each wiring field is attached to the record only when it is set, so a record carries the minimal descriptor and nothing more (RestoreDescriptor, engine/src/sources/types.ts). The estimate path never reads a secret value to size the backup: it counts the in-scope secrets and reports an unknown byte size, so even sizing the backup does not touch a value.

Secret values are captured encrypted, and restored out of band

A downpipes backup captures a secret’s wiring (the store, the scope, the worker, the binding variable and an optional comment) and its value. The value is sealed under the run’s hybrid post-quantum encryption while it is still in the isolate, so the archive holds the encrypted value and never the plaintext: nothing is logged, and no descriptor field, log line or Durable Object state holds it. The ciphertext lives only in your own destination, under keys only you hold. Putting a secret back is out of band, because Secrets Store bindings are read-only at runtime, so the engine never writes a secret into your account.

Why secrets restore out of band only

Cloudflare Secrets Store bindings expose a read at runtime but no runtime write back, so there is no in-account path the engine could use to put a secret value back. The engine treats this as an inherent platform limitation, not a transient error, and is honest about it at every step.

A secrets record is never a planned write. The restore orchestrator routes every secrets record to an out-of-band list before the plan is even built, so a secrets record never appears in the dry-run planned-writes count and the apply phase never attempts a write that would always fail (runRestore, engine/src/admin/restore.ts). In both a dry run and an apply, the record is surfaced as “restore out of band” so the operator knows to restore it manually through the Cloudflare API or wrangler.

This does not mark the run failed. Because an out-of-band secret is a known platform limitation rather than a runtime fault, it does not set the result to not-ok and does not contribute to the failure list (engine/src/admin/restore.ts). A restore whose only unwritten records are secrets is an honest success with an out-of-band note, not a failure.

If something does ask the secrets sink to write with no wired path, it refuses loudly rather than pretending. The sink throws a clear message, that the secret has no runtime write path and should be restored out of band because Secrets Store bindings are read-only at runtime, so an operator is never told a secret was restored when it was not (SecretsRestoreSink.put, engine/src/dest/restore-sink.ts).

The Workers source captures three record kinds per script

The Workers source snapshots the account’s deployed scripts through the Cloudflare REST API with the engine’s read-only discovery token; like the configuration source, and unlike every binding source, it is account-scoped and needs no Workers binding. It exists because a customer’s Worker code and wiring are otherwise unrecoverable, including the downpipes engine and console Workers themselves. Each script produces up to three records, name-prefixed by the script id so the selector can scope by name.

Record kind Name What it holds
Content <id> The recoverable code: the raw module or bundle bytes, stored as the record value with no re-encoding.
Settings <id>/settings A canonical-JSON settings record: bindings, the compatibility date and flags, observability, limits, placement and tags, with secret bindings reduced to a checklist.
Versions <id>/versions A small inventory of version id, number and created-on time, best-effort.

The versions record is an inventory only. It deliberately keeps the version id, the number and the created-on time, and drops each version’s own bindings and metadata, because per-version detail can re-contain secret references and is out of scope for v1 (WorkersSource.crawl, engine/src/sources/workers.ts).

Worker secret bindings come back redacted

A Worker’s secret bindings have a write-only value the Cloudflare API never returns, so the settings record reduces every one of them to a name-and-type entry and carries no value. The redaction covers a per-Worker inline secret, a secret key, and a Secrets Store reference (SECRET_BINDING_TYPES, engine/src/sources/workers.ts). For each, the record keeps only the binding’s name and type in a reprovision checklist and drops everything else, including any store id, so the record can never become a value oracle. A non-secret binding (a KV namespace, an R2 bucket, a D1 database, a service or a queue) is kept verbatim, since it is account metadata and not a secret (redactSettings, engine/src/sources/workers.ts).

No Worker secret values are captured

The backup holds no Worker secret values. A secret binding is reduced to a name-and-type entry in a reprovision checklist, with everything else dropped, including the store id. The checklist tells an operator which secrets to re-create at re-deploy; it never holds what they were.

Workers restore is reprovision-only

The engine never blind-redeploys a customer’s Worker from a backup, because redeploying the wrong code or wiring could brick a live service. Instead the snapshot is verified recoverable and surfaced out of band with re-deploy guidance, so the operator re-deploys deliberately (engine/src/admin/restore.ts). There is no destructive Workers restore path: a Workers record is never resolved to a write sink, and the validator proves there is no write sink for a Workers record.

The guidance is specific to the record kind. It is a pure function of the record name so the restore route and the validator share one wording (workersRestoreGuidance, engine/src/admin/restore.ts).

Record Guidance
<id> (content) Re-deploy the script code from the verified content snapshot, through wrangler deploy or the Workers API, then apply its settings record.
<id>/settings Re-create the bindings and secrets from the verified settings snapshot. The secret values were never captured, so the checklist lists their names and types to re-provision.
<id>/versions A version inventory only, informational; re-deploy from the script content record.

The recoverability the guidance relies on is proven, not asserted. The blind restore test decrypts and hash-checks every Workers record, the code, the settings and the versions inventory, writing nothing and surfacing no plaintext, so the operator knows the snapshot is intact before they re-deploy from it.

Fail-open per script, loud on a total failure

The Workers crawl tolerates a gap in one script or one aspect without failing the whole snapshot, but it refuses to report a successful snapshot that captured nothing. A script or a sub-call the token cannot read, a missing scope, a deprecated endpoint, a transient fault that survived retry, or an oversized bundle, becomes an honest unavailable marker record rather than a hard failure, so one bad script never breaks the run (WorkersSource.crawl, engine/src/sources/workers.ts).

Two conditions still throw loudly, because each means the snapshot is empty for a reason the operator must know. If the list call itself fails, that is a broken or under-scoped token rather than a per-script gap, so the crawl throws and names the likely missing Workers Scripts read scope. If at least one aspect was attempted and every one failed, that is also a broken token or a lost scope, and the crawl throws rather than reporting a snapshot that captured nothing.

A script body over the size limit is marked unavailable rather than risking the isolate. The cap is WORKERS_SCRIPT_SIZE_LIMIT, 128 MiB, far above any realistic Worker bundle, since the platform upload limit is a few mebibytes compressed (engine/src/sources/workers.ts).

Worker bodies are the buffered exception

Most large captures in downpipes are streamed, but a Worker script body is the deliberate exception: it is buffered, capped at 128 MiB, not streamed. A script bundle past the cap is marked unavailable rather than buffered, and streaming a script body is a noted future refinement (engine/src/sources/workers.ts). This is the same exception called out elsewhere in the docs, and it is worth stating here so the contrast with the streamed sources is explicit.

Capture Held whole in memory?
A large R2 object No, streamed in windows.
The D1 schema-plus-rows dump No, produced and sealed incrementally.
A Worker script body Yes, buffered, capped at 128 MiB.
A secret value Held in isolate memory for the single sealing pass, then dropped; the plaintext is never persisted, the encrypted value is.

Secrets seals whole in one slice and is non-resumable: it does not implement the checkpointed crawl that the large binding sources use, so it is captured in a single pass rather than resumed across invocations. Workers is different: it is resumable, via a checkpointed per-script crawlFrom whose resume token is the last fully-yielded script id, which lets an account with more than about 300 scripts span the backup across multiple invocations (WorkersSource, engine/src/sources/workers.ts).

Frame the assurance honestly

The honest claim is that a captured snapshot is proven recoverable, not that it has been verified end to end in your browser. Two recoverability proofs back a snapshot, and both are out of band of the console.

Proof What it proves
Blind restore test Decrypts every in-scope record to a discard sink, checks each plaintext hash, and writes nothing and surfaces no plaintext. It proves the records, including a Worker’s code, settings and versions, decrypt and match their signed hashes.
Keyless attestation Verifies the manifest signature, structural completeness (shard hashes and count) and the RUNLOG anti-rollback, with no decryption key and no plaintext read.

Read the attestation for what it is: an engine-side completeness and anti-rollback check, not a content read, and it runs even in the break-glass-only posture where there is no in-account read-back key. Full verification is out of band, because the verifying key is not in the browser; the console asserts a signature is present and well-formed rather than performing the cryptographic verification itself. The recoverability of a Worker snapshot or a secret’s value is therefore something you prove on demand, then act on deliberately.

Where this fits

These pages set the two sources in their wider context.

Last updated .