fix(peers): reject self-add in add_peer()

Observed on .228: /var/lib/archipelago/peers.json contained an entry
matching the node's own node_key.pub pubkey. It had been added
2026-03-02 and stuck around forever since add_peer() only dedupes by
pubkey — nothing stops a pubkey that happens to be ours.

How it probably got there: somewhere in the auto-add paths
(node-message receive, mesh federation bridge, invite back-and-forth)
a message we'd sent was fed back and the receiver-side add used the
echoed from_pubkey without realising it was us. Doesn't matter which
path — the guard belongs in storage.

add_peer now short-circuits when the candidate pubkey matches
data_dir/identity/node_key.pub. Helper is_own_pubkey best-effort:
unreadable identity → returns false so normal peers aren't blocked.

Also manually purged the one stray entry on .228 (1 removed, 2 real
peers remain). Future deploys include this guard so the phantom can't
come back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-19 04:02:15 -04:00
parent 616f62ce4f
commit ab85c2e361

View File

@ -50,6 +50,13 @@ pub async fn save_peers(data_dir: &Path, peers: &[KnownPeer]) -> Result<()> {
}
pub async fn add_peer(data_dir: &Path, peer: KnownPeer) -> Result<Vec<KnownPeer>> {
// Self-add guard: skip if the candidate pubkey matches our own identity.
// Auto-peering paths (e.g., node-message receive handler) can otherwise
// echo-back our own pubkey when messages bounce through federation, which
// ended up in users' peer lists as a phantom "self-peer" entry.
if is_own_pubkey(data_dir, &peer.pubkey).await {
return load_peers(data_dir).await;
}
let mut peers = load_peers(data_dir).await?;
let exists = peers.iter().any(|p| p.pubkey == peer.pubkey);
if !exists {
@ -59,6 +66,20 @@ pub async fn add_peer(data_dir: &Path, peer: KnownPeer) -> Result<Vec<KnownPeer>
Ok(peers)
}
/// Reads `data_dir/identity/node_key.pub` and compares to `candidate` (hex).
/// Returns false on any I/O or format issue — guard is best-effort; a real
/// peer with a colliding pubkey is impossible, so a false negative just
/// means the entry is added normally.
async fn is_own_pubkey(data_dir: &Path, candidate: &str) -> bool {
let path = data_dir.join("identity/node_key.pub");
let bytes = match tokio::fs::read(&path).await {
Ok(b) => b,
Err(_) => return false,
};
let own_hex = hex::encode(&bytes);
own_hex.eq_ignore_ascii_case(candidate.trim())
}
pub async fn remove_peer(data_dir: &Path, pubkey: &str) -> Result<Vec<KnownPeer>> {
let mut peers = load_peers(data_dir).await?;
peers.retain(|p| p.pubkey != pubkey);