Skip to content
downpipes docs

Precise claims and the honesty posture: what downpipes will and will not say

This page states the claims discipline once, in one reader-facing place, for anyone evaluating downpipes against its own marketing. It is written for an evaluator who wants to separate what the product enforces from what it merely asserts, and to read the residual risks plainly rather than hunt for them.

The rule behind every claim here is simple. A property is stated precisely or it is not stated at all. Where a guarantee has a limit, the limit is named. This page is also the one place in the documentation allowed to write a forbidden claim out loud, because its job is to teach the honest alternative. Everywhere else, a build check refuses the forbidden wording.

The precise-claims discipline, stated once

Each row below pairs a tempting overclaim with the precise wording downpipes uses instead, and the reason the precise wording is the true one. The reasons are drawn from the code, not from older prose.

Do not say Say instead Why
Quantum-proof, or quantum-safe Post-quantum hybrid The seal combines a classical algorithm (X25519, Ed25519) with a post-quantum one (ML-KEM-1024, ML-DSA-87), and both halves must hold. Hybrid is a hedge against one half breaking, not a promise about the future.
Tamper-proof Tamper-evident The audit log is a SHA-384 hash chain. A holder of the underlying storage could re-chain the whole log, but any edit or truncation short of a full re-chain is detectable against an exported head hash. Detection is honest; prevention is not claimed.
Reports are verified in the browser The console asserts a signature is present and well-formed; full verification is out of band The verifying key is not in the browser. Real verification recomputes hashes and checks the hybrid signature against the signer key you pinned, which the engine or the offline reader does, not the console page.
The attestation proves Cloudflare wrote the bytes The attestation is an engine-side completeness and anti-rollback check An attestation confirms the run is complete and that history has not been rolled back behind a high-water mark you keep offline. It is not a check performed by Cloudflare or by the destination store.

Post-quantum hybrid, not quantum-proof

The encryption suite is hybrid by construction. Key encapsulation combines X25519 with ML-KEM-1024 over HKDF-SHA-384, and signatures combine Ed25519 with ML-DSA-87, both halves required with no downgrade path. The point of hybrid is that if either the classical half or the lattice half is broken, the other still holds, so your longest-retention archives are sealed against the harvest-now-decrypt-later window today. A single word like quantum-proof would promise something the construction does not.

One honest caveat from the threat model. The post-quantum library is not guaranteed constant-time, which matters wherever a private key is decapsulated in the account. In the two-recipient posture the in-account engine does decapsulate the long-lived operational private key, and it does so by default in more than one place: the sampled-decrypt step of verify-at-seal runs on the production seal path and opens the just-written run with the operational key, the on-by-default hourly canary reads a run back the same way, and scheduled drills and in-console restores both load the operational identity to open archives. The narrowing that does hold is that the break-glass private key is never loaded or decapsulated in the account at all, only its public half is wrapped to. So the strict break-glass-only posture removes the operational decapsulation surface entirely. That posture no longer pays for it in coverage, either: verify-at-seal and the hourly canary now reach the full keyed tier there by using the run’s own single-run key, which the engine already holds while it is sealing that run. Note what that means for this caveat specifically, because it is the stronger version of the claim rather than a restatement. Opening a run from its own key performs no key encapsulation at all, so there is no decapsulation for a timing side channel to observe, whereas the default posture’s route decapsulates a long-lived key on every run. A constant-time native build remains the documented escalation for the default posture.

Tamper-evident, not tamper-proof

The audit log and the run log are append-only hash chains. They make a later edit detectable, which is what tamper-evident means. They do not make the storage physically unwritable, which is what tamper-proof would imply. If you want modification prevention rather than modification detection, that lives in the storage layer, so pair downpipes with R2 bucket locks or S3 Object Lock at the destination.

An attestation is an engine-side check, not a Cloudflare-side or store-side one

When a run reports as verified, the engine has re-read the archive and recomputed its hashes, checked the Merkle root and the declared record counts, and verified the hybrid signature against the signer key you pinned at ceremony time rather than any key the archive asserts about itself. A signed run log anchors freshness and linearity against an offline high-water mark, so a destination-bucket attacker who serves you an older validly-signed run is observable to a careful recoverer. None of this is a check that Cloudflare runs or that the destination store runs. It is the engine, and the same checks run independently in the offline reader.

Report verification is out of band

The console can tell you a signature is present and structurally well-formed. It cannot tell you the signature is correct, because the verifying key is not in the browser. The trustworthy verification path is the engine on a drill, or the offline reader against your destination bytes and your pinned signer, with neither the vendor nor Cloudflare in the loop.

cf-config restore is not one-click

downpipes backs up Cloudflare zone and account configuration through one registry of 313 surfaces. The honesty contract is in how those surfaces restore, not just how they back up.

Of the 313 surfaces, 60 auto-restore in-band: the engine reads your current live configuration, computes a diff against the snapshot, and applies only the fields that differ, item by item, behind a dry-run preview. The remaining 253 are backup-and-preview only. They are captured in full and verified recoverable, but a diff preview does not exist for them today: a restore plan instead names the tier-specific guidance for applying the change yourself, out of band. Those 253 need dependency-ordered creation with id remapping, or they carry write-only values such as certificate private keys and service-token secrets that a flat replay cannot safely reproduce.

Restore behaviour Surface count What the engine does
Auto-restore in-band 60 Reads live, diffs against the snapshot, applies only changed fields item by item, additive by default, behind a dry-run preview
Backup-and-preview only 253 Captures the surface and verifies it is recoverable; a restore plan names the tier-specific guidance (dependency order, or a re-provision checklist), not a diff, and you apply the change out of band

A different axis you may see in the code

The in-band surfaces are the ones with a diff-driven writer. That is not the same axis as the internal restore-tier label, where the registry splits into idempotent (176), ordered (76) and reprovision (61). The idempotent count of 176 is how cleanly a surface could replay in principle, not how many actually auto-restore today. The reader-facing number is 60 in-band and 253 backup-and-preview only, and quoting the idempotent count as though it were the in-band count would overstate what downpipes writes back by an order of magnitude.

Two further points the code makes plain. The auto-restore path is on the main branch and console-wired there, but it is not deployed to the live demo, so treat any first live use as supervised. And the account scope is guarded rather than left to the operator: the engine refuses an apply whose target account is not provably the archive’s signed origin until the operator types that account id back.

Coverage is shown as honest-unknown, never green for unknown

The configuration backup adapter is fail-open per surface. A surface the token cannot read, a product not on your plan, or a deprecated endpoint becomes an explicit unavailable marker rather than a fatal error or a silent success. When the size of a surface has not been measured yet, the relevant scorer stays inert rather than reporting a number it does not have. The principle the portal follows is that an unknown state is shown as unknown. It is never coloured green to imply a coverage or a verification that has not actually happened.

The default-posture operational-key residual

The key ceremony produces three keypairs, and the engine loads them with deliberate asymmetry. The break-glass private key is never in the account; the engine wraps to its public half and can never unwrap it. The signer private key is in the account, because it has to sign each run. The operational recipient is optional.

In the two-recipient posture the engine also holds an operational key that can decrypt, to support in-account drills and read-back. The honest consequence, stated in the threat model, is that a full compromise of your own Cloudflare account, combined with the destination bytes, can read past archives through that operational path. The break-glass key cannot be reached this way, because its private half is offline.

The strict break-glass-only posture omits the operational recipient. A full account compromise then yields ciphertext and no key that opens it. The cost you accept is narrower than it used to be, and worth stating exactly: your engine still verifies every run as it seals it and still flies its hourly canary, because both use that run’s own single-run key rather than a stored one. What it cannot do is reopen a run it sealed earlier WITHOUT YOU. The unattended work is what stops: scheduled restore tests, the automated drill, and in-account retention pruning, each of which needs a key at a moment when nobody is there to supply one. An in-console restore still works, because you are there to supply the key: the console’s break-glass panel takes your break-glass private in the browser, derives that run’s key there, and wipes it when the restore finishes. Recovery does not move offline; the unattended proof does, and you prove past runs at an attended verification instead. This is a real trade, named here so you can choose it on purpose rather than discover it later.

Posture What the engine can decrypt What a full account compromise reads What you give up
Two recipients (opt-in) Past archives, via the operational key Past archives, when that key is present Nothing operationally
Break-glass-only Nothing at rest Ciphertext only, no usable key Reopening a run it sealed earlier WITHOUT you: scheduled restore tests, the automated drill and in-account retention pruning. An attended in-console restore still works

Recovering downpipes itself is in-account, and some state re-establishes rather than restores

The engine writes a signed, no-custody export of its own control plane to the destination bucket, and the console can rebuild a wiped scheduler from it. Two honest limits sit on this. It is in-account recovery for configuration, not an off-account integrity anchor: an attacker who holds your account and its signer key could write a fresh export, so the export proves authenticity against your pinned key, not freshness after a total wipe. And it recovers configuration, not everything. The downpipe and destination configuration, the role table and the owner-governed policy return; identity-provider connections, notification routing, sessions, passkeys and the audit-log body are re-established after the rebuild rather than restored, and account-held secrets ride only as wrapped envelopes or as markers that they must be re-entered.

The auto-heal that resumes backups after a wiped scheduler depends on a destination it can find, which means a destination declared at deploy time rather than only set in the console, because a console-only destination lived in the state that was wiped. A full account loss is recovered by rebuilding the engine and re-entering that configuration, with your data restorable from the bucket throughout, and the honest recovery time is dominated by standing up a fresh Cloudflare account rather than by downpipes. The end-to-end steps and their prerequisites are in recovering downpipes itself.

Assurance and compliance: what downpipes does not claim

downpipes holds no SOC 2 attestation and no ISO 27001 certification. Both are demand-gated, which means they are pursued only when a named deal requires one, because a self-hosted product that holds none of your data and none of your tokens has no vendor-side system to attest in the usual sense. The compliance pages are a mapping of the product against a control set, not a certificate. No product makes you compliant.

The security posture that does have evidence is the identity component, which carries a single OWASP ASVS 5.0 Level 2 / Level 3-equivalent self-assessment (for an authentication component), with the cited evidence verified against the production code path. A self-assessment is a self-assessment. It is not an external audit, and it is described as exactly what it is.

There is no live assurance dashboard and no live billing surface in the product as it stands, so neither is presented as existing. Point-in-time restore is an engine-API capability with no dedicated console screen in v1, so the portal frames your recovery point as the newest good run picked from a bounded run history, not an arbitrary timestamp picker.

A note on the audit log, because it is easy to get wrong. The audit and SIEM feed carries operator identity, including actor email, source IP, role and approver emails. It is not free of personal information, so it is never described as carrying no personal data.

The offline reader and the archive format, stated precisely

The recovery path is an open-source Go reader, and the way it installs matters for what you can honestly claim. The module is github.com/downpipes/downpipe. go install .../downpipe@latest resolves to v0.2.0, a complete reader carrying prune, recombine and unseal-export, guarded by a tag-drift check in CI; confirm it with downpipe --help before citing it for a real recovery, since a cached module resolution can still hand you an older binary. A from-source build of the default branch remains the no-network alternative. Either way a plain build or go install stamps its version string as dev, since neither runs the maintainer’s release pipeline. The archive format is downpipe/0.1.0, a versioned constant that appears byte-identically in every archive, so the bytes plus your offline key recover with a conformant reader regardless of the engine version.

Say that the format is versioned by semver, not that it is frozen. While the major is 0, a change to a byte-level rule bumps the minor and is a new format identity rather than an erratum against the existing one, and a patch changes no byte-level rule. That is why the honest statement of what a reader opens is the downpipe/0.1.x line, and not any archive whatever its version.

Why a build check enforces this, and what it cannot catch

The documentation build runs a master must-not-claim check over every page. It fires on the forbidden tokens directly, for example tamper-proof, quantum-proof, a wrong Cloudflare surface count, or a workers.dev or other non-custom host in an example. It also fires on affirmative overclaims that a negated, honest mention is allowed to pass, so writing that there is no live assurance dashboard is fine while presenting one as real is not.

This page is the single allowlisted exception, because teaching the honest alternative means naming the wrong claim. The trade is that this page is reviewed in full by a person at the launch gate rather than by the check. A text search cannot catch a paraphrase that means tamper-proof without using the word, which is what the human review exists to catch. Everywhere else, the check catches the literal and near-literal regressions a writer copying older prose would otherwise make.

Where this fits

This page is the documented anchor for the master must-not-claim build check, and the honest phrasings here are the ones the rest of the documentation is held to. The pages below either set the context for these claims or carry the underlying detail.

Last updated .