Repository navigation
Description
Problem
A captured signed directory request can be replayed successfully at exactly one instant: signed.at + SIGNED_WINDOW_MS (5 minutes after its timestamp). At that millisecond the replay guard has already forgotten the nonce, while the verifier still considers the request inside its time window — so the same signed request is accepted twice.
Evidence (verified this run on main 9874fe59)
pkgs/id/src/directory_auth.rs:28—pub const SIGNED_WINDOW_MS: u64 = 5 * 60 * 1000;- Verifier gate (
directory_auth.rs:327, inverify_signed):let window_ok = signed.at.abs_diff(now) <= SIGNED_WINDOW_MS;— the boundary instant is inclusive: atnow == at + SIGNED_WINDOW_MSthe request is still in-window. - Replay guard eviction (
directory_auth.rs:249-250, inReplayGuard::accept):self.seen.retain(|_, seen_at| *seen_at + SIGNED_WINDOW_MS > now);— the entry is exclusive: atnow == seen_at + SIGNED_WINDOW_MSthe nonce is already evicted. (seen_atis the original request'ssigned.at, stored atdirectory_auth.rs:266.) acceptis called only after the signature verifies (directory_auth.rs:352), and a replay carries the original valid signature, so nothing else stops the second acceptance.
Probe (verbatim mirror, run on the laptop)
Mirror of ReplayGuard::accept + the verify_signed window gate (comparisons and the retain predicate character-exact; directory_auth.rs sha256 fab8803d… on main 9874fe59). Probe: ~/git/fleet/probe-2026-10-28-laneB-replay-invite/ (laptop), copy + log in the goal bookkeeping hidden_files/probe-2026-10-28-laneB-replay-invite/:
P1 first use at at: Ok(())
P1 replay at at+WINDOW-1: Err("request was already used")
P1 replay at at+WINDOW (exact): Ok(()) <-- replay accepted
P1 replay at at+WINDOW+1: Err("signed request is outside its time window")
The controls bracket the bug exactly: refused 1 ms before the boundary (nonce still remembered) and 1 ms after it (window closed), accepted only at the boundary millisecond.
Why it matters
The replay guard is the only thing standing between a captured signed request (method + target + body hash are all in the signed message) and a second execution of it inside its validity window. The window is narrow — one millisecond per captured request — but it is deterministic: an attacker who captured a request knows its at exactly and can flood replays around at + 300000 so one lands on the boundary millisecond on the server's clock. The two boundary conventions (<= in the verifier, > in the guard) were clearly meant to describe the same interval; they disagree at exactly one point.
Acceptance criteria
- One convention, both places: a signed request is acceptable at a given
nowiff its nonce is still remembered at thatnow— e.g. the guard retains whileseen_at + SIGNED_WINDOW_MS >= now, or the verifier uses<— with the choice pinned and commented so the two cannot drift apart again. - Regression test in
directory_auth.rstests: first use atat, replay atat + SIGNED_WINDOW_MS - 1, atat + SIGNED_WINDOW_MS, and atat + SIGNED_WINDOW_MS + 1— only the first use succeeds. - Existing
directory_authtests still pass (laptop or CI).
Activity
developing-today commentedon Oct 28, 2026
CODE-717
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
4 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done (PR opened)