Security audit findings NET-01 (partial) and Codex #9 ("unauthenticated
hints can make the victim pull arbitrary known peers").
Inbound QUIC connections are intentionally anonymous: only the responder
is authenticated, so any LAN host can open a stream and send
`LibraryChanged`/`CallToPlayChanged` hints naming any `claimed_peer_id`.
If the claimed peer was known and the forged session or revision did
not match the cached snapshot, state sync scheduled a full pinned Hello
pull to that peer. Sending one small forged hint to every node on the
LAN therefore made all of them pull a victim's complete snapshot at
once (reflected amplification), bounded only by the 5-second per-peer
coalesce window.
Full mutual TLS would bind hints to a verified identity but is a larger
protocol change than this application warrants. Instead the hint now
carries the source IP of the anonymous connection, and
`hint_requires_pull_from_snapshot` discards any hint whose source IP
differs from the address at which the claimed peer was last
authenticated. On a LAN a QUIC connection cannot be established from a
spoofed IP, so a third host can no longer select which peer this node
pulls. A genuine peer whose address changed loses only the hint fast
path; mDNS rediscovery and pinned liveness reconciliation still pick it
up.
`PeerEndpointGeneration::for_tests` is added under cfg(test) so unit
tests can build a `PeerRevisionSnapshot`.
Test plan: `just test`. The new test accepts a hint from the peer's
address, rejects the same hint from another IP or with no address, and
keeps the revision comparison for matching sources. Manual: with two
peer-cli containers, adding a game on one still triggers the other to
refresh its library promptly.
Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg