session_obf_key is derived once at cold start and deliberately not rotated across rekeys (documented in handle_rekey_resp). The obf envelope is SipHash-CTR XOR keyed by (session_obf_key, 8-byte random nonce). With a fixed key for the connection's whole life, nonce collisions arrive at the ~2^32 birthday bound (~1h at 1e6 pps); a collision XORs two masked regions, leaking the XOR of their type||len headers and inner-AEAD ciphertexts (inner AEAD keeps content secret — statistical regularity, not a confidentiality break, but exactly what the anti-DPI layer exists to avoid). Fix: rotate the obf key per epoch (resync via epoch material) or widen the nonce / make it random||counter.
See docs/2026-07-25-project-review-and-roadmap.md for the full review synthesis.
session_obf_key is derived once at cold start and deliberately not rotated across rekeys (documented in handle_rekey_resp). The obf envelope is SipHash-CTR XOR keyed by (session_obf_key, 8-byte random nonce). With a fixed key for the connection's whole life, nonce collisions arrive at the ~2^32 birthday bound (~1h at 1e6 pps); a collision XORs two masked regions, leaking the XOR of their type||len headers and inner-AEAD ciphertexts (inner AEAD keeps content secret — statistical regularity, not a confidentiality break, but exactly what the anti-DPI layer exists to avoid). Fix: rotate the obf key per epoch (resync via epoch material) or widen the nonce / make it random||counter.
See docs/2026-07-25-project-review-and-roadmap.md for the full review synthesis.