HTTP Early Data Is a Replay Boundary

Sep 2

TLS and QUIC can reduce repeat-connection latency by allowing a client to send application data before a new handshake completes. That 0-RTT path changes the threat model: early data can be replayed.

The invariant is:

No request may enter early data unless executing it more than once is acceptable or the application supplies a durable deduplication boundary.

Encryption does not provide uniqueness. An attacker does not need to decrypt captured early data to replay it. Infrastructure-level anti-replay controls can reduce exposure, but distributed deployments cannot casually promise global single execution.

Latency optimization crosses an application boundary

In an ordinary full handshake, fresh handshake state helps bind application traffic to the connection. In 0-RTT, a client uses material derived from an earlier session and sends data immediately:

full handshake: ClientHello -> ServerHello -> request
0-RTT resume:   ClientHello + request ----------->

The second path saves a round trip. It also means the server may receive the same valid early request more than once. RFC 9001 explicitly identifies replay vulnerability for QUIC 0-RTT, while RFC 8470 defines how HTTP intermediaries and origins communicate early-data risk.

“POST” is not the whole policy

HTTP method semantics are useful but insufficient. A GET that triggers an email or increments a paid counter is unsafe despite its method. A POST with a strong operation key and transactional deduplication may be replay-safe.

Classify operations by effect:

OperationEarly-data defaultReason
static asset readallowno business mutation
cacheable catalogue readallowrepeat has equivalent effect
account balance readusually denystale or sensitive context may matter
payment creationdenyduplicate financial effect
idempotent upsert with durable keyconditionaldepends on deduplication scope
login or token exchangedenyreplay changes security state

Do not infer replay safety from a framework annotation alone. Trace the operation through queues, email, billing, inventory, and third-party calls.

Use 425 as a protocol boundary

RFC 8470 defines 425 Too Early. An origin can reject a request received in early data when it is unwilling to risk replay. A compliant client can retry after the handshake completes.

At the edge, preserve whether early data was used and enforce an allowlist. A conceptual policy is:

if request.is_early_data:
  if route not in replay_safe_routes:
    return 425
  if request carries authorization with unsafe semantics:
    return 425
forward request with trusted early-data context

Never trust a public client header that merely claims the request was or was not early data. The trusted TLS terminator must set or sanitize this context before forwarding.

Idempotency needs durable scope

For an operation intentionally allowed to retry, store the operation key and outcome atomically with the mutation:

BEGIN;

INSERT INTO operation_result (tenant_id, operation_id, state)
VALUES ($1, $2, 'started')
ON CONFLICT (tenant_id, operation_id) DO NOTHING;

-- Proceed only when this transaction inserted the key.
-- Store the final result in the same transaction as the business mutation.

COMMIT;

The key must be scoped to the authenticated tenant or principal, validated for entropy and length, retained for at least the replay/retry window, and bound to a fingerprint of the intended operation. Reusing one key with a different payload must fail.

An in-memory cache on one instance is not durable deduplication. A request replayed into another region or after a restart bypasses it.

Anti-replay has topology costs

A single TLS terminator can track accepted tickets more easily than a global anycast fleet. Sharing replay state across regions adds coordination and latency—the very costs 0-RTT tries to remove. Keeping state local leaves cross-region replay windows.

This is why infrastructure anti-replay and business idempotency should be treated as layers:

  • edge policy reduces which routes can receive early data;
  • TLS ticket policy limits age and scope;
  • application idempotency prevents duplicate business effects;
  • audit signals detect replays and policy violations.

None should be used to claim exactly-once delivery.

Roll out from evidence

Before enabling early data, measure how much traffic actually resumes sessions, the round-trip time saved by region, and whether that saving changes a customer SLO. Then canary only replay-safe routes.

Observe:

  • early-data requests accepted and rejected by route;
  • 425 responses and successful post-handshake retries;
  • duplicate operation-key conflicts;
  • ticket age and resumption rate;
  • region changes between original and resumed connections;
  • mutations that reached an early-data path unexpectedly.

Test capture-and-replay explicitly in a non-production environment. Verify that unsafe routes return 425, safe reads remain correct, and idempotent writes return the original outcome without repeating side effects.

Common mistakes

  • Assuming encrypted means non-replayable.
  • Enabling 0-RTT for an entire hostname.
  • Allowing early authorization or session-establishment requests.
  • Treating all GET routes as side-effect free.
  • Deduplicating at the HTTP layer while a downstream consumer repeats the effect.
  • Retrying a 425 again as early data.
  • Counting median handshake improvement while ignoring low resumption rates.

The CTO decision

0-RTT is valuable when a round trip materially affects the product and the eligible operation set is narrow and provably replay-safe. Make early-data eligibility an explicit route policy, reject unsafe work with 425, and keep durable operation identity at every side-effect boundary.

The optimization is not “turn on QUIC.” It is buying latency with a carefully bounded replay surface.

References

>