Skip to content

Ship an R2-only Cloudflare ciphertext receiver #12

Description

@JacobLinCool

Summary

Ship a deliberately narrow TypeScript Cloudflare Worker that accepts automatic Protocol v1 uploads and durably stores their ciphertext in R2.

The Worker is an untrusted, bounded ingress—not an enrollment, device-authentication, analysis, or management service. Its only successful mutation is creating an immutable R2 object and returning the matching receipt.

Requirements

Scope and trust boundary

  • Expose one Protocol v1 upload POST route.
  • Do not expose list, download, delete, admin, dashboard, configuration, participant, or management APIs.
  • Do not use D1, Queues, KV, Durable Objects, a private decryption key, or any decrypt/parse path.
  • Treat ingress as public and unauthenticated. A deploy-time allowlist may contain accepted configuration digests and researcher signer fingerprints, but these cleartext claims are not participant or device authentication.
  • Fix the accepted protocol version, media type, header set, configuration allowlist, and R2 binding at deployment. Do not add remote configuration.
  • Give researchers read access directly through least-privilege, S3-compatible R2 credentials outside the public Worker.

Request validation and streaming

  • Reject every method, path, media type, transfer shape, or required header not defined by Protocol v1.
  • Require and strictly parse bundle_id, Content-Length, SHA-256 Content-Digest, exact claimed sequence range, event count, configuration digest, and producer metadata defined by Finalize a language-neutral Particeps Protocol v1 and conformance corpus #8/Implement a durable replay-safe Android upload outbox #9.
  • Enforce a 32 MiB body hard limit before and while reading. Reject missing, conflicting, malformed, truncated, overlong, or trailing input.
  • Stream the request ReadableStream directly into R2. Do not buffer the complete body in Worker memory.
  • Supply the expected SHA-256 to R2 so the durable object write itself validates content integrity.
  • Use bundle_id as the immutable R2 object key under a fixed deployment namespace.
  • Store bounded R2 custom metadata for ciphertext SHA-256, byte count, claimed range, claimed event count, configuration digest, producer platform/version, and receive time. Mark all routing metadata as untrusted claims.

Idempotency and receipts

  • Use a create-only conditional R2 write so an existing bundle_id can never be overwritten.
  • After a successful durable new write, return 201 Created with a canonical receipt containing the exact stored identity, digest, size, range, event count, and configuration digest.
  • If the same ID already exists and its object checksum and metadata are identical, return 200 OK with the identical receipt.
  • If the same ID exists with any different digest, size, or metadata, return 409 Conflict; never replace either object.
  • Handle concurrent identical or conflicting requests safely. Rely on R2 conditional writes and strong consistency, not a non-atomic read-then-overwrite sequence.
  • Return no 2xx until R2 confirms durable persistence. A lost response followed by an exact replay must be safe.

Abuse and operations

  • Provide a Wrangler deployment template with explicit bindings, secrets/configuration, routes, compatibility date, and environment separation.
  • Configure or document Cloudflare WAF/rate limiting, a fixed request bound, deployment isolation, R2 lifecycle/retention policy, storage-budget alerts, and emergency ingress disablement.
  • Log only bounded operational reason codes, response status, coarse timing, and non-sensitive aggregate sizes. Do not log participant UUIDs, configuration artifacts, raw headers, URLs containing secrets, or body bytes.
  • Expose no claim of participant authenticity, device authenticity, enrollment, completeness, or decrypted validity in receipts or documentation.

Acceptance criteria

  • A valid upload is streamed to R2 and returns 201 only after the object is readable with the expected checksum and metadata.
  • Response loss followed by exact replay returns the same receipt with 200 and creates no second object.
  • Concurrent exact duplicates yield one creation and safe replay responses; a same-ID content or metadata conflict yields 409 without overwrite.
  • Wrong method/path/media type, malformed headers, digest mismatch, truncation, extra bytes, and bodies above 32 MiB fail without a durable object.
  • Simulated R2 failure never returns 2xx.
  • Worker-memory tests demonstrate that body handling does not buffer a full 32 MiB request.
  • Deployment and log tests prove that the Worker has no R2 list/download/admin surface, no decrypt key, and no sensitive logging.
  • Operational documentation explicitly describes ingress and all stored routing metadata as unauthenticated/untrusted.

Documentation

Add the upload/receipt contract, Worker threat model, Wrangler deployment guide, R2 naming/metadata specification, WAF/rate-limit/lifecycle runbook, retention/cost guidance, researcher S3 read-access guide, and incident procedures.

References: R2 Workers API, R2 consistency, and Workers limits.

Dependencies

Blocked by #8 (final Protocol v1) and #9 (durable exact-replay Android outbox).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Receiver, offline analysis, Parquet, join linksarea: receiverreceiver/ Cloudflare Worker

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions