pub enum SecurityWarningCode {
Show 14 variants
SenderAddressMismatch,
TransportIdentityMismatch,
ControlSignatureInvalid,
UnsignedControlRejected,
MediaSenderGroupMismatch,
PlaintextSend,
PlaintextReceiveRejected,
SessionSenderGroupMismatch,
SessionRekeyTriggered,
NostrKeyPackageSlotExhausted,
PushKeyPackagePoolExhausted,
RelayAddressBindingMismatch,
RelayAddressDeclarationRefused,
GroupLeafIdentityUnproven,
}Expand description
Machine-readable taxonomy for Event::SecurityWarning, so consumers can
branch on a stable code instead of matching the human-readable reason
string (which is for logs/UI and may change between versions).
Variants§
SenderAddressMismatch
A control message’s signature verified, but the key that signed it does not derive to the address the frame claims to come from.
There is no benign reading. An address is the hash of its identity key, so a legitimate peer’s frames always re-derive to their own address: a rotated key is a different address, i.e. a different peer, not the same peer with new bytes. This is either an impersonation attempt or a peer running a build with a broken derivation.
Replaces TOFU_KEY_MISMATCH, whose remedy (resetTofuForPeer) no longer
exists because there is no pin to reset.
TransportIdentityMismatch
A control message’s signed sender did not match its transport peer identity.
ControlSignatureInvalid
A control message’s signature failed verification (invalid signature or malformed metadata).
UnsignedControlRejected
An unsigned control message was rejected.
All security-gated control traffic must be signed by the key its sender
address derives from. This is unconditional: it does not depend on
require_transport_identity, nor on whether the sender was seen before.
A peer that will not sign is making a claim that cannot be checked.
MediaSenderGroupMismatch
An encrypted media chunk’s MLS group did not match the 1:1 session group of the claimed wire sender — a valid ciphertext (from some session) delivered under a different sender’s name is a media forgery/misattribution attempt.
PlaintextSend
An outbound message left this node as plaintext because encryption is
disabled or MLS is uninitialized while require_encryption is false
(an explicit opt-out — the default fails closed). Emitted at most once
per peer per protocol instance.
PlaintextReceiveRejected
Inbound plaintext content (text or legacy media) was rejected by the
encryption policy: either require_encryption is enabled, or a
confirmed MLS session exists with the claimed sender and plaintext
from that peer is a downgrade/forgery attempt (plaintext carries no
sender authentication). Emitted at most once per peer; the per-peer
tracking set is bounded, so after a flood of forged sender ids a
peer may warn again.
SessionSenderGroupMismatch
An encrypted 1:1 envelope named an MLS group that is not the session
slot shared with the claimed wire sender. The text-path counterpart to
Self::MediaSenderGroupMismatch: it catches the same misattribution
on paths where MLS cannot, because the envelope failed before it
authenticated anything.
SessionRekeyTriggered
A 1:1 MLS session was torn down and re-advertised after an epoch
desync. Expected occasionally (a genuine fork heals this way), but the
triggering frame is not authenticated — see schedule_session_rekey —
so a sustained rate of these for one peer indicates injected frames
rather than a real fork.
NostrKeyPackageSlotExhausted
A Nostr key-package publication slot could not be refilled, so cold first contact over Nostr is degraded or unavailable until it succeeds.
Slots are consumed locally — an init key leaves provider storage only when this node processes a Welcome built against it — so consumption itself is normal traffic, and a stranger can drive it only by actually establishing sessions with us. What this warns about is the refill failing (MLS or storage error), which is the failure the publication design must never absorb silently: a stale published record makes every stranger who fetches it build a Welcome that can never be processed.
PushKeyPackagePoolExhausted
The per-peer key-package pool hit its ceiling, so a peer was advertised an init key another peer already holds.
The push path normally gives each peer its own single-use init key. Past the ceiling it reuses one instead of growing the pool without bound — weakening forward secrecy at session establishment (one compromised init key opens every Welcome built against it) and re-introducing the race where the second peer to use a package finds it already consumed. Both clear as packages are consumed or expire.
Reaching it means a device has accumulated unconsumed advertisements: many peers met that never established a session. Sustained emission is the signal that the ceiling, not peer churn, is now the binding constraint.
RelayAddressBindingMismatch
The relay acknowledged an address declaration naming an address that is not this node’s.
There is no benign reading. The bridge declares local_address() and
signs a proof over a per-connection challenge; the relay verifies that
the declared address derives from the key that signed it, and echoes
back what it bound. So the echo can only differ if the relay bound
something other than what it verified — a broken or hostile relay, or a
frame injected into the socket.
Nothing is torn down: a relay that controls the socket already controls everything a local refusal could protect, so the mitigation is that the signal is loud rather than that the connection dies. What it means in practice is that this connection’s frames are attributed to an identity this node cannot prove, so receivers strict-matching the sender against the transport identity will reject its security-gated control traffic.
RelayAddressDeclarationRefused
The relay refused this connection’s address declaration, so it stays attributed by account name.
Operational degradation, not an attack signal: the refusal path is
deliberately non-fatal on both sides, and the connection keeps working
exactly as it did before addresses existed. What is lost is new MLS
session establishment over the relay — __MLS_KEY_PKG__ and
__MLS_WELCOME__ are security-gated, so a receiver that strict-matches
the account name against the off1… sender rejects them, while already
established sessions keep working because __MLS_ENC__ is data-plane.
The relay’s own text is not carried on the event. It is
remote-chosen, and this event’s reason is shipped verbatim by the
telemetry scrubber, so the wording stays bounded in the device log
while the code above is the classification. The causes it distinguishes
range from unusable key material to this socket having been displaced
by a newer login; none of them changes what an app should do. No retry
is attempted — the next reconnect declares from scratch.
GroupLeafIdentityUnproven
A group leaf carries a credential its own signature key does not derive to — an identity claimed inside a group without the key to prove it.
There is no benign reading, and unlike most codes here it accuses someone the user is already in a room with. An address is the hash of its identity key, so every honest leaf reproduces its own credential; producing one that does not means deliberately building a leaf around someone else’s name.
peer_id is who the finding concerns, not always who to blame. On
the three refusal sites it is the peer that delivered the forgery — the
Welcome’s inviter, or the sender of the frame carrying the commit — and
never the address the forged leaf claimed. That attribution is worth
something because it is proved independently: __GRP_MLS_WELCOME__,
__GRP_MLS_COMMIT__ and __MLS_WELCOME__ are all security-gated, so
the sender signed with the key its own address derives from. On the
fourth site there is no delivering peer at all and peer_id is this
device’s own id.
reason is diagnostic text, must not be parsed, and deliberately
carries no identifier — the impersonated address appears only in
this device’s logs. Telemetry scrubbing hashes peer_id and passes
reason through verbatim, and the identity at stake belongs to a third
party who is not even part of the exchange.
Emitted from four sites. The first three refuse a claim arriving, so nothing is installed and the frame is dropped:
- joining a group Welcome whose ratchet tree contains such a leaf (the invite is declined outright);
- processing a membership commit that would install one (the commit is not merged, and is never buffered for retry);
- joining a 1:1 session Welcome whose ratchet tree contains one —
including when we already hold a session with that peer, where the
refusal is non-destructive and the existing session stays live (so a
secure_session_failedalongside this does not mean the working session ended).
The fourth is different in kind and needs a different response:
- a roster read skipping a leaf already seated in local group state. No wire gate can have admitted it, so it arrived by a direct write to this device’s secure store or via a group joined by a build predating those gates. Such a leaf is kept out of every roster and cannot speak — but it holds live group secrets and reads everything sent to the group, which no later refusal undoes. The remedy is to abandon the group, not to evict a member of it.
Apps should treat a group that produces this as untrusted for attribution — a message shown as coming from a member is exactly what the forged leaf was for.
Implementations§
Trait Implementations§
Source§impl Clone for SecurityWarningCode
impl Clone for SecurityWarningCode
Source§fn clone(&self) -> SecurityWarningCode
fn clone(&self) -> SecurityWarningCode
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for SecurityWarningCode
Source§impl Debug for SecurityWarningCode
impl Debug for SecurityWarningCode
Source§impl<'de> Deserialize<'de> for SecurityWarningCode
impl<'de> Deserialize<'de> for SecurityWarningCode
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for SecurityWarningCode
Source§impl PartialEq for SecurityWarningCode
impl PartialEq for SecurityWarningCode
Source§impl Serialize for SecurityWarningCode
impl Serialize for SecurityWarningCode
impl StructuralPartialEq for SecurityWarningCode
Auto Trait Implementations§
impl Freeze for SecurityWarningCode
impl RefUnwindSafe for SecurityWarningCode
impl Send for SecurityWarningCode
impl Sync for SecurityWarningCode
impl Unpin for SecurityWarningCode
impl UnsafeUnpin for SecurityWarningCode
impl UnwindSafe for SecurityWarningCode
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Declassify for T
impl<T> Declassify for T
type Declassified = T
fn declassify(self) -> T
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more