fix(mesh): honour explicit !ai allowlist for unauthenticated stock clients

A stock meshcore client (e.g. a phone) can't sign our typed envelopes, so it is
never 'authenticated' — which meant ticking it as an allowed assistant contact
had no effect and !ai stayed denied. The explicit per-contact allowlist is a
deliberate operator opt-in for a specific key, so match it regardless of
authentication, keyed on the asker's resolved identity (bound archipelago key,
else firmware routing key — how meshcore addresses the contact). The spoofable
federation-trust-list match still requires authentication.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-06-19 16:42:06 -04:00
parent 7831e68d13
commit 63611a4453

View File

@ -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;
}
}