Some notes on traffic timing correlations for Cloudflare’s implementation of Privacy Pass

Just for fun I quickly Claude Coded up a quick simulation to see if timing correlation attacks work on Cloudflare’s Privacy Pass deployment. Note that this considers the interactive version of the Privacy Pass protocol, which looks like this:

In this protocol, each time the User attempts to access the website (Resource), the User has to immediately requests a credential from the Issuer. This likely means that there are two session attempts: one at the Resource and one at the Issuer, both with very similar timestamps. The question then is: if the Issuer and Resource are run by the same company, can they easily link these attempts together by comparing session timestamps?

(You might ask why Cloudflare would deploy a privacy-preserving protocol if they also wanted to break your privacy! And that’s a valid question. Here we’re just being paranoid.)

I used Claude Code to implement a traffic analysis simulation at a fraction of Cloudflare’s estimated traffic scale. This simulation simulates many connection attempts with some basic network assumptions, and then ramps it up to a fraction of Cloudflare’s scale and extrapolates. The Github can be found here. I warn you that I’ve barely checked the code and analysis, and so it’s probably all wrong! But I thought I should share the results anyway.

Everything below here is written by Claude itself.


Cloudflare-Scale Privacy Pass Timing Analysis — Results

We simulated one minute of Cloudflare’s Privacy Pass system at 1% scale (preserving the same per-second density), based on public Cloudflare figures:

  • 7.67 billion challenges/day (~5.3M/minute)
  • Batch size 30 → ~177K fresh issuances/minute (scaled to 1,770)
  • 5,000 unique users active in the window (scaled from 500K)
  • 29:1 cached-to-fresh token ratio producing ~51K noise verifier sessions
  • Global/cellular network delays: log-normal, p50 ~100ms, p99 ~410ms

Assumptions & Details

Both the Issuer and Verifier are assumed to log timestamps from high-resolution NTP-synchronised clocks, as would be available on a well-provisioned Linux server (typically clock_gettime(CLOCK_REALTIME) at nanosecond granularity, with NTP jitter on the order of single-digit milliseconds between cooperating datacenters). Timestamps are represented as IEEE 754 double-precision floats of Unix seconds, which preserves sub-microsecond precision for dates within this century. Network propagation delays are modelled as log-normal distributions — a natural fit for latency, which is strictly positive and right-skewed — with parameters calibrated to a global user base that includes LTE and 3G cellular clients: the median one-way delay is roughly 100 ms, the 95th percentile about 270 ms, and the 99th percentile about 410 ms. A candidate Issuer record is considered plausible for a given Verifier session if both the forward delay (challenge → issuance start) and backward delay (issuance end → token received) are non-negative (respecting causality) and fall within a hard cap of 2.0 seconds. Each candidate is scored by a product of Gaussians on the two delay legs (σ = 150 ms), so near-simultaneous events score exponentially higher than distant ones; scores are normalised to sum to 1.0 across all candidates for a session. Candidates whose normalised weight falls below 5% are pruned and weights are re-normalised. A session is declared “matched” only if the top candidate carries at least 70% of the total weight — i.e., it dominates all alternatives. If more than 10 candidates survive pruning, the timing window is considered too crowded to draw any conclusion and the session is marked “no_match” rather than producing a misleadingly long candidate list. The 30:1 ratio of cached-token redemptions to fresh issuances (reflecting Privacy Pass’s batch size of 30) means that ~97% of Verifier sessions have no corresponding Issuer record at all, and are correctly classified as no-match by the analyser.

Experiment 1: Pure timing attack (b = 0)

StatusCount%
Matched310.1%
Ambiguous1,5552.9%
No match51,51497.0%

At ~32 issuer records/second, timing windows overlap so heavily that fewer than 1 in 1,000 sessions are uniquely identified. When a match does occur, confidence is extremely high (mean 0.99) — these are sessions that happened to land in a momentarily sparse window.

Experiment 2: With 2 bits of side-channel info (b = 2)

StatusCount%
Matched3950.7%
Ambiguous1,3762.6%
No match51,32996.7%

Just 2 bits of auxiliary information (e.g. continental geolocation) increases successful identifications by 12.7x. Ambiguous sessions also improve: average candidate count drops from 6.8 to 3.4, and top-candidate confidence nearly doubles (0.23 → 0.42).

Key takeaway

Privacy Pass provides strong anonymity under pure timing attack at Cloudflare’s traffic volume — the sheer density of concurrent issuances makes correlation intractable. However, even modest side-channel information (2 bits) shifts the picture significantly, and additional bits (ASN, browser fingerprint, TLS characteristics) would compound. A real-world attacker with access to both Issuer and Verifier logs and a few bits of auxiliary information could plausibly de-anonymise a non-trivial fraction of sessions.