Guide

Why pre-signed URLs and not a proxy

The design decision behind the upload surface — zero connect0 bandwidth in the data path, capability bound by SigV4 + expiry. The cost / latency / trust tradeoffs.

5/18/2026 · connect0 · 3 min

The core connects data in without ever touching the bytes — pre-signed URLs are why.

Every upload to connect0 PUTs directly to Cloudflare R2. connect0's servers never touch the bytes. This is a deliberate choice — here's why we made it and what alternatives we rejected.

The pre-signed pattern

upload.create_link returns a URL like:

https://<account>.r2.cloudflarestorage.com/connect0-sandbox-fs/projects/<id>/data/foo.csv
  ?X-Amz-Algorithm=AWS4-HMAC-SHA256
  &X-Amz-Credential=...
  &X-Amz-Date=20260518T160000Z
  &X-Amz-Expires=900
  &X-Amz-SignedHeaders=content-type;host
  &X-Amz-Signature=...

The signature is computed server-side by connect0 using R2 access keys. The user gets a URL that proves "this access-key-holder authorized this exact PUT to this exact key with these exact headers, valid for this exact window." No connect0 server is in the data path.

The alternative we rejected: server-side proxy

The other shape — and the more common one in less-considered SaaS — is POST /v1/upload to a connect0 endpoint that buffers the file and forwards it to R2. We didn't ship that. Reasons:

Bandwidth

A 100 MB upload through connect0 is 100 MB ingress and 100 MB egress on the GCP side. Multiply by every upload from every user. Pre-signed: zero bytes through us.

Latency

Proxy upload waits for the full file to land on connect0 before forwarding. Pre-signed: user's TCP connection is to R2 directly.

Reliability

Adding a hop adds failure modes — connect0 OOMs mid-upload, our proxy redeploys mid-stream, etc. R2 has its own SLA we trust.

Cost

GCP charges for egress. Cloudflare doesn't. Even at a few cents per gig, that compounds.

What pre-signed costs us

Complexity at the edges

We need a way to confirm uploads actually happened — the user's client could PUT and immediately tab away, or PUT fail at byte 99, or lie about success. That's why we have the R2 event-notification path: R2 itself reports completion through a Cloudflare Queue, and that's the only signal that flips upload_link.claimed_at.

Less server-side validation

A proxy could scan the file before persisting (size cap, magic-byte check, virus scan). We can't — by the time we know the bytes are in R2, they're already in the sandbox FS. We accept this; the sandbox is the trust boundary anyway. Antivirus inside a user-controlled sandbox would be theatre.

Asymmetric content-length

R2 doesn't honour S3's content-length-range policy. We work around it by signing exact Content-Length into URLs when the agent knows the size. For variable-size uploads, we rely on the post-claim bytes_received audit + the oversize flag.

What the capability gives an attacker if leaked

A pre-signed URL that escapes (Slack DM, log file, terminal scrollback) is a one-shot capability. Worst case if it's leaked:

  • Overwrite one file at one path that the legitimate user was already going to write.
  • Within a 15-min window (configurable up to 1h).
  • Exact content-type (must match the signed header).

The audit row is always there — leaks show up in the ledger. We accept the residual risk because the alternative (longer-TTL or broader-scope credentials) is meaningfully worse.

Next steps

Where this fits

This is the Connect everything pillar of connect0 — the core that connects everything your company runs on. Pre-signed uploads are the design that lets data connect into the core at zero bandwidth and bounded risk. Start building → · All guides →