# Hiding who connects, and what they connect for

*3 September 2026. Response to: "what else can the server do to hide who connects to send vs
receive — i.e. other relays or vpn nodes for incoming perhaps or something similar making camping
on netlify traffic to track just this app a bit more complex."*

## What is already done, so it isn't redone

Checked in the code rather than recalled:

- **Message plaintext is padded to buckets before sealing** — `[256, 1024, 4096, 16384, 32768]`,
  mandatory rather than a setting, with a strict all-zero check on decode so padding cannot become
  a covert channel (`mesh-core-rs/src/payload.rs`, `PROTOCOL.md` §5.1/5.3). Message *length* is
  already coarse.
- **Server responses are padded to buckets** — `[512, 2048, 8192, …]`, top bucket covering the
  worst case (`server/netlify/functions/_shared.mts`, `DECISIONS.md` J12).
- **Every failure returns one shape**, so there is no "does this mailbox exist" oracle.
- **Destination tokens rotate** every ten minutes, and the server is configured not to log IPs.

## What is still visible to somebody camping on the traffic

**1. The hostname, in the clear, on every single connection.** This is the biggest one by a wide
margin. TLS still sends the server name unencrypted, so a passive observer reads
`channelmessenger.netlify.app` and knows this device runs Channel — before any of the padding above
matters. Everything else on this page is a refinement of a signal that this one gives away for
free. No HTTPS/SVCB record with an ECH configuration is published for the host today.

**2. Send is distinguishable from receive, which is the specific question asked.** Not from the URL
— that is inside TLS — but from *direction asymmetry*:

| | Upstream | Downstream |
|---|---|---|
| Deposit (send) | envelope, ≥256 B bucketed, plus headers | small, 512 |
| Collect, no mail | headers only | 512 |
| Collect, mail waiting | headers only | 2048+ |

So an observer separates senders from receivers by upstream size, and mail from no-mail by
downstream bucket. Neither requires decrypting anything.

**3. Bucket boundaries still leak magnitude**, per `J57` — not how many messages, but roughly how
much.

## What can be done, ranked by value per unit of work

### 1. Encrypted Client Hello — removes the "which app" signal entirely
ECH encrypts the server name in the TLS handshake, so the observer sees a connection to a large
shared CDN and nothing else. This is the highest-value change available because it defeats the
signal that makes all the others findable in the first place.

It is not ours to switch on unilaterally: it needs the CDN to support ECH and the client to fetch
the config over DoH. **Verify Netlify's support before planning on it** — and note the convergence
with `ATTACHMENTS-AND-COST.md`, which already argues for moving storage to Cloudflare. If the
platform moves, ECH becomes available with it.

### 2. Oblivious HTTP — this is the thing being described, and it is standardised
RFC 9458. Requests go to a **Relay**, which sees the client's IP but not the request, and forwards
to a **Gateway**, which sees the request but not the IP. **Neither party alone can link an IP to a
request**, and only collusion between two separate organisations reconstructs it. Apple uses it for
Private Relay and Safe Browsing; Cloudflare and Fastly operate managed relays.

The property this buys is worth stating exactly, because it is stronger than what we claim today:
we currently say we *choose not to log* IP addresses. With OHTTP we would say we **cannot see
them** — a claim that survives a subpoena, a change of hosting provider, and a change of heart.

Our traffic is a good fit: small, stateless, frequent requests. Polling through OHTTP is
unremarkable.

**The load-bearing constraint: the relay must be run by somebody else.** If Channel operates both
halves it is an elaborate way of learning exactly what it learns today.

### 3. Pin request and response to one fixed size — kills the asymmetry in §2
Every interaction becomes the same shape in both directions, so send, receive, and receive-nothing
are indistinguishable. Costs about a cent per user per month (`UNIT-ECONOMICS.md`), free on an
egress-free store, and it is a change to one response path plus the client request builders. Should
land with the cheap-poll change, since they touch the same code.

### 4. Let a user point the app at their own proxy
A SOCKS5/Tor setting is a few hours of work and costs nothing to operate. It serves the small
number of people who genuinely need more than the above, using infrastructure they already trust,
**and we run none of it** — so there is nothing for us to be compelled to hand over.

### 5. Shared CDN addressing is a defensive property worth keeping
The host resolves to shared AWS addresses carrying an enormous number of unrelated sites. Blocking
Channel by IP means breaking a great deal of other traffic, which raises the political cost of
doing so. A dedicated IP would be a convenience for us and a gift to a censor.

## What not to do, and why

**Do not run our own VPN or relay nodes.** It is the intuitive answer and it is backwards: whoever
operates the relay sees the source IPs, so building one moves the observation point *to us*. That
is strictly worse than today — we would be creating the record we currently do not hold, and taking
on network-operator legal exposure to do it. The entire value of OHTTP is that the relay is not us.

**Do not plan around domain fronting.** It worked, and then AWS, Google and Cloudflare all shut it
off in 2018. It is not a tool that is available any more.

**Do not build a bespoke mix network.** `J57`'s anonymity-set argument applies directly: a network
whose only users are Channel users identifies Channel users. Standing inside a large existing crowd
beats building a small new one, which is the whole reason ECH and OHTTP are the right shapes.

## Honest summary

**One change dominates: encrypt the hostname.** Until the SNI is hidden, everything else is
tightening a seal on a box with the label still on the outside. OHTTP is the correct second step
and the one that turns a policy promise into a structural one. Pinning sizes is cheap and answers
the send-versus-receive question directly. Running our own relays would feel like progress and
would be a regression.
