pub enum SecurityWarningCode {
Show 18 variants
SenderAddressMismatch,
TransportIdentityMismatch,
ControlSignatureInvalid,
UnsignedControlRejected,
MediaSenderGroupMismatch,
PlaintextSend,
PlaintextReceiveRejected,
SessionSenderGroupMismatch,
SessionRekeyTriggered,
NostrKeyPackageSlotExhausted,
PushKeyPackagePoolExhausted,
RelayAddressBindingMismatch,
RelayAddressDeclarationRefused,
GroupLeafIdentityUnproven,
StaleControlFrame,
GatewayAddressBindingMismatch,
GatewayAddressDeclarationRefused,
KeyPackageOutsideValidityWindow,
}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.
StaleControlFrame
A control frame was refused for what its signature says about when: stamped further in the past than this node accepts, further ahead of this node’s clock than it accepts, or carrying the older signing payload from a peer that has proved it can produce the newer one.
The three share a code because they share a remedy, and the remedy is the first thing to check: this device’s own clock. The signed timestamp is compared against it, so a device whose clock is wrong refuses honest peers in bulk, and the event stream then looks like a coordinated attack rather than a local fault. A run of these naming many different peers is a clock; a run naming one peer while others are fine is that peer, either replaying captures or running a broken build.
It is the fresh-frame counterpart to
Self::UnsignedControlRejected: both refuse a frame whose signature
proves less than this node requires, one for what it omits and one for
what it fails to state. Recovery of last resort is
security.control_freshness_enforced = false, which returns this node
to accepting frames of any age (see issue 403 for what that gives back
to an attacker).
GatewayAddressBindingMismatch
A gateway echoed an AddressDeclared naming an address that is not
this device’s.
The gateway twin of Self::RelayAddressBindingMismatch, and it has
the same absence of a benign reading: the gateway verifies that the
declared address derives from the key that signed the proof, so an
echo naming anything else means it bound what it did not verify. A
session attached under an address this device does not control is how
another device’s inbound traffic gets drawn here, and how the presence
answers given about that address are poisoned.
Reported and not acted on, for the reason the relay case gives: a gateway that owns the socket already owns everything a local teardown would protect. What the bridges do act on is narrower and is not this signal: they refuse to offer a carrier whose session was never bound.
GatewayAddressDeclarationRefused
A gateway refused this connection’s address declaration.
Unlike the relay case (Self::RelayAddressDeclarationRefused), this
is not a degraded-but-working connection. The relay keeps delivering
on established sessions in account-name space; a gateway has no such
space, so an unproved session can only be told verdicts and is never
registered as a recipient. The bridge therefore never reports the
carrier available, and this warning explains a transport that stays
down while its socket connects fine.
KeyPackageOutsideValidityWindow
A peer’s key package was refused because this device’s clock is outside its validity window: the window has not started yet, or has already closed.
Like Self::StaleControlFrame, the first thing to check is a clock,
and it is usually this device’s. A package is valid from an hour before
it was minted until 30 days after, by the receiver’s clock. So a device
more than an hour behind a peer cannot start a session with it, though
the peer, whose clock accepts this device’s package, still can. Once
the gap passes 30 days each side refuses the other’s package and no
session forms at all: messages and connection requests wait for as long
as the clocks disagree, and both sides report this. Phones that have not
been online are where this happens. Many different peers is this
device’s clock; one peer while others are fine is that peer’s.
A window that has already closed is not always a clock: a relay can
hold a package for days, and the receiver anchors its cached expiry to
when the frame arrived. That package is discarded, as an expired one
is, and the reason says either cause; the next package the peer sends
replaces it. A window that has not started is kept, since it becomes
valid once the clocks agree.
Raised on every route that admits a pending package: when it arrives, and on the first send after a restart. The package has not proved its sender when the window is checked, so the peer named is a claim, and the event is reported at most once per peer.
Implementations§
Trait Implementations§
Source§impl Clone for SecurityWarningCode
impl Clone for SecurityWarningCode
impl 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<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.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 moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);