docs/privacy-assessment.md
Last reviewed: July 2026
The user-facing contract is PRIVACY_POLICY.md. This document records implementation-level behavior and residual risks that should be re-audited when storage or transport semantics change.
Signed announces can expose:
The app does not advertise the device's assigned name. iOS manages BLE address randomization; bitchat does not attempt to create a stable MAC address.
That randomization does not deliver the unlinkability it might suggest, because the application layer publishes stable identifiers above it. The 8-byte peer ID in every packet header is the first 8 bytes of the Noise static key fingerprint, so it does not rotate; announces carry the static keys themselves; and the fixed service UUID makes any bitchat device detectable as such by a passive scanner. A receiver in radio range can therefore recognise a specific device across sessions and locations, and detect that the app is in use at all. RSSI, timing, traffic volume, and radio fingerprints remain observable as well.
Ingress validates announce structure, sender binding, signatures, payload sizes, and freshness. Current-link Noise authentication is required before destructive courier handoff or strict directed delivery. Floods, queues, fragments, ingress work, and per-peer state are bounded.
Residual risk: private-message metadata such as timing, radio adjacency, ciphertext size, rotating recipient tags, and relay connections remains observable. A compromised recipient device can disclose plaintext.
Public archives contain content already intended for public mesh/board distribution, but a seized unlocked device can reveal it. Group metadata and media can reveal relationships or content even when the in-memory chat timeline has gone away.
BitChat's proprietary private-envelope fallback protects plaintext with secp256k1 key agreement, HKDF-SHA256, and XChaCha20-Poly1305. It is not NIP-17, NIP-44, or NIP-59 compatible and does not provide forward secrecy against later compromise of the recipient's static Nostr private key. Relays still see the recipient public-key tag, event timing and size, and network metadata.
Bridge courier drops use a throwaway publisher key, an opaque Noise-sealed envelope, and a day-rotating recipient tag. Only a party already holding the recipient's Noise static key can compute candidate tags.
Relay publication is considered successful only after an explicit NIP-01 OK true from at least one target relay. Rejected, disconnected, timed-out, or merely socket-written events stay retryable.
When mesh bridge is enabled, public mesh messages not marked “nearby only” are signed under a per-cell Nostr identity and published to a neighborhood rendezvous geohash. Presence and public bridge traffic therefore expose a coarse area to relays and participants.
A bridge gateway can carry signed bridge/location events and opaque courier drops for nearby mesh-only peers. It cannot validly publish a neighbor's radio-only message because the author must first sign the bridge event.
Relays added by hand persist in local preferences (nostr.customRelays, at most 8, normalized on read) and are wiped on panic. An added relay names an operator someone chose to route through, so it is treated as sensitive local state rather than inert configuration. .onion addresses are accepted, which is the point: the four built-in relays are well-known clearnet hostnames and a filter blocking four names would otherwise end internet-delivered private messages until a new build shipped.
Turning the Tor preference off routes relay sockets and the relay-directory fetch directly, disclosing the device IP to every relay operator including those carrying private messages. The settings UI states this while the preference is off.
Residual risk: Nostr relay retention and logging are outside project control. Public events may be copied indefinitely. Timing, coarse location, and participation can be correlated even when content is encrypted or per-cell identities are used.
CLGeocoder.reverseGeocodeLocation. Apple may process the supplied location under its own privacy terms, so this operation is not accurately described as wholly on-device.SecureLogger uses OSLog privacy markers and filters likely secrets. Release builds suppress debug verbosity.bitchat/PrivacyInfo.xcprivacy declares:
CA92.1 for app-only preferences and 1C8F.1 for the shared app groupC617.1 for app-container files and 3B52.1 for user-granted files35F9.1 for elapsed-time deadlines and timersbitchatShareExtension/PrivacyInfo.xcprivacy declares app-group UserDefaults reason 1C8F.1. Both manifests declare no tracking domains and no data collection by the app developer. They must remain bundled in their respective executable bundles.
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.
userInfo still carries the routing peer ID and deep link, neither of which the system displays. The preference is notifications.hideMessagePreviews; turning it off restores full previews.willResignActive, so the snapshot iOS stores for the app switcher shows a placeholder rather than an open conversation. The cover is opaque rather than blurred, and is added synchronously because the capture follows shortly after that notification. Panic wipe separately deletes snapshots already on disk.Not addressed, and deliberately out of scope here:
FileProtectionType application is inside #if os(iOS); Data Protection on macOS additionally requires an entitlement. The Mac app also has no app-switcher equivalent.The panic action clears identity/session state, preferences, location state, groups, prekeys, outbox mail, courier mail, bridge dedup state, gossip archive, board data, managed media, hand-added relays, and active subscriptions/transports. Managed media deletion completes synchronously, after active media work has been invalidated. Keychain secrets use device-only accessibility, and an install marker detects and clears app keys that survive uninstall before a later reinstall can use them. New persistent stores must add an explicit wipe hook and a regression test.
PRIVACY_POLICY.md.PrivacyInfo.xcprivacy files before archive submission.