From ab85c2e36197d2e3c2c79591a536544b9e79e280 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 19 Apr 2026 04:02:15 -0400 Subject: [PATCH] fix(peers): reject self-add in add_peer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- core/archipelago/src/peers.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/archipelago/src/peers.rs b/core/archipelago/src/peers.rs index 8d2f3a44..80edfa80 100644 --- a/core/archipelago/src/peers.rs +++ b/core/archipelago/src/peers.rs @@ -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> { + // 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 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> { let mut peers = load_peers(data_dir).await?; peers.retain(|p| p.pubkey != pubkey);