Commit Graph
8 Commits
Author SHA1 Message Date
b7c6f42b3a Close forged-directness link-rebind DoS on DM routing (#1421)
* Gate connected-link trust on a secure session; deny forged direct announces the connected shortcut

Residual gap after the rotation-heal containment (#1401): "verified
direct" announces prove the signature but not directness — TTL is
unsigned — so a malicious connected peer can replay a victim's fresh
announce with its TTL restored. When the victim has no live link, the
replayer's link rebinds to the victim's ID and reads as "connected",
and MessageRouter's connected fast-path then trusts it outright: every
DM stalls on a Noise handshake the replayer can never complete and is
silently lost while showing "sent".

Router-level trust gate (no wire change):
- Transport gains canDeliverSecurely(to:) — BLE answers with an
  established Noise session; Nostr keeps its prompt-delivery predicate;
  the protocol default forwards to canDeliverPromptly for transports
  without a forgeable link layer.
- MessageRouter.sendPrivate only trusts a connected link outright when
  it can deliver securely; otherwise it still sends (kicking the
  handshake on a genuine link) but retains a copy and hands a sealed
  copy to couriers, like the reachable path. flushOutbox gets the same
  gate so a flush over an insecure link resends instead of dropping the
  retained copy.

Presence hardening (defense in depth): a "direct" announce arriving on
a link already bound to a different peer no longer shortcuts the
claimed peer into "connected" — only real link state does. Genuine
first-contact and direct announces are unaffected; only the ambiguous
heal path loses the forgeable shortcut.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Harden the insecure-link outbox bound; document the second-replay presence gap and the courier metadata tradeoff

Review follow-ups on the canDeliverSecurely gate:

- flushOutbox no longer counts connected-but-insecure flushes toward the
  maxSendAttempts drop: the message was actually transmitted over a live
  link, so a peer whose Noise handshake stalls across reconnect flapping
  must not burn through the cap and lose the store-and-forward copy the
  gate exists to preserve. Retention stays bounded by the 24h outbox TTL
  and the per-peer FIFO cap; acks still clear it. Attempt-counting stays
  for reachable-only (heuristic) sends. Regression test: >8 connected-
  insecure flushes keep the retained copy, drop callback never fires.

- Document the known second-replay presence gap: linkBoundToOtherPeer
  reads the binding before rebindLinkAfterVerifiedDirectAnnounce steals
  the link, so once a first replay has rebound a link to an absent
  victim's ID, a second replay marks the victim connected. Presence
  display only — DMs stay on the retain+courier path via the router
  gate. Not closed at the announce layer because the post-rebind state
  is indistinguishable from a legitimate rotation/reconnect heal (a
  supported, field-verified flow). Covered by a two-announce test.

- Note the accepted courier-spray metadata tradeoff at the connected-
  insecure send: nearby verified peers receive a sealed copy (they learn
  a DM exists, never its content), cleared on ack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Promote a healed rotation to connected; normalize the secure-delivery probe; retain Codable properties in Periphery

Codex review follow-ups on 5017c232:

- P1: a legitimate rotation announce necessarily arrives on a link still
  bound to the OLD peer ID, so the linkBoundToOtherPeer denial stored the
  new identity as disconnected — and the rebind only fixed link state,
  never the registry. A healed rotation then read as disconnected until
  the peer happened to announce again. rebindLinkAfterVerifiedDirect-
  Announce now promotes the rebound identity to connected after the
  containment checks pass (registry markConnected + topology/snapshot/
  peer-list refresh; the .peerConnected UI event already fired from the
  announce path). This consciously moves the forged-presence residue
  from second-replay to first-successful-rebind — bounded by the rebind
  containment (never steals a live identity, one rebind per link per
  cooldown) and still display-only: DMs stay gated on canDeliverSecurely.
  Comments and the pinned tests updated; new regression test covers
  rotate-on-open-link -> rebind -> isPeerConnected(new) == true.

- P2: BLEService.canDeliverSecurely probed the Noise session with the
  peer ID as given, but sessions are keyed by the short wire ID — a send
  keyed by the full 64-hex Noise key (favorites resolution) misread an
  established session as insecure and needlessly retained + couriered
  every DM until ack. Normalize with toShort() like isPeerConnected.
  Test drives a real XX handshake and asserts both ID forms pass.

- Periphery: the PrekeyBundleStore.StoredBundle.noiseKey "assign-only"
  flake fired again and slipped past its baselined USR (read exists in
  loadFromDisk; the indexer intermittently misses it). Replace the
  baseline entry with retain_codable_properties: true — deterministic,
  and a truly-dead Codable field is a persisted-format change anyway.
  Local periphery scan --strict: no unused code detected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:46:33 +02:00
ef848857b7 Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so
platform-specific code is never touched), with test targets indexed and
the share extension built. 277 dead declarations removed or demoted:
dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed-
feature remnants (autocomplete command suggestions, back-swipe tuning,
MediaSendError, GeohashParticipantTracker), unused Tor dormancy
bindings, assign-only properties, unused parameters (renamed to _), and
redundant public accessibility. 13 orphaned localization keys deleted
across all 29 locales (old pre-#1392 location-notes UI, app_info
warnings).

Two real tests were flagged as unused because they never ran: Swift
Testing methods missing @Test (NostrProtocolTests.
testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests.
testAssemblesCompressedLargeFrame). Re-armed both; they pass.

Deliberately kept, now recorded in .periphery.baseline.json: iOS-only
code invisible to the CI macOS scan, C FFI signatures, keep-alive
NWPathMonitor reference, InboundEventKey.eventID (dedup semantics),
wifiBulk capability bit (reserved for Wi-Fi bulk work, used by
BitFoundation package tests), and the String secureClear cluster
(exercised by package tests).

New: .periphery.yml config and an advisory Dead Code CI job (mirrors
the SwiftLint precedent from #1361) that fails on findings not in the
committed baseline.

Verified: full macOS app suite, BitFoundation (119) and BitLogger (13)
package tests green; periphery scan --strict exits clean.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:24:19 +02:00
Hot Pixel GroupandGitHub e191e9c6f2 Fix slash-command suggestions that insert commands the processor rejects (#1354)
The autocomplete panel is the only in-app surface for discovering slash
commands, but several suggestions do not match what CommandProcessor
accepts, so tapping them inserts a command that returns "unknown
command":

- CommandInfo suggests /dm, /favorite, /unfavorite, but the processor
  only handles /m, /msg, /fav, /unfav. Aliases are aligned to the
  accepted spellings (msg, fav, unfav).
- Favorites are suggested only in geohash contexts (isGeoPublic ||
  isGeoDM) — exactly where the processor rejects them ("favorites are
  only for mesh peers"). The gating is inverted so they appear in mesh,
  where they work.

Also, small related fixes to the discovery surface:
- /help is now handled (the ChatViewModel command docstring already
  claimed it existed); it prints a local system line listing the valid
  commands, and the unknown-command error points at it.
- The suggestion panel keeps the matched command's usage row (e.g.
  "/msg <nickname>") visible while arguments are typed, instead of
  vanishing at the first space; in that mode the row is informational
  and no longer overwrites the draft on tap.

New string is added source-language (en) only. The CommandInfo contract
test is updated to the corrected metadata.
2026-07-05 14:15:57 +02:00
jackandClaude Fable 5 8a867a17a1 Remove noise service exposure; single-owner selection state
Transport callers no longer reach the raw NoiseEncryptionService:
getNoiseService() is deleted in favor of narrow purpose-named Transport
methods (session public key, identity fingerprint, static/signing keys,
sign/verify, callback installation). VerificationService now reaches
crypto through the transport, so it can no longer pin a stale service
across a panic reset. myPeerID/myNickname become private(set); the
existing setNickname mutator is the sole nickname path.

ConversationStore is now the sole owner of private-chat selection:
PrivateChatManager.selectedPeer is a published read-only mirror, and
startChat/endChat mutate through the store intent. The bridge method
and its five call sites are deleted, removing a latent bug where a
stale manager selection pushed back into the store could resurrect a
just-removed conversation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:29:07 +02:00
ab0da61533 [codex] Refactor BLE transport event handling (#1266)
* Refactor BLE transport event handling

* Make image output paths unique

* Keep queued Nostr read receipts alive

* Allow self-authored RSR ingress replies

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2026-05-31 13:58:27 +02:00
IslamandGitHub 4cfcefcda6 BitFoundation module to centralize shared components (#1089)
* Run local packages’ tests as well on CI

* BitFoundation module to centralize shared components
2026-04-14 14:10:03 -05:00
264a95b61a Raise protocol coverage to 99 percent (#1058)
* Expand protocol coverage with edge-case tests

* Stabilize read receipt transport test

* Stabilize BLE duplicate packet test

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2026-03-12 17:55:41 -10:00
7e86d2061f Expand coverage for transport, chat, and media flows (#1056)
* Expand coverage for transport, chat, and media flows

* Stabilize transport and media coverage tests

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2026-03-12 16:20:19 -10:00