listDevices returns identity-only stubs — enough for a device we already have a session with, useless for one we do not, because initiate() verifies the signed prekey's signature and a stub carries empty bytes. Choosing between the two lists meant that whenever ANY device already had a session, every device got a stub, so a device still needing one could drop out of the fan-out with no partial-delivery warning (the other device succeeded, so was non-empty). The rule is union: full bundle where the key route returned one, stub otherwise. Found while verifying a two-device pair by hand; the live suite's 'a message reaches every device a person has' passes either way, which is why it needed the hand test to surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
2.1 KiB
Markdown
46 lines
2.1 KiB
Markdown
# Security
|
|
|
|
## Reporting a vulnerability
|
|
|
|
Email **security@flamenetmessenger.com**. Please do not open a public issue for
|
|
anything affecting confidentiality or authentication. We acknowledge within 72
|
|
hours.
|
|
|
|
## Status
|
|
|
|
**Not independently audited.** This Android app has been reviewed in-house and
|
|
is covered by the JVM unit suites plus on-device instrumentation, alongside the shared 253-check relay conformance suite and
|
|
cross-platform interop tests that run the real engine on both sides.
|
|
|
|
An internal adversarial audit on 2026-08-29 found and fixed a family of
|
|
sender-attribution flaws: the ratchet authenticates a *device*, and the user id
|
|
inside a sealed envelope is a claim its author chooses — the two were not bound.
|
|
Devices now bind to their user on first proven use, control traffic (delivery
|
|
keys, avatars) is refused from an unproven sender, and the key-transparency log
|
|
head is verified against a pinned issuer key. Finding that class of bug in-house
|
|
is a reason to expect more from outside review, not fewer.
|
|
|
|
## What the app protects, and what it cannot
|
|
|
|
Private keys are generated on the device and live in the Android Keystore, which never hands out key material. Message history is
|
|
sealed at rest. The app defends against a **malicious or compromised relay**
|
|
reading messages, and against a passive adversary recording traffic to decrypt
|
|
after a quantum computer exists (when both ends publish a post-quantum
|
|
identity — the safety number says which guarantee it covers).
|
|
|
|
It does **not** defend against:
|
|
|
|
- **A compromised device.** Malware with your unlock reads plaintext. The app
|
|
lock is a speed bump against someone holding your unlocked phone, not
|
|
encryption.
|
|
- **Metadata.** The relay sees who is addressed and when. Sealed sender removes
|
|
the sender from the stored row, not from the network connection.
|
|
- **Your correspondent.** Anything you send can be screenshotted, kept, or
|
|
repeated by the person you sent it to.
|
|
|
|
## Verifying what you run
|
|
|
|
Every byte of the shipped APK's contents reproduces from this source; only the
|
|
randomized signing block differs. See `docs/REPRODUCIBLE.md` in the relay repo
|
|
and `scripts/verify-build.py` in the Android repo.
|