You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A vulnerability in the zero-paste wire add handle flow allowed a malicious actor with access to a public phonebook to deposit authenticated write capability against every wire user listed there. Fixed in v0.5.14 by restoring bilateral-required wire add semantics.
Affected versions
v0.5.9 through v0.5.13 (all v0.5.x releases that shipped the zero-paste handle flow). v0.4.x and earlier (SPAKE2-only pairing) are unaffected.
Severity: Medium
Unauthenticated remote write to any phonebook-listed user's slot, up to slot quota (64 MB total, 256 KiB per event).
Not remote code execution.
Not impersonation (Ed25519 sigs over canonical JSON still verify — the attacker is a known sender, not a forger).
Not inbox read access (slot read requires the slot_token, which is never leaked by the relay; the vuln leaks it via the daemon's ack).
Technical detail
Attack chain on v0.5.13
GET https://wireup.net/v1/handles returns every claimed handle + their slot_id (public, by design — Mastodon-style discovery).
For each handle, attacker POSTs a self-signed pair_drop (kind=1100) to the auth-free /v1/handle/intro/<nick> endpoint (src/relay_server.rs:1569). Relay validates: kind, embedded card signature, size cap. No bearer auth required by design.
Relay appends the event to the target's slot. Target's daemon pulls.
maybe_consume_pair_drop (src/pair_invite.rs:489) auto-pinned the stranger's card with tier VERIFIED in trust.json, saved their slot_token in relay_state.json, then POSTed pair_drop_ack carrying the receiver's slot_token back to the attacker.
Attacker now has bearer credential for /v1/events/<receiver-slot>. They can POST arbitrary signed events (any allowed kind, up to slot quota) to the receiver's inbox.
Root cause
The original v0.5 design intent was bilateral: wire add fires on both sides, capability flows after both sides gesture consent. The implementation collapsed the receiver-side gesture into automatic acceptance, making wire add a unilateral grant of write access.
Fix in v0.5.14
maybe_consume_pair_drop now bifurcates on nonce presence:
SPAKE2 invite-URL path (pair_drop carries pre-shared pair_nonce): unchanged. The nonce IS the consent gesture — the operator gave it to the sender out-of-band.
Handle path (no nonce, zero-paste wire add): write to new state/wire/pending-inbound-pairs/<handle>.json store. No trust pin. No ack. No slot_token leak. OS toast prompts the operator to run wire add <peer>@<their-relay> to accept or wire pair-reject <peer> to refuse.
cmd_add now checks pending-inbound first; if present, runs cmd_add_accept_pending which pins peer VERIFIED with stored coords, sends pair_drop_ack carrying our slot_token, deletes the pending record. Otherwise → outbound path (unchanged).
After v0.5.14, scraping the phonebook + spraying produces N entries in N victims' pending-inbound queues with zero VERIFIED pins, zero slot_token leaks, zero write capability. Each victim sees one OS toast per attacker; victims who don't manually wire add back are fully protected.
Mitigations for users on v0.5.13 or earlier
Upgrade:
cargo install slancha-wire
wire upgrade # respawns daemon at v0.5.14
If you cannot upgrade immediately: set accept_unknown_pair_drops: false in \$WIRE_HOME/config/wire/policy.json to disable open-mode discovery. This loses the zero-paste UX (only SPAKE2 invite-URL pairing will work) but closes the auto-pin vector.
To audit existing trust state:wire peers --json lists every VERIFIED peer. Compare against your expected pair partners. Any peer you don't recognize is a candidate for revocation (manual edit of \$WIRE_HOME/config/wire/trust.json).
Threat-model addendum + remaining residual windows
docs/THREAT_MODEL.md updated with the bilateral-pair doctrine. Residual windows worth tracking:
Relay-side rate limit on /v1/handle/intro/<nick> is per-source via tower_governor but not per-recipient. A high-volume sprayer at peak times could still trigger many OS toasts before the global limit kicks in. Tracking for v0.5.15.
wire block <peer> primitive doesn't exist yet. Today, blocking a key means manual trust.json edit. Tracking for v0.5.15.
Credit
Vulnerability surfaced during pre-release security review on 2026-05-17 by the operator + a four-persona adversarial threat-model session (paranoid-adversary / principled-protocol-designer / pragmatic-operator-UX / spam-abuse-researcher). All four converged on the same root cause: capability emitted to an unauthenticated requester on first contact.
No known exploitation in the wild — wire is at <50 users, the phonebook fits on one screen, and abuse would have been visible in inbox traffic. The vuln existed in shipped code for 5 days (v0.5.9 onward); patch released same day as discovery.
Closing this issue when v0.5.14 has been validated on multiple operators' machines + adopted by majority of paired peers.
Summary
A vulnerability in the zero-paste
wire addhandle flow allowed a malicious actor with access to a public phonebook to deposit authenticated write capability against every wire user listed there. Fixed in v0.5.14 by restoring bilateral-requiredwire addsemantics.Affected versions
v0.5.9 through v0.5.13 (all v0.5.x releases that shipped the zero-paste handle flow). v0.4.x and earlier (SPAKE2-only pairing) are unaffected.
Severity: Medium
Technical detail
Attack chain on v0.5.13
GET https://wireup.net/v1/handlesreturns every claimed handle + theirslot_id(public, by design — Mastodon-style discovery).pair_drop(kind=1100) to the auth-free/v1/handle/intro/<nick>endpoint (src/relay_server.rs:1569). Relay validates: kind, embedded card signature, size cap. No bearer auth required by design.maybe_consume_pair_drop(src/pair_invite.rs:489) auto-pinned the stranger's card with tierVERIFIEDintrust.json, saved theirslot_tokeninrelay_state.json, then POSTedpair_drop_ackcarrying the receiver's slot_token back to the attacker./v1/events/<receiver-slot>. They can POST arbitrary signed events (any allowed kind, up to slot quota) to the receiver's inbox.Root cause
The original v0.5 design intent was bilateral:
wire addfires on both sides, capability flows after both sides gesture consent. The implementation collapsed the receiver-side gesture into automatic acceptance, makingwire adda unilateral grant of write access.Fix in v0.5.14
maybe_consume_pair_dropnow bifurcates on nonce presence:pair_nonce): unchanged. The nonce IS the consent gesture — the operator gave it to the sender out-of-band.wire add): write to newstate/wire/pending-inbound-pairs/<handle>.jsonstore. No trust pin. No ack. No slot_token leak. OS toast prompts the operator to runwire add <peer>@<their-relay>to accept orwire pair-reject <peer>to refuse.cmd_addnow checks pending-inbound first; if present, runscmd_add_accept_pendingwhich pins peer VERIFIED with stored coords, sendspair_drop_ackcarrying our slot_token, deletes the pending record. Otherwise → outbound path (unchanged).After v0.5.14, scraping the phonebook + spraying produces N entries in N victims' pending-inbound queues with zero VERIFIED pins, zero slot_token leaks, zero write capability. Each victim sees one OS toast per attacker; victims who don't manually
wire addback are fully protected.Mitigations for users on v0.5.13 or earlier
Upgrade:
cargo install slancha-wire wire upgrade # respawns daemon at v0.5.14If you cannot upgrade immediately: set
accept_unknown_pair_drops: falsein\$WIRE_HOME/config/wire/policy.jsonto disable open-mode discovery. This loses the zero-paste UX (only SPAKE2 invite-URL pairing will work) but closes the auto-pin vector.To audit existing trust state:
wire peers --jsonlists every VERIFIED peer. Compare against your expected pair partners. Any peer you don't recognize is a candidate for revocation (manual edit of\$WIRE_HOME/config/wire/trust.json).Threat-model addendum + remaining residual windows
docs/THREAT_MODEL.mdupdated with the bilateral-pair doctrine. Residual windows worth tracking:pair_getloop times out at `T+timeout-ε` but the relay already buffered joining's bootstrap, joining can still receive host's previously-buffered bootstrap and flip to paired. Small window, not closed by this release./v1/handle/intro/<nick>is per-source viatower_governorbut not per-recipient. A high-volume sprayer at peak times could still trigger many OS toasts before the global limit kicks in. Tracking for v0.5.15.wire block <peer>primitive doesn't exist yet. Today, blocking a key means manualtrust.jsonedit. Tracking for v0.5.15.Credit
Vulnerability surfaced during pre-release security review on 2026-05-17 by the operator + a four-persona adversarial threat-model session (paranoid-adversary / principled-protocol-designer / pragmatic-operator-UX / spam-abuse-researcher). All four converged on the same root cause: capability emitted to an unauthenticated requester on first contact.
No known exploitation in the wild — wire is at <50 users, the phonebook fits on one screen, and abuse would have been visible in inbox traffic. The vuln existed in shipped code for 5 days (v0.5.9 onward); patch released same day as discovery.
Closing this issue when v0.5.14 has been validated on multiple operators' machines + adopted by majority of paired peers.