Open Free and open source, read the code Get it Donate App help Contact
Messenger
The app
Developers
Project
Legal

A flamenet.io account is not a Flamenet Messenger account. The two sign-ins are separate.

Audit scope

A brief for an external reviewer. Auditors bill by the hour and spend the first of them working out what the system is and what is worth attacking, this document is an attempt to give that away for free rather than pay for it twice.

Nothing here asks for a conclusion. Where this project believes something is sound, that belief is stated so it can be attacked directly.

1. What the system is

Four components, four repositories, each with its own pipeline:

flamenet-e2e src/ JavaScript Reference cryptographic engine. Normative where implementations disagree. This repository.
flamenet-messenger-ios Sources/E2E/ Swift (CryptoKit) iOS engine and app. Checked against the reference in both directions.
flamenet-messenger-android engine/ Kotlin Android engine and app. Held to the published vectors rather than to the other two engines.
flamenet-relay Swift (Vapor) The relay. Holds no key that opens an encrypted message.

There is no browser client today. One existed as a WordPress plugin and was uninstalled; the relay serves its own site and no messaging client. This engine is consumed by the two native apps and by the test harnesses, not by a page.

That is a deliberate position rather than an accident of scheduling. A web client is re-delivered by the server on every load, so the party being audited would also be the party shipping the code that holds the keys, and could ship different code to one user. Nothing in this design defends against that, so any future browser client must be scoped as a separate trust story, and no confidentiality claim made about the native apps transfers to it.

2. What is in scope

In rough order of what would hurt most if wrong:

  1. Session establishment: X3DH and its post-quantum hybrid (spec §5, §12). Key confusion, missing domain separation, associated-data mismatches.

  2. The Double Ratchet (§6, §13). Nonce reuse, chain-key reuse across a ratchet step, out-of-order and skipped-message handling.

  3. Token authentication and proof-of-possession (§8, Token.swift). Replay, cross-device use, cross-relay use, and the single-use proof cache.

  4. Sealed sender (§14). Whether a sealed submission leaks its sender through any path, including rate-limit state, attachment rows, and error timing.

  5. Key transparency (§19). Whether inclusion and consistency verification can be made to accept a substituted key.

  6. Post-quantum identity (§20). Hybrid verification, and whether the downgrade pin can be evaded.

  7. That there is no way to store a readable message. The mode this list used to send a reviewer after, at-rest encryption under a key the operator held, is gone: AtRestCrypto.swift is deleted and nothing calls it. Worth an hour confirming that, rather than the hours the old entry would have cost looking for a file that is not there.

    This used to end "and no route accepts a plaintext body", which reads as a server-side guarantee and is not one. The relay never inspects payload. There is no length check, no base64 check and no shape check; it stores whatever string it is handed, subject only to the global 4 MiB body cap. So no route is DESIGNED to carry a readable body and every shipped client encrypts, but the server cannot refuse one, and a reviewer should not spend time looking for the check that refuses it.

    That is arguably the right design rather than a gap, since a relay able to recognise plaintext would be a relay that inspects message bodies. It is restated here because the old wording invited a reviewer to confirm something that does not exist.

3. What is explicitly out of scope

Not because it is safe, but because it is known:

  • Metadata. The relay sees recipient, timing, approximate size and source address. /metadata documents what is stored and for how long.
  • Group rosters. Server-visible by design; §22 records the alternative.
  • Compromised endpoints. Keys live on devices; malware on one reads plaintext.
  • Peers with no post-quantum identity. A device that has not taken the update, or one restored from a backup written before it, publishes no ik_pq and is authenticated classically. §20.2 makes that a supported state and not an error; it is not a silent downgrade. §20.6.

4. Trust boundaries

  • Client ⇄ relay. The relay is assumed hostile. It distributes prekey bundles, so it is the natural place to attempt key substitution, §19 exists to make that leave evidence, and §19.4 gossip is what detects a fork.
  • Issuer ⇄ relay. The issuer's verification key is configuration, never fetched. A relay that fetched it over HTTPS could be handed a key by a compromised issuer.
  • Client ⇄ issuer. Separate boundary, weaker rule. A client has no configuration channel, so it reads the issuer key from the relay's /.well-known/flamenet/relay once and pins it, as it does the log head. The first read is taken on faith and every head after it is checked, which catches a relay that changes its issuer key and not one that was dishonest from the first contact. §19.3.
  • Device ⇄ device. Peers authenticate through the ratchet. Identity changes are pinned and surfaced rather than accepted silently.

5. Assets

Ordered by consequence of disclosure:

  1. Device identity private keys (ikSig, ikDH), impersonation.
  2. The issuer signing seed, mints tokens the relay accepts for any device.
  3. Ratchet state, decrypts the messages of the sessions it covers.
  4. Delivery keys, permit sending sealed to a recipient; not confidentiality.

6. Where to start reading

Paths are given relative to the repository that holds them, since the split.

  • docs/SPEC.md (flamenet-e2e), the protocol, primitives through REST contract.
  • src/e2e.js (flamenet-e2e), the reference engine.
  • Sources/FlamenetRelay/Routes.swift (flamenet-relay), §8 handlers, including the one deliberately unauthenticated route.
  • Sources/FlamenetRelay/Token.swift (flamenet-relay), token and proof verification.
  • test/vectors/protocol-v1.json (flamenet-e2e), published vectors, reproduced by all three engines.

7. Claims we would like attacked

Stated as falsifiable propositions rather than assurances:

  1. A relay cannot read the contents of an encrypted conversation.
  2. A relay cannot serve a device key it has not first committed to the log, without two clients who talk to each other detecting it.
  3. A sealed submission's stored record does not identify its sender, and no other row does either.
  4. A token stolen from one device does not authorise action as another device, nor at another relay.
  5. A device that has published a post-quantum identity cannot be presented as classical to a client that has already seen it.

8. Known-unsound, do not spend time confirming

  • This list used to open by saying the operator held an at-rest key and could read any conversation sent with encryption turned off. That is no longer true, and leaving it here would have handed a reviewer a weakness to confirm that does not exist. The routes are gone and the table was dropped; the privacy page records the removal rather than quietly dropping the old text, on the grounds that people read it and decided things.
  • Sealed sender is not anonymity. The connection is still observed.
  • Key transparency without client gossip detects a server that changes its mind, not one that was never honest. Gossip is implemented on the web engine and iOS, and not on Android, which holds the verification primitives in KeyLog.kt and calls them from nowhere: no inclusion proof is checked, no head is pinned, no head is sent. A fork along social boundaries evades the two that do gossip, and nothing on Android detects one at all. §19.5.

9. Test surface

test/, seven suites under npm test: conformance, ratchet, client, vault, interop, relay, eddsa-session. The first four need nothing but node and are the four this repository's own pipeline runs. The relay suite boots the compiled binary and drives §8 over HTTP, negative cases included. The interop suite compiles the iOS engine from the app's own sources, so a drift between engines fails here rather than on a phone.

test/ holds more than those seven. federation, fleet, load, multihoming, interop-kotlin and interop-swift-kotlin each need something npm test cannot assume is present, a second relay, an Android toolchain, a throwaway stack to put under load, so they are run deliberately rather than on every change.

A green run is not evidence of security. It is evidence that the specific things we thought to check still hold.

Free and open source · No ads · No phone number · Your own server if you want one