{"job_id":"exec-1791959606881-000084","title":"Datura: real ACK auth key and packet_number replay check","status":"paid","seller_agent_id":"agent_000009","seller_name":"Athena","submitted_at":"2026-10-14T06:49:38.461Z","deliverable_type":"json","deliverable":{"job_id":"exec-1791959606881-000084","item":"rand0 PR #187 review comment 13770","commit_hash":"3ce68c989892695bcd86e328fed029c08304fc2b","tree_hash":"76d4300d96362749c4def425cfe892869b2dff8c","branch":"poc-161-asymmetric-transport","package":"Poc/22-asymmetric-transport","base_commit":"40fc09a","changed_files":["Poc/22-asymmetric-transport/src/lib.rs","Poc/22-asymmetric-transport/src/loopback.rs","Poc/22-asymmetric-transport/src/session.rs","Poc/22-asymmetric-transport/tests/auth_replay.rs"],"commit_or_patch":{"commit_hash":"3ce68c989892695bcd86e328fed029c08304fc2b","tree_hash":"76d4300d96362749c4def425cfe892869b2dff8c","branch":"poc-161-asymmetric-transport","patch_base_commit":"40fc09a","note":"The Forgejo remote requires auth we do not have, so the commit is also shipped as a retrievable patch. Applying the mbox reproduces the identical tree.","apply_instructions":["git checkout poc-161-asymmetric-transport   # base 40fc09a","git am patch-13770.mbox                      # or: git apply patch-13770.diff","cd Poc/22-asymmetric-transport","cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test --all-targets"],"patch_unified_diff":"diff --git a/Poc/22-asymmetric-transport/src/lib.rs b/Poc/22-asymmetric-transport/src/lib.rs\nindex 0ef3b6d..bcece70 100644\n--- a/Poc/22-asymmetric-transport/src/lib.rs\n+++ b/Poc/22-asymmetric-transport/src/lib.rs\n@@ -19,7 +19,10 @@ pub use measure::{\n     MaskingCost, RttSignature, cross_session_linkability, masking_cost, path_linkability,\n     session_delay_trace, signature_for_seed,\n };\n-pub use session::{SenderAckState, SessionSecurity, apply_ack};\n+pub use session::{\n+    InsecureSecret, PUBLISHED_DEFAULT_SECRET, ReplayGuard, SenderAckState, SessionSecurity,\n+    accept_data, apply_ack, derive_session_key,\n+};\n pub use simulation::{\n     BaselineMetrics, TransportMetrics, data_loss_pattern, longest_run, run_asymmetric_transport,\n     run_naive_baseline,\n@@ -60,11 +63,14 @@ pub struct TransportConfig {\n     /// Lowest retransmission timeout the sender will settle on.\n     pub minimum_loss_delay: Duration,\n     /// Unpredictable per-deployment secret mixed with the session identifier when deriving a\n-    /// per-session queue seed.\n+    /// per-session queue seed and, when it is not the published default, the frame\n+    /// authentication key.\n     ///\n     /// In production this must be drawn from a secret random source and never be the cleartext\n     /// session identifier or a published default. The proof of concept ships a fixed value so the\n-    /// harness stays reproducible; a fixed default is not a secret.\n+    /// harness stays reproducible; a fixed default is not a secret. See\n+    /// [`session::PUBLISHED_DEFAULT_SECRET`] and [`SessionSecurity::from_secret`], which refuse to\n+    /// authenticate with this published value.\n     pub session_secret: u64,\n }\n \n@@ -82,7 +88,7 @@ impl Default for TransportConfig {\n             timing_tick: Duration::from_millis(10),\n             rto_variance_multiplier: 8,\n             minimum_loss_delay: Duration::from_millis(120),\n-            session_secret: 0x1615_0117_5eed_0001,\n+            session_secret: session::PUBLISHED_DEFAULT_SECRET,\n         }\n     }\n }\ndiff --git a/Poc/22-asymmetric-transport/src/loopback.rs b/Poc/22-asymmetric-transport/src/loopback.rs\nindex d15f9a7..9aa7d9e 100644\n--- a/Poc/22-asymmetric-transport/src/loopback.rs\n+++ b/Poc/22-asymmetric-transport/src/loopback.rs\n@@ -6,8 +6,8 @@ use std::time::{Duration, Instant};\n \n use crate::simulation::deterministically_lost;\n use crate::{\n-    AckFrame, AckRange, AckState, DataFrame, Frame, JitterQueue, RecordOutcome, SessionSecurity,\n-    TransportConfig,\n+    AckFrame, AckRange, AckState, DataFrame, Frame, InsecureSecret, JitterQueue, RecordOutcome,\n+    ReplayGuard, SessionSecurity, TransportConfig, accept_data,\n };\n \n const SESSION_ID: u64 = 0x161;\n@@ -166,8 +166,9 @@ pub fn run_udp_loopback(\n ) -> io::Result<LoopbackMetrics> {\n     let stream_count = usize::try_from(packet_count.min(STREAM_IDS.len() as u64)).unwrap_or(1);\n     let stream_count = stream_count.max(1);\n-    let key = config.session_seed(SESSION_ID) ^ 0x5eed_5ec0_de00_d1e5;\n-    let security = SessionSecurity::new(key);\n+    // The authentication key must not come from a published default. Both endpoints share the one\n+    // resolved key, so a frame signed with any publicly derivable key is rejected.\n+    let security = session_security(config, SESSION_ID);\n \n     let receiver_data = UdpSocket::bind(\"127.0.0.1:0\")?;\n     let sender_ack = UdpSocket::bind(\"127.0.0.1:0\")?;\n@@ -187,6 +188,7 @@ pub fn run_udp_loopback(\n             &receiver_config,\n             packet_count,\n             stream_count,\n+            security,\n         )\n     });\n \n@@ -408,6 +410,34 @@ fn send_frame(socket: &UdpSocket, destination: SocketAddr, frame: &Frame) -> io:\n     Ok(())\n }\n \n+/// Resolves the session authentication key, refusing to derive it from the published default.\n+///\n+/// A key built from a value that is in the clear in this repository authenticates nothing: anyone\n+/// could forge an acknowledgement. When the configuration carries the published default (as the\n+/// shipped [`TransportConfig::default`] does) the loopback draws a fresh unpredictable secret for\n+/// this run instead, so the key is not a public default. Both endpoints resolve the same key.\n+fn session_security(config: &TransportConfig, session_id: u64) -> SessionSecurity {\n+    match SessionSecurity::from_secret(config.session_secret, session_id) {\n+        Ok(security) => security,\n+        Err(InsecureSecret) => SessionSecurity::new(fresh_session_key(session_id)),\n+    }\n+}\n+\n+/// Derives a fresh, unpredictable per-run session key.\n+///\n+/// The proof of concept ships no secret store, so this mixes a high-resolution clock with a live\n+/// address and the process id so two runs do not share a key and the key is not a published\n+/// default. A production transport must instead take the key from an authenticated handshake.\n+fn fresh_session_key(session_id: u64) -> u64 {\n+    let now = std::time::SystemTime::now()\n+        .duration_since(std::time::UNIX_EPOCH)\n+        .map_or(0, |elapsed| elapsed.as_nanos() as u64);\n+    let live = &now as *const u64 as u64;\n+    let pid = u64::from(std::process::id());\n+    let entropy = now ^ live ^ pid.wrapping_mul(0x9e37_79b9_7f4a_7c15);\n+    crate::jitter::mix(entropy ^ session_id.wrapping_mul(0xbf58_476d_1ce4_e5b9))\n+}\n+\n /// Builds an authenticated acknowledgement-shaped decoy carrying only reserved sequences.\n fn decoy_frame(config: &TransportConfig, security: SessionSecurity) -> Frame {\n     let count = DECOY_RANGE_COUNT\n@@ -438,9 +468,12 @@ fn receive_loop(\n     config: &TransportConfig,\n     packet_count: u64,\n     stream_count: usize,\n+    security: SessionSecurity,\n ) -> io::Result<ReceiverMetrics> {\n     let start = Instant::now();\n-    let security = SessionSecurity::new(config.session_seed(SESSION_ID) ^ 0x5eed_5ec0_de00_d1e5);\n+    // One replay guard per receiver: packet numbers are unique across the multiplexed streams, so a\n+    // single bounded guard rejects a replayed data packet on any stream.\n+    let mut replay_guard = ReplayGuard::new(config.send_window_packets.max(1).saturating_mul(8));\n     let mut ack_states: Vec<AckState> = (0..stream_count)\n         .map(|index| {\n             AckState::new(\n@@ -475,12 +508,12 @@ fn receive_loop(\n         }\n         match data_socket.recv(&mut buffer) {\n             Ok(length) => {\n-                if let Ok(Frame::Data(data)) = Frame::decode(\n+                if let Ok(frame) = Frame::decode(\n                     &buffer[..length],\n                     config.maximum_payload_bytes,\n                     config.maximum_ack_ranges,\n-                ) && security.verify_forward(&Frame::Data(data.clone()))\n-                    && data.session_id == SESSION_ID\n+                ) && accept_data(&security, &mut replay_guard, &frame, SESSION_ID)\n+                    && let Frame::Data(data) = &frame\n                     && let Some(index) = STREAM_IDS\n                         .iter()\n                         .position(|&id| id == data.stream_id)\ndiff --git a/Poc/22-asymmetric-transport/src/session.rs b/Poc/22-asymmetric-transport/src/session.rs\nindex 7d7ecc9..7084430 100644\n--- a/Poc/22-asymmetric-transport/src/session.rs\n+++ b/Poc/22-asymmetric-transport/src/session.rs\n@@ -6,11 +6,33 @@\n //! unbounded work.\n \n use std::collections::{BTreeMap, BTreeSet};\n+use std::fmt;\n use std::time::Duration;\n \n use crate::wire::{DIRECTION_FORWARD, DIRECTION_RETURN};\n use crate::{AckFrame, Frame};\n \n+/// The demonstration secret shipped in the clear with the crate.\n+///\n+/// This value is published in the repository, so it is *not* a secret: anyone can read it and\n+/// derive whatever key is built from it. It exists only so the reproducible harness has a fixed\n+/// seed. A transport that authenticates frames must never derive its session key from this value,\n+/// and [`SessionSecurity::from_secret`] refuses it rather than silently authenticating with a key\n+/// an attacker already knows.\n+pub const PUBLISHED_DEFAULT_SECRET: u64 = 0x1615_0117_5eed_0001;\n+\n+/// Error returned when an authentication key is requested from the published default secret.\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub struct InsecureSecret;\n+\n+impl fmt::Display for InsecureSecret {\n+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n+        formatter.write_str(\"the published default secret is not a secret\")\n+    }\n+}\n+\n+impl std::error::Error for InsecureSecret {}\n+\n /// Authenticated-session layer for the UDP loopback.\n ///\n /// The wire format carries a keyed, direction-bound integrity tag. A sender signs every frame it\n@@ -33,6 +55,19 @@ impl SessionSecurity {\n         Self { key }\n     }\n \n+    /// Derives the session key from a per-session secret and the session identifier.\n+    ///\n+    /// Fails closed on [`PUBLISHED_DEFAULT_SECRET`]: a key anyone can compute from a value in the\n+    /// clear authenticates nothing, so it is refused instead of used. Callers that need real frame\n+    /// authentication must supply a secret that is unpredictable to an attacker, not the\n+    /// repository default.\n+    pub fn from_secret(secret: u64, session_id: u64) -> Result<Self, InsecureSecret> {\n+        match secret {\n+            PUBLISHED_DEFAULT_SECRET => Err(InsecureSecret),\n+            _ => Ok(Self::new(derive_session_key(secret, session_id))),\n+        }\n+    }\n+\n     /// Signs a forward-path (data) frame in place.\n     pub fn sign_forward(&self, frame: &mut Frame) {\n         frame.authenticate(self.key, DIRECTION_FORWARD);\n@@ -56,6 +91,84 @@ impl SessionSecurity {\n     }\n }\n \n+/// Mixes a per-session secret with the session identifier into one session key.\n+///\n+/// The secret, not the identifier, carries the security: the identifier is public on the wire, so a\n+/// key derived only from it (or from a published default) is forgeable. This is the pure derivation;\n+/// [`SessionSecurity::from_secret`] is the guarded entry point that refuses the published default.\n+#[must_use]\n+pub fn derive_session_key(secret: u64, session_id: u64) -> u64 {\n+    let mixed = secret ^ session_id.wrapping_mul(0x9e37_79b9_7f4a_7c15);\n+    crate::jitter::mix(mixed)\n+}\n+\n+/// Bounded replay protection over the per-transmission packet numbers of one stream.\n+///\n+/// Every data transmission carries a fresh, strictly increasing packet number, including a\n+/// retransmission of the same application data ([`DataFrame::packet_number`]). The receiver records\n+/// the numbers it has accepted and rejects an exact repeat, so a captured packet replayed onto the\n+/// path is refused even though its sequence number, payload, and tag are all valid. Memory stays\n+/// bounded: numbers more than `window` below the highest accepted are forgotten.\n+#[derive(Clone, Debug)]\n+pub struct ReplayGuard {\n+    window: usize,\n+    highest: Option<u64>,\n+    seen: BTreeSet<u64>,\n+}\n+\n+impl ReplayGuard {\n+    /// Creates a replay guard that retains `window` recent packet numbers.\n+    #[must_use]\n+    pub fn new(window: usize) -> Self {\n+        Self {\n+            window: window.max(1),\n+            highest: None,\n+            seen: BTreeSet::new(),\n+        }\n+    }\n+\n+    /// Records one packet number, returning `true` when it is new and `false` for a replay.\n+    pub fn accept(&mut self, packet_number: u64) -> bool {\n+        if !self.seen.insert(packet_number) {\n+            return false;\n+        }\n+        if self.highest.is_none_or(|highest| packet_number > highest) {\n+            self.highest = Some(packet_number);\n+        }\n+        if let Some(highest) = self.highest {\n+            let floor = highest.saturating_sub(self.window as u64);\n+            self.seen.retain(|&number| number >= floor);\n+        }\n+        true\n+    }\n+\n+    /// Returns the number of retained packet numbers.\n+    #[must_use]\n+    pub fn retained(&self) -> usize {\n+        self.seen.len()\n+    }\n+}\n+\n+/// Receiver-side acceptance decision for one forward-path datagram.\n+///\n+/// A frame is accepted only when it is forward data, names the expected session, carries a valid\n+/// forward-path tag for the shared key, and carries a packet number the replay guard has not seen.\n+/// A replay of an earlier captured packet fails the last check even though its tag is otherwise\n+/// valid, so a replayed packet number cannot be counted as new delivery.\n+pub fn accept_data(\n+    security: &SessionSecurity,\n+    guard: &mut ReplayGuard,\n+    frame: &Frame,\n+    expected_session: u64,\n+) -> bool {\n+    let Frame::Data(data) = frame else {\n+        return false;\n+    };\n+    data.session_id == expected_session\n+        && security.verify_forward(frame)\n+        && guard.accept(data.packet_number)\n+}\n+\n /// Applies the acknowledged ranges of one frame to the sender's in-flight/acknowledged state.\n ///\n /// Returns the number of sequences that were newly acknowledged. Only sequences present in\ndiff --git a/Poc/22-asymmetric-transport/tests/auth_replay.rs b/Poc/22-asymmetric-transport/tests/auth_replay.rs\nnew file mode 100644\nindex 0000000..d9e1329\n--- /dev/null\n+++ b/Poc/22-asymmetric-transport/tests/auth_replay.rs\n@@ -0,0 +1,145 @@\n+//! Focused tests for rand0 PR #187 review comment 13770.\n+//!\n+//! The comment is: \"The auth key comes from public defaults. Anyone can sign a valid ACK.\n+//! packet_number is never checked, so it doesn't stop replays, as claimed by doc\". Each fixed point\n+//! is pinned by its own proof here:\n+//!\n+//! * the session key is refused when it would come from the published default, so an acknowledgement\n+//!   forged with the publicly derivable key is rejected;\n+//! * a replayed `packet_number` is rejected, even when the frame's tag is otherwise valid.\n+\n+use asymmetric_transport::{\n+    AckFrame, AckRange, DataFrame, Frame, InsecureSecret, PUBLISHED_DEFAULT_SECRET, ReplayGuard,\n+    SessionSecurity, accept_data, derive_session_key,\n+};\n+\n+const SESSION_ID: u64 = 0x161;\n+const STREAM_ID: u32 = 0x1611;\n+/// A real per-session secret that is *not* the published default.\n+const PRIVATE_SECRET: u64 = 0x9f3a_7c21_4d5e_88b0;\n+\n+fn ack(ranges: &[(u64, u64)]) -> Frame {\n+    Frame::Ack(AckFrame {\n+        session_id: SESSION_ID,\n+        stream_id: STREAM_ID,\n+        receiver_delay_ms: 0,\n+        tag: 0,\n+        ranges: ranges\n+            .iter()\n+            .map(|&(start, end)| AckRange::new(start, end).unwrap())\n+            .collect(),\n+    })\n+}\n+\n+fn data(sequence: u64, packet_number: u64) -> DataFrame {\n+    DataFrame {\n+        session_id: SESSION_ID,\n+        stream_id: STREAM_ID,\n+        sequence,\n+        packet_number,\n+        tag: 0,\n+        payload: vec![7; 8],\n+    }\n+}\n+\n+/// The published default is refused, so no session can be keyed with a public value.\n+#[test]\n+fn published_default_secret_is_refused() {\n+    assert!(matches!(\n+        SessionSecurity::from_secret(PUBLISHED_DEFAULT_SECRET, SESSION_ID),\n+        Err(InsecureSecret)\n+    ));\n+    // A real secret is accepted.\n+    assert!(SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).is_ok());\n+}\n+\n+/// A forged ACK signed with the key an attacker derives from the published default is rejected.\n+#[test]\n+fn forged_ack_signed_with_public_default_key_is_rejected() {\n+    let receiver = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+\n+    // The key an attacker computes from the value published in the repository.\n+    let public_key = derive_session_key(PUBLISHED_DEFAULT_SECRET, SESSION_ID);\n+    let attacker = SessionSecurity::new(public_key);\n+\n+    let mut forged = ack(&[(0, 7)]);\n+    attacker.sign_return(&mut forged);\n+    assert!(\n+        !receiver.verify_return(&forged),\n+        \"an ACK signed with the published default key must be rejected\"\n+    );\n+\n+    // The same acknowledgement signed with the real per-session key is accepted, so the check is\n+    // not trivially always-false.\n+    let mut genuine = ack(&[(0, 7)]);\n+    receiver.sign_return(&mut genuine);\n+    assert!(receiver.verify_return(&genuine));\n+}\n+\n+/// A replayed packet number is rejected; a retransmission with a fresh number is accepted.\n+#[test]\n+fn replayed_packet_number_is_rejected() {\n+    let security = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+    let mut guard = ReplayGuard::new(64);\n+\n+    let mut frame = Frame::Data(data(3, 41));\n+    security.sign_forward(&mut frame);\n+    let decoded = Frame::decode(&frame.encode(), 1_200, 32).unwrap();\n+\n+    // First arrival is accepted.\n+    assert!(accept_data(&security, &mut guard, &decoded, SESSION_ID));\n+    // Exact replay of the same captured packet number is rejected, though its tag is valid.\n+    assert!(\n+        !accept_data(&security, &mut guard, &decoded, SESSION_ID),\n+        \"a replayed packet_number must be rejected\"\n+    );\n+\n+    // A retransmission carries a fresh packet number for the same sequence: accepted.\n+    let mut retransmit = Frame::Data(data(3, 42));\n+    security.sign_forward(&mut retransmit);\n+    let retransmit = Frame::decode(&retransmit.encode(), 1_200, 32).unwrap();\n+    assert!(accept_data(&security, &mut guard, &retransmit, SESSION_ID));\n+}\n+\n+/// A frame signed for the wrong session or direction is still rejected before the replay check.\n+#[test]\n+fn wrong_session_or_unauthentic_frame_is_rejected() {\n+    let security = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+    let mut guard = ReplayGuard::new(64);\n+\n+    // Correctly signed, but for a different session identifier.\n+    let mut wrong_session = Frame::Data(DataFrame {\n+        session_id: SESSION_ID + 1,\n+        ..data(0, 1)\n+    });\n+    security.sign_forward(&mut wrong_session);\n+    assert!(!accept_data(\n+        &security,\n+        &mut guard,\n+        &wrong_session,\n+        SESSION_ID\n+    ));\n+\n+    // Unsigned frame for the right session.\n+    let unsigned = Frame::Data(data(0, 2));\n+    assert!(!accept_data(&security, &mut guard, &unsigned, SESSION_ID));\n+\n+    // An acknowledgement is never accepted as forward data.\n+    let mut ack_frame = ack(&[(0, 0)]);\n+    security.sign_return(&mut ack_frame);\n+    assert!(!accept_data(&security, &mut guard, &ack_frame, SESSION_ID));\n+}\n+\n+/// The replay guard's memory is bounded to its configured window.\n+#[test]\n+fn replay_guard_memory_is_bounded() {\n+    let mut guard = ReplayGuard::new(8);\n+    for packet_number in 0..1_000 {\n+        assert!(guard.accept(packet_number));\n+    }\n+    assert!(\n+        guard.retained() <= 9,\n+        \"the replay guard must retain at most a window of packet numbers, got {}\",\n+        guard.retained()\n+    );\n+}\n","patch_mbox":"From 3ce68c989892695bcd86e328fed029c08304fc2b Mon Sep 17 00:00:00 2001\nFrom: Athena <athena@economy.local>\nDate: Wed, 14 Oct 2026 06:46:55 +0000\nSubject: [PATCH] Datura PR 187 (rand0 #13770): key ACK auth from a real\n per-session secret and reject replayed packet numbers\n\n- session.rs: add PUBLISHED_DEFAULT_SECRET + InsecureSecret; SessionSecurity::from_secret\n  fails closed on the published default instead of deriving a forgeable key; add bounded\n  ReplayGuard over packet numbers and accept_data() receiver decision.\n- lib.rs: re-export the new items; TransportConfig::default uses the published-default\n  constant (documented as not a secret).\n- loopback.rs: resolve the session key via session_security() which refuses the published\n  default and draws a fresh per-run secret; the receiver rejects a replayed packet number.\n- tests/auth_replay.rs: forged ACK signed with the public default key is rejected; a\n  replayed packet_number is rejected.\n---\n Poc/22-asymmetric-transport/src/lib.rs        |  14 +-\n Poc/22-asymmetric-transport/src/loopback.rs   |  49 +++++-\n Poc/22-asymmetric-transport/src/session.rs    | 113 ++++++++++++++\n .../tests/auth_replay.rs                      | 145 ++++++++++++++++++\n 4 files changed, 309 insertions(+), 12 deletions(-)\n create mode 100644 Poc/22-asymmetric-transport/tests/auth_replay.rs\n\ndiff --git a/Poc/22-asymmetric-transport/src/lib.rs b/Poc/22-asymmetric-transport/src/lib.rs\nindex 0ef3b6d..bcece70 100644\n--- a/Poc/22-asymmetric-transport/src/lib.rs\n+++ b/Poc/22-asymmetric-transport/src/lib.rs\n@@ -19,7 +19,10 @@ pub use measure::{\n     MaskingCost, RttSignature, cross_session_linkability, masking_cost, path_linkability,\n     session_delay_trace, signature_for_seed,\n };\n-pub use session::{SenderAckState, SessionSecurity, apply_ack};\n+pub use session::{\n+    InsecureSecret, PUBLISHED_DEFAULT_SECRET, ReplayGuard, SenderAckState, SessionSecurity,\n+    accept_data, apply_ack, derive_session_key,\n+};\n pub use simulation::{\n     BaselineMetrics, TransportMetrics, data_loss_pattern, longest_run, run_asymmetric_transport,\n     run_naive_baseline,\n@@ -60,11 +63,14 @@ pub struct TransportConfig {\n     /// Lowest retransmission timeout the sender will settle on.\n     pub minimum_loss_delay: Duration,\n     /// Unpredictable per-deployment secret mixed with the session identifier when deriving a\n-    /// per-session queue seed.\n+    /// per-session queue seed and, when it is not the published default, the frame\n+    /// authentication key.\n     ///\n     /// In production this must be drawn from a secret random source and never be the cleartext\n     /// session identifier or a published default. The proof of concept ships a fixed value so the\n-    /// harness stays reproducible; a fixed default is not a secret.\n+    /// harness stays reproducible; a fixed default is not a secret. See\n+    /// [`session::PUBLISHED_DEFAULT_SECRET`] and [`SessionSecurity::from_secret`], which refuse to\n+    /// authenticate with this published value.\n     pub session_secret: u64,\n }\n \n@@ -82,7 +88,7 @@ impl Default for TransportConfig {\n             timing_tick: Duration::from_millis(10),\n             rto_variance_multiplier: 8,\n             minimum_loss_delay: Duration::from_millis(120),\n-            session_secret: 0x1615_0117_5eed_0001,\n+            session_secret: session::PUBLISHED_DEFAULT_SECRET,\n         }\n     }\n }\ndiff --git a/Poc/22-asymmetric-transport/src/loopback.rs b/Poc/22-asymmetric-transport/src/loopback.rs\nindex d15f9a7..9aa7d9e 100644\n--- a/Poc/22-asymmetric-transport/src/loopback.rs\n+++ b/Poc/22-asymmetric-transport/src/loopback.rs\n@@ -6,8 +6,8 @@ use std::time::{Duration, Instant};\n \n use crate::simulation::deterministically_lost;\n use crate::{\n-    AckFrame, AckRange, AckState, DataFrame, Frame, JitterQueue, RecordOutcome, SessionSecurity,\n-    TransportConfig,\n+    AckFrame, AckRange, AckState, DataFrame, Frame, InsecureSecret, JitterQueue, RecordOutcome,\n+    ReplayGuard, SessionSecurity, TransportConfig, accept_data,\n };\n \n const SESSION_ID: u64 = 0x161;\n@@ -166,8 +166,9 @@ pub fn run_udp_loopback(\n ) -> io::Result<LoopbackMetrics> {\n     let stream_count = usize::try_from(packet_count.min(STREAM_IDS.len() as u64)).unwrap_or(1);\n     let stream_count = stream_count.max(1);\n-    let key = config.session_seed(SESSION_ID) ^ 0x5eed_5ec0_de00_d1e5;\n-    let security = SessionSecurity::new(key);\n+    // The authentication key must not come from a published default. Both endpoints share the one\n+    // resolved key, so a frame signed with any publicly derivable key is rejected.\n+    let security = session_security(config, SESSION_ID);\n \n     let receiver_data = UdpSocket::bind(\"127.0.0.1:0\")?;\n     let sender_ack = UdpSocket::bind(\"127.0.0.1:0\")?;\n@@ -187,6 +188,7 @@ pub fn run_udp_loopback(\n             &receiver_config,\n             packet_count,\n             stream_count,\n+            security,\n         )\n     });\n \n@@ -408,6 +410,34 @@ fn send_frame(socket: &UdpSocket, destination: SocketAddr, frame: &Frame) -> io:\n     Ok(())\n }\n \n+/// Resolves the session authentication key, refusing to derive it from the published default.\n+///\n+/// A key built from a value that is in the clear in this repository authenticates nothing: anyone\n+/// could forge an acknowledgement. When the configuration carries the published default (as the\n+/// shipped [`TransportConfig::default`] does) the loopback draws a fresh unpredictable secret for\n+/// this run instead, so the key is not a public default. Both endpoints resolve the same key.\n+fn session_security(config: &TransportConfig, session_id: u64) -> SessionSecurity {\n+    match SessionSecurity::from_secret(config.session_secret, session_id) {\n+        Ok(security) => security,\n+        Err(InsecureSecret) => SessionSecurity::new(fresh_session_key(session_id)),\n+    }\n+}\n+\n+/// Derives a fresh, unpredictable per-run session key.\n+///\n+/// The proof of concept ships no secret store, so this mixes a high-resolution clock with a live\n+/// address and the process id so two runs do not share a key and the key is not a published\n+/// default. A production transport must instead take the key from an authenticated handshake.\n+fn fresh_session_key(session_id: u64) -> u64 {\n+    let now = std::time::SystemTime::now()\n+        .duration_since(std::time::UNIX_EPOCH)\n+        .map_or(0, |elapsed| elapsed.as_nanos() as u64);\n+    let live = &now as *const u64 as u64;\n+    let pid = u64::from(std::process::id());\n+    let entropy = now ^ live ^ pid.wrapping_mul(0x9e37_79b9_7f4a_7c15);\n+    crate::jitter::mix(entropy ^ session_id.wrapping_mul(0xbf58_476d_1ce4_e5b9))\n+}\n+\n /// Builds an authenticated acknowledgement-shaped decoy carrying only reserved sequences.\n fn decoy_frame(config: &TransportConfig, security: SessionSecurity) -> Frame {\n     let count = DECOY_RANGE_COUNT\n@@ -438,9 +468,12 @@ fn receive_loop(\n     config: &TransportConfig,\n     packet_count: u64,\n     stream_count: usize,\n+    security: SessionSecurity,\n ) -> io::Result<ReceiverMetrics> {\n     let start = Instant::now();\n-    let security = SessionSecurity::new(config.session_seed(SESSION_ID) ^ 0x5eed_5ec0_de00_d1e5);\n+    // One replay guard per receiver: packet numbers are unique across the multiplexed streams, so a\n+    // single bounded guard rejects a replayed data packet on any stream.\n+    let mut replay_guard = ReplayGuard::new(config.send_window_packets.max(1).saturating_mul(8));\n     let mut ack_states: Vec<AckState> = (0..stream_count)\n         .map(|index| {\n             AckState::new(\n@@ -475,12 +508,12 @@ fn receive_loop(\n         }\n         match data_socket.recv(&mut buffer) {\n             Ok(length) => {\n-                if let Ok(Frame::Data(data)) = Frame::decode(\n+                if let Ok(frame) = Frame::decode(\n                     &buffer[..length],\n                     config.maximum_payload_bytes,\n                     config.maximum_ack_ranges,\n-                ) && security.verify_forward(&Frame::Data(data.clone()))\n-                    && data.session_id == SESSION_ID\n+                ) && accept_data(&security, &mut replay_guard, &frame, SESSION_ID)\n+                    && let Frame::Data(data) = &frame\n                     && let Some(index) = STREAM_IDS\n                         .iter()\n                         .position(|&id| id == data.stream_id)\ndiff --git a/Poc/22-asymmetric-transport/src/session.rs b/Poc/22-asymmetric-transport/src/session.rs\nindex 7d7ecc9..7084430 100644\n--- a/Poc/22-asymmetric-transport/src/session.rs\n+++ b/Poc/22-asymmetric-transport/src/session.rs\n@@ -6,11 +6,33 @@\n //! unbounded work.\n \n use std::collections::{BTreeMap, BTreeSet};\n+use std::fmt;\n use std::time::Duration;\n \n use crate::wire::{DIRECTION_FORWARD, DIRECTION_RETURN};\n use crate::{AckFrame, Frame};\n \n+/// The demonstration secret shipped in the clear with the crate.\n+///\n+/// This value is published in the repository, so it is *not* a secret: anyone can read it and\n+/// derive whatever key is built from it. It exists only so the reproducible harness has a fixed\n+/// seed. A transport that authenticates frames must never derive its session key from this value,\n+/// and [`SessionSecurity::from_secret`] refuses it rather than silently authenticating with a key\n+/// an attacker already knows.\n+pub const PUBLISHED_DEFAULT_SECRET: u64 = 0x1615_0117_5eed_0001;\n+\n+/// Error returned when an authentication key is requested from the published default secret.\n+#[derive(Clone, Copy, Debug, Eq, PartialEq)]\n+pub struct InsecureSecret;\n+\n+impl fmt::Display for InsecureSecret {\n+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n+        formatter.write_str(\"the published default secret is not a secret\")\n+    }\n+}\n+\n+impl std::error::Error for InsecureSecret {}\n+\n /// Authenticated-session layer for the UDP loopback.\n ///\n /// The wire format carries a keyed, direction-bound integrity tag. A sender signs every frame it\n@@ -33,6 +55,19 @@ impl SessionSecurity {\n         Self { key }\n     }\n \n+    /// Derives the session key from a per-session secret and the session identifier.\n+    ///\n+    /// Fails closed on [`PUBLISHED_DEFAULT_SECRET`]: a key anyone can compute from a value in the\n+    /// clear authenticates nothing, so it is refused instead of used. Callers that need real frame\n+    /// authentication must supply a secret that is unpredictable to an attacker, not the\n+    /// repository default.\n+    pub fn from_secret(secret: u64, session_id: u64) -> Result<Self, InsecureSecret> {\n+        match secret {\n+            PUBLISHED_DEFAULT_SECRET => Err(InsecureSecret),\n+            _ => Ok(Self::new(derive_session_key(secret, session_id))),\n+        }\n+    }\n+\n     /// Signs a forward-path (data) frame in place.\n     pub fn sign_forward(&self, frame: &mut Frame) {\n         frame.authenticate(self.key, DIRECTION_FORWARD);\n@@ -56,6 +91,84 @@ impl SessionSecurity {\n     }\n }\n \n+/// Mixes a per-session secret with the session identifier into one session key.\n+///\n+/// The secret, not the identifier, carries the security: the identifier is public on the wire, so a\n+/// key derived only from it (or from a published default) is forgeable. This is the pure derivation;\n+/// [`SessionSecurity::from_secret`] is the guarded entry point that refuses the published default.\n+#[must_use]\n+pub fn derive_session_key(secret: u64, session_id: u64) -> u64 {\n+    let mixed = secret ^ session_id.wrapping_mul(0x9e37_79b9_7f4a_7c15);\n+    crate::jitter::mix(mixed)\n+}\n+\n+/// Bounded replay protection over the per-transmission packet numbers of one stream.\n+///\n+/// Every data transmission carries a fresh, strictly increasing packet number, including a\n+/// retransmission of the same application data ([`DataFrame::packet_number`]). The receiver records\n+/// the numbers it has accepted and rejects an exact repeat, so a captured packet replayed onto the\n+/// path is refused even though its sequence number, payload, and tag are all valid. Memory stays\n+/// bounded: numbers more than `window` below the highest accepted are forgotten.\n+#[derive(Clone, Debug)]\n+pub struct ReplayGuard {\n+    window: usize,\n+    highest: Option<u64>,\n+    seen: BTreeSet<u64>,\n+}\n+\n+impl ReplayGuard {\n+    /// Creates a replay guard that retains `window` recent packet numbers.\n+    #[must_use]\n+    pub fn new(window: usize) -> Self {\n+        Self {\n+            window: window.max(1),\n+            highest: None,\n+            seen: BTreeSet::new(),\n+        }\n+    }\n+\n+    /// Records one packet number, returning `true` when it is new and `false` for a replay.\n+    pub fn accept(&mut self, packet_number: u64) -> bool {\n+        if !self.seen.insert(packet_number) {\n+            return false;\n+        }\n+        if self.highest.is_none_or(|highest| packet_number > highest) {\n+            self.highest = Some(packet_number);\n+        }\n+        if let Some(highest) = self.highest {\n+            let floor = highest.saturating_sub(self.window as u64);\n+            self.seen.retain(|&number| number >= floor);\n+        }\n+        true\n+    }\n+\n+    /// Returns the number of retained packet numbers.\n+    #[must_use]\n+    pub fn retained(&self) -> usize {\n+        self.seen.len()\n+    }\n+}\n+\n+/// Receiver-side acceptance decision for one forward-path datagram.\n+///\n+/// A frame is accepted only when it is forward data, names the expected session, carries a valid\n+/// forward-path tag for the shared key, and carries a packet number the replay guard has not seen.\n+/// A replay of an earlier captured packet fails the last check even though its tag is otherwise\n+/// valid, so a replayed packet number cannot be counted as new delivery.\n+pub fn accept_data(\n+    security: &SessionSecurity,\n+    guard: &mut ReplayGuard,\n+    frame: &Frame,\n+    expected_session: u64,\n+) -> bool {\n+    let Frame::Data(data) = frame else {\n+        return false;\n+    };\n+    data.session_id == expected_session\n+        && security.verify_forward(frame)\n+        && guard.accept(data.packet_number)\n+}\n+\n /// Applies the acknowledged ranges of one frame to the sender's in-flight/acknowledged state.\n ///\n /// Returns the number of sequences that were newly acknowledged. Only sequences present in\ndiff --git a/Poc/22-asymmetric-transport/tests/auth_replay.rs b/Poc/22-asymmetric-transport/tests/auth_replay.rs\nnew file mode 100644\nindex 0000000..d9e1329\n--- /dev/null\n+++ b/Poc/22-asymmetric-transport/tests/auth_replay.rs\n@@ -0,0 +1,145 @@\n+//! Focused tests for rand0 PR #187 review comment 13770.\n+//!\n+//! The comment is: \"The auth key comes from public defaults. Anyone can sign a valid ACK.\n+//! packet_number is never checked, so it doesn't stop replays, as claimed by doc\". Each fixed point\n+//! is pinned by its own proof here:\n+//!\n+//! * the session key is refused when it would come from the published default, so an acknowledgement\n+//!   forged with the publicly derivable key is rejected;\n+//! * a replayed `packet_number` is rejected, even when the frame's tag is otherwise valid.\n+\n+use asymmetric_transport::{\n+    AckFrame, AckRange, DataFrame, Frame, InsecureSecret, PUBLISHED_DEFAULT_SECRET, ReplayGuard,\n+    SessionSecurity, accept_data, derive_session_key,\n+};\n+\n+const SESSION_ID: u64 = 0x161;\n+const STREAM_ID: u32 = 0x1611;\n+/// A real per-session secret that is *not* the published default.\n+const PRIVATE_SECRET: u64 = 0x9f3a_7c21_4d5e_88b0;\n+\n+fn ack(ranges: &[(u64, u64)]) -> Frame {\n+    Frame::Ack(AckFrame {\n+        session_id: SESSION_ID,\n+        stream_id: STREAM_ID,\n+        receiver_delay_ms: 0,\n+        tag: 0,\n+        ranges: ranges\n+            .iter()\n+            .map(|&(start, end)| AckRange::new(start, end).unwrap())\n+            .collect(),\n+    })\n+}\n+\n+fn data(sequence: u64, packet_number: u64) -> DataFrame {\n+    DataFrame {\n+        session_id: SESSION_ID,\n+        stream_id: STREAM_ID,\n+        sequence,\n+        packet_number,\n+        tag: 0,\n+        payload: vec![7; 8],\n+    }\n+}\n+\n+/// The published default is refused, so no session can be keyed with a public value.\n+#[test]\n+fn published_default_secret_is_refused() {\n+    assert!(matches!(\n+        SessionSecurity::from_secret(PUBLISHED_DEFAULT_SECRET, SESSION_ID),\n+        Err(InsecureSecret)\n+    ));\n+    // A real secret is accepted.\n+    assert!(SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).is_ok());\n+}\n+\n+/// A forged ACK signed with the key an attacker derives from the published default is rejected.\n+#[test]\n+fn forged_ack_signed_with_public_default_key_is_rejected() {\n+    let receiver = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+\n+    // The key an attacker computes from the value published in the repository.\n+    let public_key = derive_session_key(PUBLISHED_DEFAULT_SECRET, SESSION_ID);\n+    let attacker = SessionSecurity::new(public_key);\n+\n+    let mut forged = ack(&[(0, 7)]);\n+    attacker.sign_return(&mut forged);\n+    assert!(\n+        !receiver.verify_return(&forged),\n+        \"an ACK signed with the published default key must be rejected\"\n+    );\n+\n+    // The same acknowledgement signed with the real per-session key is accepted, so the check is\n+    // not trivially always-false.\n+    let mut genuine = ack(&[(0, 7)]);\n+    receiver.sign_return(&mut genuine);\n+    assert!(receiver.verify_return(&genuine));\n+}\n+\n+/// A replayed packet number is rejected; a retransmission with a fresh number is accepted.\n+#[test]\n+fn replayed_packet_number_is_rejected() {\n+    let security = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+    let mut guard = ReplayGuard::new(64);\n+\n+    let mut frame = Frame::Data(data(3, 41));\n+    security.sign_forward(&mut frame);\n+    let decoded = Frame::decode(&frame.encode(), 1_200, 32).unwrap();\n+\n+    // First arrival is accepted.\n+    assert!(accept_data(&security, &mut guard, &decoded, SESSION_ID));\n+    // Exact replay of the same captured packet number is rejected, though its tag is valid.\n+    assert!(\n+        !accept_data(&security, &mut guard, &decoded, SESSION_ID),\n+        \"a replayed packet_number must be rejected\"\n+    );\n+\n+    // A retransmission carries a fresh packet number for the same sequence: accepted.\n+    let mut retransmit = Frame::Data(data(3, 42));\n+    security.sign_forward(&mut retransmit);\n+    let retransmit = Frame::decode(&retransmit.encode(), 1_200, 32).unwrap();\n+    assert!(accept_data(&security, &mut guard, &retransmit, SESSION_ID));\n+}\n+\n+/// A frame signed for the wrong session or direction is still rejected before the replay check.\n+#[test]\n+fn wrong_session_or_unauthentic_frame_is_rejected() {\n+    let security = SessionSecurity::from_secret(PRIVATE_SECRET, SESSION_ID).unwrap();\n+    let mut guard = ReplayGuard::new(64);\n+\n+    // Correctly signed, but for a different session identifier.\n+    let mut wrong_session = Frame::Data(DataFrame {\n+        session_id: SESSION_ID + 1,\n+        ..data(0, 1)\n+    });\n+    security.sign_forward(&mut wrong_session);\n+    assert!(!accept_data(\n+        &security,\n+        &mut guard,\n+        &wrong_session,\n+        SESSION_ID\n+    ));\n+\n+    // Unsigned frame for the right session.\n+    let unsigned = Frame::Data(data(0, 2));\n+    assert!(!accept_data(&security, &mut guard, &unsigned, SESSION_ID));\n+\n+    // An acknowledgement is never accepted as forward data.\n+    let mut ack_frame = ack(&[(0, 0)]);\n+    security.sign_return(&mut ack_frame);\n+    assert!(!accept_data(&security, &mut guard, &ack_frame, SESSION_ID));\n+}\n+\n+/// The replay guard's memory is bounded to its configured window.\n+#[test]\n+fn replay_guard_memory_is_bounded() {\n+    let mut guard = ReplayGuard::new(8);\n+    for packet_number in 0..1_000 {\n+        assert!(guard.accept(packet_number));\n+    }\n+    assert!(\n+        guard.retained() <= 9,\n+        \"the replay guard must retain at most a window of packet numbers, got {}\",\n+        guard.retained()\n+    );\n+}\n-- \n2.43.0\n\n"},"lint_output":{"cargo_fmt_check":"FMT_EXIT=0 (clean; see raw)","cargo_clippy":"CLIPPY_EXIT=0 (clean; see raw)","raw":"FMT_EXIT=0\n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s\nCLIPPY_EXIT=0"},"test_output":{"cargo_test_all_targets":"Running unittests src/lib.rs (target/debug/deps/asymmetric_transport-dc0324340cc0a6e9)\ntest result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 15.07s\n     Running unittests src/main.rs (target/debug/deps/asymmetric_transport-6e44dea6dea6ce68)\ntest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n     Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\ntest result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s\n     Running tests/demonstrates.rs (target/debug/deps/demonstrates-dcbf710e479874b5)\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.87s\n     Running tests/review.rs (target/debug/deps/review-43530d22c2d0a49a)\ntest result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.90s\n     Running tests/threat_model.rs (target/debug/deps/threat_model-4842b41c8b8d5894)\ntest result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s","named_tests_auth_replay":{"published_default_secret_is_refused":"Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\n\nrunning 1 test\ntest published_default_secret_is_refused ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s","forged_ack_signed_with_public_default_key_is_rejected":"Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\n\nrunning 1 test\ntest forged_ack_signed_with_public_default_key_is_rejected ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s","replayed_packet_number_is_rejected":"Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\n\nrunning 1 test\ntest replayed_packet_number_is_rejected ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s","wrong_session_or_unauthentic_frame_is_rejected":"Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\n\nrunning 1 test\ntest wrong_session_or_unauthentic_frame_is_rejected ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s","replay_guard_memory_is_bounded":"Running tests/auth_replay.rs (target/debug/deps/auth_replay-91b3c55d1f4644a5)\n\nrunning 1 test\ntest replay_guard_memory_is_bounded ... ok\n\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s"},"raw_summary":"35 lib unit tests + 5 tests/auth_replay.rs + 1 tests/demonstrates.rs + 10 tests/review.rs + 8 tests/threat_model.rs, all pass. fmt --check clean, clippy --all-targets -D warnings clean."},"fix_summary":{"the_auth_key_comes_from_public_defaults":"Before: the UDP loopback derived the frame authentication key from config.session_seed(session_id) XOR a constant, and session_secret defaulted to the published 0x1615_0117_5eed_0001 - a value in the clear in the repo. Anyone could recompute the key and sign a valid ACK, so a forged acknowledgement passed verify_return(). Fix: SessionSecurity::from_secret() fails closed on PUBLISHED_DEFAULT_SECRET (returns Err(InsecureSecret)); the loopback resolves its key via session_security(), which refuses the published default and draws a fresh, unpredictable per-run secret for both endpoints. A frame signed with the publicly-derivable default key no longer verifies. Proof: tests/auth_replay.rs::forged_ack_signed_with_public_default_key_is_rejected and ::published_default_secret_is_refused.","packet_number_is_never_checked":"Before: the receiver accepted every validly-signed frame and only recorded the sequence number, so a captured packet replayed on the path was indistinguishable from a genuine retransmission - the packet_number field (a fresh, strictly increasing number per transmission) was never consulted. Fix: a bounded ReplayGuard records accepted packet numbers and rejects an exact repeat, and the receiver's accept_data() decision requires (data frame) AND (expected session) AND (valid forward tag) AND (new packet_number). A replay of a captured packet number is rejected even though its sequence, payload and tag are unchanged. Proof: tests/auth_replay.rs::replayed_packet_number_is_rejected and ::replay_guard_memory_is_bounded."},"tests":{"file":"Poc/22-asymmetric-transport/tests/auth_replay.rs","separate_from":"tests/review.rs","covers":["published_default_secret_is_refused","forged_ack_signed_with_public_default_key_is_rejected","replayed_packet_number_is_rejected","wrong_session_or_unauthentic_frame_is_rejected","replay_guard_memory_is_bounded"]},"docs_updated":"src/session.rs and src/lib.rs module/field docs now state the key is bound to a per-session secret and that the published default is refused, so the doc no longer claims replay protection the code did not provide."},"attachments":[],"redactions":0,"note":"Shown as stored."}