Commit Graph
9 Commits
Author SHA1 Message Date
jackandClaude Opus 5 399cdf95ab Stop broadcasting the adjacency graph and the authorship marker
Two radio-layer metadata leaks that need no cross-platform agreement,
because both only change what this device chooses to emit.

**Announces no longer carry the neighbour list.** The TLV held up to ten
8-byte peer IDs, so a *single* passive receiver could reconstruct the local
adjacency graph — who is standing next to whom — with no need for several
receivers or RSSI trilateration. In a crowd that is the most sensitive
thing the radio layer gives away, and unlike the identity keys it is not
required for the protocol to work.

Backward compatible in both directions: an empty list omits the TLV
entirely rather than emitting a zero-length one, the decoder already treats
its absence as "no topology offered", and lists from other peers are still
parsed so a mixed network behaves sensibly.

The cost is source routing. MeshTopologyTracker builds its adjacency map
from these lists, so with everyone silent there are no routes to compute
and directed traffic floods instead — which is already the documented
fallback whenever a route fails. More airtime for directed sends in dense
meshes; no correctness change. Left as a TransportConfig constant rather
than a user setting because it is a protocol trade-off, not a preference,
and flipping it back is one line.

**Public broadcasts no longer always originate at the maximum TTL.**
`ttl == messageTTLDefault` was a reliable "this device wrote it" marker to
any direct listener, which discloses authorship rather than mere presence.
Origin TTL is now drawn from 5...7: in a dense graph relays already clamp
broadcasts to 5, so an origin emitting 5 is indistinguishable from relayed
traffic, and in a sparse chain a 6 could be an origin or one hop from a 7.

Signature-safe and needs no agreement: TTL is excluded from the signed
bytes (toBinaryDataForSigning zeroes it so relays can decrement), so a peer
on any version just sees a smaller starting TTL and relays it normally. The
floor is not below the dense-graph clamp, since lower would cost reach
without buying ambiguity that clamp does not already provide.

Announces deliberately keep the fixed TTL: three link-binding paths read a
maximum-TTL announce as "direct link", and an announce's sender ID already
identifies the device, so there is nothing to hide and something to break.

**Not done here: padding.** Extending padding beyond Noise frames, and
fixing the gap where a frame needing over 255 bytes of padding is emitted
unpadded, both looked unilateral but are not. `toBinaryDataForSigning`
encodes with padding enabled, so the padding bytes are inside the signed
material for every signed packet — changing the algorithm changes the
signed byte stream and breaks signature verification against any peer that
has not changed it identically. That makes it a coordinated wire change;
recorded in the privacy assessment and in #1487's open questions rather
than attempted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 21:03:35 +02:00
c1ce9029d8 Keep working when the network is hostile, and make the app verifiable (#1486)
Tier 3 of a protest-hardening review. The BLE mesh is already properly
fenced off from network reachability and needs nothing here; every gap is
on the internet side, or in how someone gets a build they can trust.

Say when Tor is blocked. The bootstrap poll loop simply ended at its
75-second deadline, leaving isStarting true with no further state, so a
network that blocks Tor was indistinguishable from a slow one and the UI
said "starting tor…" indefinitely. TorManager now exposes
bootstrapDidStall and posts .TorBootstrapDidStall, and the app reports
that mesh messaging still works while internet delivery is paused. It is
cleared on each new start or restart, so a later attempt can report again.

Let relays be added by hand. The four built-in relays are well-known
clearnet hostnames, which is four names for a censor to block and no
recourse short of a new build. NostrRelaySettings persists up to eight
additional relays, normalized, .onion accepted, with a settings editor.
They join the same target set as the built-ins and are subject to the
same activation policy. Removal reconciles against the previous set:
the teardown path iterates the current targets, so without that a removed
relay's socket and queued sends would linger — covered by a test that
fails without it. The merged list is cached rather than recomputed,
because allowedRelayList consults it once per candidate URL and would
otherwise read UserDefaults inside that loop.

Stop stranding people who denied location. The activation gate required
location permission or a mutual favorite, but teleporting into a geohash
requires neither, so someone with no permission and no favorites could
sit in a channel that never connected while Tor and the relays stayed
suppressed and nothing said why. Being in a location channel is now a
third arm of the gate, in both the activation service and the relay
manager's copy of the policy, and leaving the channel closes it again.

Stop burning the Tor timeout when Tor is off. GeoRelayDirectory awaited
Tor readiness unconditionally, but with the preference off TorManager has
been shut down, so every refresh spent the full bootstrap deadline and the
directory froze on its cached copy. It now keys on the preference, not on
live readiness: Tor wanted but unavailable must still skip the fetch
rather than fall back to clearnet.

Say what turning Tor off costs. The toggle's copy described it as
hiding your IP "for location channels", understating both scope and
consequence. It now names private messages too, and while the toggle is
off the settings screen states that every relay can see the device IP.

Make builds verifiable. There was no release verification of any kind:
no signatures, no checksums, no documented procedure. Post-takedown that
is the acute gap, because mirrors appear and people install whatever they
can find during a shutdown. source-manifest.yml publishes a per-tag
SHA-256 manifest with a provenance attestation, self-checking before it
publishes, and docs/VERIFYING-A-BUILD.md explains how to verify source and
states plainly that compiled builds from anywhere but the App Store cannot
be verified. It also records the gaps honestly: no published signing key,
no reproducible build, no non-GitHub mirror.

docs/TOR-INTEGRATION.md was substantially stale — it documented a
torrc, SOCKSPort and ControlPort that the in-process Arti client does not
use, and claimed there are no user-visible settings — so it is rewritten,
including the deferred gap below.

Deferred: no Tor bridges or pluggable transports. arti-client is built
without pt-client or bridge-client and bootstraps from stock config, so
in a country that blocks Tor outright there is still no circumvention
path — only a clear report that there isn't. Closing it needs the Rust
features, bridge config through the FFI, and an xcframework rebuild under
the pinned toolchain with a provenance update, which is its own change.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 19:45:37 +02:00
c079d2ab5d Harden what a locked or seized device gives away (#1484)
* Harden what a locked or seized device gives away

The realistic compromise for many of the people this app is built for is
not interception but a phone taken, and often unlocked under coercion.
Content encryption is in good shape; these are the gaps around it.

Hide notification previews, by default. Notification content is rendered
by the system on the lock screen, so it was readable without unlocking:
DM alerts carried the sender's nickname and the full message body, and
geohash alerts put the geohash in the title. Alerts now state that a DM,
mention, or location-channel activity arrived and withhold the rest until
the app is opened. userInfo still carries the routing peer ID and deep
link, neither of which the system displays, so taps land where they did.
A settings toggle restores full previews for anyone who wants them.
Default-on is the deliberate part: a phone face-up on a table should not
narrate conversations, and someone who wants previews can say so.

Cover the window on willResignActive, so the snapshot iOS stores for the
app switcher shows a placeholder rather than an open conversation. Opaque
rather than blurred, because blurred large text stays partly legible and
the snapshot goes to disk. Added synchronously from a UIKit notification
with queue: nil, since the capture follows shortly after and an
OperationQueue hop or a SwiftUI state change can lose that race. Panic
wipe already deleted snapshots already on disk; this stops new ones from
being worth deleting.

Bound media by age as well as size. The 100 MB quota only ever considered
incoming files, so outgoing media had no lifetime at all and a received
photo could outlive its conversation indefinitely. A launch-time sweep now
deletes managed media older than seven days, incoming and outgoing, with
the same exemptions quota eviction honors: in-flight live captures and
files reserved by a delivery or deletion in progress.

Make /clear tell the truth on the mesh timeline. It recorded an echo
watermark and left the gossip archive on disk for up to 6 hours, so
someone who cleared before a police stop had deleted nothing. Clearing now
erases the archive too. The watermark still matters: it suppresses
pre-clear messages this device hears again from peers. The cost is that
the device stops serving recent public backlog until it hears fresh
traffic, which is a fair reading of what clearing a timeline means.

Documented in PRIVACY_POLICY.md and the privacy assessment, including a
new section on what is deliberately NOT addressed: there is still no
duress mechanism of any kind (no decoy passphrase, no wipe-on-failed-auth,
no app lock), macOS gets no file-protection classes, and media is not
sealed at the app layer. The duress question is a product decision as much
as an engineering one, since in some jurisdictions destroying data on
demand is itself an offence and hiding may protect someone better than
destroying, so it is called out rather than guessed at.

Three findings from the audit that prompted this work turned out to be
already fixed on main and are not included: keychain accessibility is
AfterFirstUnlockThisDeviceOnly with a retrying migration, the panic media
wipe uses a two-location durable marker transaction, and panic already
discards staged share-extension content.

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

* Inject the previews preference instead of reading shared defaults

CI caught a real problem this introduced. `NotificationServiceTests`
already asserted the full-preview title and body, and both it and the new
redaction tests read `NotificationPrivacySettings` from
`UserDefaults.standard` in the same process. Whichever ran second
depended on the other's cleanup, so it passed locally and failed in CI:

  XCTAssertEqual failed: ("🔒 new dm") is not equal to ("🔒 DM from Alice")

Fixed at the source rather than by ordering or serialization.
`NotificationService` now takes a `hidePreviewsProvider`, defaulting to
the real preference, so each test states which behavior it asserts. The
pre-existing test asks for previews shown and gains a redacted
counterpart; the redaction tests no longer touch the shared store.

`NotificationPrivacySettings` also gained store-injecting accessors so
the default-value and round-trip assertions can use an isolated suite
rather than mutating preferences other tests read.

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

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 19:26:29 +02:00
229a41557e docs: correct inaccurate privacy and metadata claims (#1485)
* docs: correct inaccurate privacy and metadata claims

Several documented guarantees did not match the implementation. These
matter more than ordinary doc drift: someone deciding whether to carry
this phone to a protest reads these sentences as the threat model.

- Peer IDs were described as "short ephemeral IDs derived per session"
  that "rotate periodically" and "prevent tracking". They are the first
  8 bytes of the Noise static key fingerprint, stable across sessions
  and reboots, and replaced only by a panic wipe. Corrected in the
  whitepaper (§3, §8), IdentityModels, and BitchatProtocol, whose
  header notes claimed "no persistent identifiers in protocol headers"
  while every header carries exactly one.
- "No plaintext message content is ever written to disk" was false for
  accepted media, which is stored unsealed under the platform's
  data-protection class. Narrowed to what actually holds.
- Padding was described as applying to all packets but fragments. Only
  noiseEncrypted and noiseHandshake frames are padded; the pad bytes
  equal the pad length rather than being random; and because that
  length must fit one byte, a frame needing over 255 bytes of padding
  is emitted unpadded. Documented in the whitepaper (§4.1) and
  MessagePadding.
- The gossip archive window is 6 hours in production, not the
  15 minutes claimed in PRIVACY_POLICY.md and the privacy assessment.
  The 15-minute figure is the struct default that BLEService overrides.
- The privacy assessment credited iOS BLE address randomization without
  noting that stable app-layer identifiers defeat it.

The whitepaper's future-work list now names the changes these
corrections imply: rotating on-air identity, padding for non-Noise
types, and making the announce neighbor list optional.

No behavior change; comments and documentation only.

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

* docs: correct the same claims in the README

The README repeats two of the claims corrected elsewhere in this PR, and
it is the document people actually read before deciding to trust the app.

- "no persistent identifiers" is the inverse of what the mesh does; it
  now points at the whitepaper's identity and metadata sections.
- "end-to-end encryption with forward secrecy" holds for live Noise
  sessions but not for sealed store-and-forward mail, which the
  whitepaper already flags as its main cryptographic trade-off.

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

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 19:02:26 +02:00
jackandGitHub e9275cb3d8 Relabel private Nostr envelopes honestly in docs; harden legacy envelope validation (#1480)
Split-out safe half of #1437 (the kind-1402 wire migration stays held for Android coordination). Docs (README/WHITEPAPER/PRIVACY_POLICY/privacy-assessment) now describe the actual proprietary DM construction — kind 1059 gift wrap carrying XChaCha20-Poly1305 with a 24-byte nonce, base64url v2: framing, and an HKDF that borrows the nip44-v2 info label but is not the NIP-44 key schedule — instead of claiming NIP-17/44/59.

Hardens the existing legacy inbound path: 64 KiB ciphertext cap before decode (~46x the largest producible legacy envelope), outer kind/recipient-tag/signature binding, tagless kind-13 seal binding, unsigned kind-14 inner binding, inner tags restricted to the two shapes deployed clients emit (verified against every historical iOS release and a fixture frozen from Android production), SecRandomCopyBytes failure now throws, non-UTF-8 plaintext now throws instead of returning empty. Adds frozen cross-platform fixtures with hash-pinned generators; interop-reviewed with no rejection surface for deployed clients.
2026-07-26 12:50:17 +02:00
cd727c6867 Make panic wipe deterministic and device-bound (#1431)
* Make panic wipe deterministic and device-bound

* Scope install markers to iOS

* Harden panic recovery and service shutdown

* Invalidate queued BLE ingress during panic

* Harden panic keychain and media cleanup

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: jack <jack@deck.local>
2026-07-26 10:28:50 +02:00
jackandGitHub 820c933958 Harden PTT audio and the 1.7.1 release (#1423)
Centralize PTT and voice audio-session ownership, harden courier/bridge/outbox delivery and recovery, correct location and delivery-state races, add privacy/release metadata, and ship reproducible universal Arti slices with Release CI coverage.

Validated by the full iOS suite, repeated audio/fragment/performance regressions, BitFoundation tests, strict lint and dead-code analysis, universal iOS Release builds, and iOS/macOS archives.
2026-07-10 14:04:09 +02:00
jackandClaude Fable 5 4093ee6733 Rebuild Arti from audited source with enforced provenance
Vendored arti.xcframework rebuilt from source (Rust 1.96.0, normalized
archive metadata for reproducible hashes). New ARTI-BINARY-PROVENANCE.md
records toolchain, rebuild steps, and a SHA256 manifest for every file
in the xcframework. A new CI workflow turns that policy into a gate:
PRs must keep the binary matching the manifest, and binary changes must
ship with source/lockfile/build-script evidence.

Also raises TorManager.awaitReady's default timeout from 25s to 75s to
match the bootstrap monitor deadline - a shorter wait reported "not
ready" while Arti was still legitimately bootstrapping, silently
stranding queued relay work.

Privacy policy, Tor integration doc, and privacy assessment updated to
match the current implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:22:15 +01:00
4f1ac30f12 Feat/mesh robustness efficiency (#451)
* chat: de-dup private chats across ephemeral/stable IDs; prefer most advanced delivery status\n\n- Fixes LazyVStack duplicate ID warnings and blank row in PM\n- Merges messages by id from ephemeral and Noise-key stores\n- Chooses read > delivered > partiallyDelivered > sent > sending > failed (newer wins on tie)\n- Ensures status icon updates immediately without waiting for another send\n- Adds exhaustive handling for DeliveryStatus in ranking

* logging: reduce noisy info logs to debug; keep errors/warnings\n\n- Downgrade routing/ACK/subscription/connect logs to debug\n- Retain security/fingerprint/keychain info logs\n- Keep errors and warnings intact\n\ndocs: add docs/privacy-assessment.md covering BLE privacy, routing TTL/jitter, Nostr E2E gift wraps, ACK throttling, and logging posture

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2025-08-17 21:25:25 +02:00