Skip to content

v0.5.14: bilateral-required `wire add` (security)

Choose a tag to compare

@laulpogan laulpogan released this 18 May 00:27

Security release — closes phonebook-scrape pairing vulnerability

Affected versions: v0.5.9 through v0.5.13 (all v0.5.x releases that shipped the zero-paste wire add handle flow).

Severity: Medium. Unauthenticated remote write capability to any wire user's slot via public phonebook enumeration. No remote code execution; no impersonation (Ed25519 sigs still verify); no inbox read access.

The bug

v0.5.13's receiver-side maybe_consume_pair_drop (src/pair_invite.rs:489-548) auto-pinned a stranger's signed pair_drop event with VERIFIED tier and shipped back pair_drop_ack carrying the receiver's slot_token — the bearer credential for authenticated writes to that slot. A malicious actor could:

  1. Scrape GET /v1/handles and /phonebook (public by design — Mastodon-style discovery).
  2. For each handle: POST a signed pair_drop to the auth-free /v1/handle/intro/<nick> endpoint.
  3. Receive the victim's slot_token via pair_drop_ack.
  4. Send arbitrary signed events (any kind, up to 256 KiB per event, 64 MB slot quota) to the victim's inbox.

Cost: 1 keypair, ~1 hour of scripting. No defense beyond the relay's global rate limiter and slot quota.

The fix — bilateral-required wire add

wire add must now fire on both sides before any capability flows. The design intent — bilateral consent — was never quite implemented for the zero-paste handle flow; this release restores it.

Receiver-side gate. maybe_consume_pair_drop is bifurcated on nonce presence:

  • SPAKE2 invite-URL path (pair_drop carries pre-shared pair_nonce): unchanged. The invite-URL nonce IS the consent gesture.
  • Handle path (no nonce, zero-paste wire add): write to state/wire/pending-inbound-pairs/<handle>.json. No trust pin. No ack. No slot_token leak. OS toast prompts operator to run wire add <peer>@<their-relay> to accept or wire pair-reject <peer> to refuse.

Operator-side completion. cmd_add checks pending-inbound first. If present → bilateral completion: pin VERIFIED, ship slot_token via pair_drop_ack, delete pending record. Otherwise → outbound (unchanged) — emit signed pair_drop, await reciprocal wire add from peer.

Attack surface after v0.5.14

A phonebook-scrape + spray produces N records in N victims' pending-inbound queues. 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.

Zero-paste UX preserved: two commands (wire add from each side).

New CLI surface

wire pair-list-inbound [--json]     # enumerate pending-inbound (flat array)
wire pair-reject <peer>             # drop a pending record without pairing
wire pair-list                      # human output: PENDING INBOUND section + SPAKE2
wire status                         # JSON: pending_pairs.inbound_count + inbound_handles

wire pair-list --json shape unchanged for back-compat with existing scripts.

Tests

145 lib + 28 CLI tests passing. Four new pending-inbound integration tests in tests/cli.rs.

Upgrade

cargo install slancha-wire
wire upgrade    # respawns daemon at v0.5.14

No protocol or schema changes. Existing verified peers remain paired.

Credit + disclosure

Vulnerability identified during pre-release security review on 2026-05-17. No known exploitation in the wild. Patch released same day. Full code-path analysis + four-persona threat-model review preceded the fix.

Full Changelog: v0.5.13...v0.5.14

Full Changelog: v0.5.13...v0.5.14