Security model

What your relay can see, written down.

The relay never needs message plaintext or device private keys. It does need routing metadata.

0.1.x — public APIs and persisted formats may change before 1.0.

Where the boundary sits

Plaintext and long-term private keys exist only inside a device. Everything the relay holds is sealed, and everything sealed still carries an address.

One message travelling from one device to another through a relay Two outlined devices with readable contents and a private key inside each. Between them, sealed envelopes drawn as solid slabs leaning in the direction of travel, carrying metadata ticks on their outside edge, and the OpenE2EE mark standing in for the relay. Dotted brass rules mark where the message is sealed and where it is opened.Device A · readableDevice B · readablesealopenmetadata visiblesealedsealedrelay · never needs plaintext One message travelling from one device to another through a relay Two outlined devices with readable contents and a private key inside each, one above the other. Between them, sealed envelopes drawn as solid slabs leaning in the direction of travel, carrying metadata ticks on their outside edge, and the OpenE2EE mark standing in for the relay. Dotted brass rules mark where the message is sealed and where it is opened.Device A · readablesealsealedmetadata visiblerelay · never needs plaintextsealedopenDevice B · readable

The delivery envelope, field by field

This is the shape a relay adapter receives. Sealed sender omits two fields; it does not empty the envelope.

FieldIdentified deliverySealed senderWhat it is
targetUserIdvisiblevisibleWho the message is for
targetDeviceIdvisiblevisibleWhich of their devices
senderUserIdvisibleomittedWho sent it
senderDeviceIdvisibleomittedWhich device sent it
messageTypevisiblevisibleHandshake or steady-state
timestampvisiblevisibleWhen it was sent
groupIdvisiblevisiblePresent on group messages
clientMessageIdvisiblevisibleRetry idempotency key
ciphertextopaqueopaqueBytes the relay never needs to read

Some fields appear only in context: recipientRegistrationId accompanies the initial handshake envelope, and contentHint, ephemeral, and urgent are optional delivery hints. Your relay also assigns its own message id and server timestamp.

What sealed sender does not hide

Sealed sender removes the sender identity from the envelope. Five things it leaves in place, because a security page that only lists wins is marketing.

  • Who is receiving

    The recipient user and device are in the clear on every envelope, sealed or not. Sealed sender hides the sender, not the conversation.

  • When, and roughly how much

    Timestamps are plaintext. Message bodies are padded into 160-byte buckets, which coarsens length but does not conceal it, and attachments travel through a separate object store.

  • Group context

    The group ID travels in the clear. Multi-recipient sealed sends hand the relay the recipient list so it can fan the message out.

  • Network-level metadata

    Addresses, connection timing, and traffic patterns are properties of your transport and deployment. This SDK makes no claim about them, and implements no decoy traffic or timing jitter.

  • Sealed sender is best effort

    If the recipient profile key or identity key is unavailable, the client falls back to identified delivery rather than failing the send. Design for the fallback, not the happy path.

Threat model boundary

Taken from the security model in the repository. An attacker in full control of your relay can serve stale or hostile responses and observe every routing field above. The relay never needs message plaintext or device private keys, and once trust is pinned, an identity substitution that would reach them fails closed.

In scope

  • Passive network observation
  • Active tampering, replay, reordering, duplication, and truncation
  • Malicious or stale relay responses within the adapter contract
  • Identity component substitution after trust is pinned
  • Malformed prekeys, KEM material, protocol messages, and restored state
  • Loss and out-of-order delivery within configured protocol bounds
  • Later compromise followed by an uncompromised ratchet recovery step

Out of scope, or only partly mitigated

  • Compromised endpoints, JavaScript engines, dependencies, or build pipeline
  • Hostile same-process code and high-assurance local timing attackers
  • Guaranteed secret erasure from managed memory
  • Hardware, physical, speculative-execution, and microarchitectural attacks
  • Traffic-analysis resistance beyond the padding behaviour explicitly implemented
  • Signal Messenger interoperability outside the alignments stated in the security model
  • Authenticity on unverified first contact without a safety-number check

First contact is trust-on-first-use, not authenticated identity. A relay that is hostile from the very first message can substitute an identity before any trust is pinned; a safety-number comparison is what closes that gap. After trust is pinned, substituting either half of the identity tuple fails closed until a rotation is explicitly accepted.

What there is to check

Everything above is a claim. These are the figures behind it, each with the date it was measured, because an undated number stops being evidence the moment it ages.

5,875
assertions across 351 modules, 0 failing and 1 skipped, on 2026-07-24. The checks live in the private engineering repository this one is exported from; an export is only cut from a revision where they pass.
6
direct production dependencies — @noble/ciphers, @noble/curves, @noble/hashes, @noble/post-quantum, async-lock, unique-names-generator — resolving to 6 packages in total. The only transitive edges are the @noble packages depending on one another, so the resolved tree adds nothing that list does not already name. Everything else is a development or optional peer dependency. The published repository runs a build, a typecheck, and a production-tree advisory audit on every change.
7
published specifications the implementation profile is pinned to, by revision, in the table below.
72 hours
to acknowledge a vulnerability report sent to security@open-e2ee.dev rather than to a public issue, and 7 days to an initial assessment. The full policy, including disclosure handling and severity targets, is in the repository (opens in a new tab).

What those checks are, in the terms used elsewhere on this site: cryptographic known-answer checks run ML-KEM, hashing, AEAD, and signature primitives against published vector data. Conformance scenarios check session, group, and sealed-sender invariants against known-answer material and against behaviour documented in the published specifications. Property-based checks run over protocol and encoding invariants. Integration flows cover multi-device, group lifecycle, provisioning, and relay delivery. Adapter suites cover every storage, relay, and object-store adapter. The quickstart printed in the README is executed as written on every change.

Specifications, pinned by revision

The implementation follows its own versioned profile, based on these published specifications at these revisions. Mirrored from the SDK README, which is the maintained source.

SpecificationRevision
X3DH (opens in a new tab)Revision 1, 2016-11-04
PQXDH (opens in a new tab)Revision 3, 2023-05-24 (last updated 2024-01-23)
Double Ratchet (opens in a new tab)Revision 4, 2025-11-04
Sesame (opens in a new tab)Revision 2, 2017-04-14
ML-KEM Braid (opens in a new tab)Revision 1, 2025-02-21 (last updated 2025-09-26)
FIPS 203 (ML-KEM) (opens in a new tab)Final, 2024-08-13
RFC 8032 (Ed25519) (opens in a new tab)

Published limits

Not audited by any independent firm
Reviewed continuously by adversarial AI agents; not audited by any independent firm. Every change passes an adversarial AI review before it merges, and recurring whole-codebase AI audit passes run against the engineering repository. What that covers — and what it does not — is stated in the assurance summary (opens in a new tab). No independent firm has audited the SDK, and none is engaged. The codebase is structured for review — narrow state transitions, injectable adapters, small protocol seams — but structure is not certification, and neither is a machine reading it.
Not a validated cryptographic module
Pure-JavaScript cryptography is not FIPS 140-validated, and no part of this SDK is offered as a validated module. The ML-KEM implementation follows FIPS 203, but following a standard and being validated against it are different things, and only the second satisfies a programme that requires validation. If yours does, this is the wrong choice, and we would rather you learned it here than in week six.
JavaScript is not a constant-time environment
Browser, Node, and React Native engines provide no machine-level constant-time guarantee. The implementation uses best-effort patterns on selected paths; it does not claim timing equivalence against a co-resident adversary.
Memory cannot be reliably wiped
Typed-array overwrites shorten the life of some buffers. Strings, engine temporaries, registers, and garbage-collected pages remain outside the SDK’s control.
Post-quantum means confidentiality, not signatures
Post-quantum key agreement is the default and fails closed, using ML-KEM-1024 for PQXDH and an ML-KEM Braid ratchet. There is no public disabled mode. Identity signatures remain classical Ed25519 and X25519. The protection is against harvest-now-decrypt-later, not against a quantum adversary forging identities.
There is no key escrow, so device loss is real
Nothing recoverable is held on a server. Transferring state to new hardware requires the old device to still be available; a lost device means re-registering, and contacts see a safety-number change. Decide your recovery story before launch, not after.
Not Signal Messenger, and not wire-compatible with it
An independent implementation of the published Signal Protocol specifications. Not affiliated with or endorsed by Signal Messenger LLC or Signal Technology Foundation. The cryptographic core follows the Signal Protocol specification — same key agreement, same ratchet, in many places byte-identical — but the encoding is deliberately our own. Messages, identities, and sessions do not cross between the two, and safety numbers shown here will never match the ones a Signal Messenger client displays. Every deliberate difference, and what it costs, is documented in DEVIATIONS (opens in a new tab).

Take this to whoever signs it off

This page is the summary; the documents above are the evidence. Between them they carry the pinned specification revisions, the dependency and test figures, and the current audit status, each dated where it is measured.

If you need more than documents, enterprise and OEM agreements include a scoped security review, service levels, and negotiated portfolio or redistribution rights. That is a conversation rather than a checkout, and it starts with a meeting.

A question about the threat model, or an answer you need for a questionnaire — email security@open-e2ee.dev. No licence, meeting, or account first. If you are earlier than that, read how E2EE changes your app.