diff --git a/core/archipelago/src/mesh/listener/assist.rs b/core/archipelago/src/mesh/listener/assist.rs index cf0e07ab..c22efc75 100644 --- a/core/archipelago/src/mesh/listener/assist.rs +++ b/core/archipelago/src/mesh/listener/assist.rs @@ -200,15 +200,20 @@ async fn is_sender_allowed( } } - // Explicit per-contact allowlist: a listed pubkey may ask regardless of the - // trusted_only policy — but only when the message is authenticated, so a - // spoofed packet claiming an allowlisted key can't slip through. - if authenticated { - if let Some(ref pk) = pubkey_hex { - let allowed = state.assistant.read().await.allowed_contacts.clone(); - if allowed.iter().any(|a| a.eq_ignore_ascii_case(pk)) { - return true; - } + // Explicit per-contact allowlist: the operator deliberately ticked THIS + // contact, so honour it even for an unauthenticated radio asker. A stock + // meshcore client (e.g. a phone) can't sign our typed envelopes, so it can + // never be `authenticated` — gating the allowlist on authentication made + // ticking such a contact have no effect. We match the asker's resolved + // identity key: the bound archipelago key if we know it, else the firmware + // routing key (`pubkey_hex`), which is how meshcore addresses the contact + // and what the UI adds to the allowlist for a keyless radio peer. This is a + // narrow, explicit opt-in for a specific key — the spoofable federation- + // trust-list match below still requires authentication. + if let Some(ref pk) = pubkey_hex { + let allowed = state.assistant.read().await.allowed_contacts.clone(); + if allowed.iter().any(|a| a.eq_ignore_ascii_case(pk)) { + return true; } }