Foctet is an experimental Draft v0 implementation of authenticated encrypted
framing, a one-shot application/foctet body envelope, and encrypted archive
formats. The project is under active security and interoperability development.
It is not yet a stable, general-purpose production E2EE SDK. Several surfaces (datagram/UDP, WASM body envelope, HTTP protected context) are implemented but remain partial or evolving — see "Known limitations" below. Do not describe it as a complete E2EE library for arbitrary TCP/UDP/QUIC/WebSocket/WebTransport payloads until the gates in "Roadmap to a production claim" below are met.
The full threat model (per-threat defenses, residual risks, metadata leakage,
key-loss policy) is documented in docs/THREAT_MODEL.md; versioning,
key-lifecycle, and incident-response policies are in docs/POLICIES.md.
Please report suspected vulnerabilities privately. Do not open a public issue or pull request for security problems.
Contact (in order of preference):
- GitHub's private vulnerability reporting ("Security" → "Report a vulnerability") on this repository — the canonical channel; it keeps the report, discussion, and advisory in one place.
- If you cannot use GitHub, email the maintainer at the address listed on the
repository/crates.io profile, with
[foctet security]in the subject.
GitHub private vulnerability reporting is the encrypted security-contact
channel until the maintainer publishes an offline contact-encryption public key
and fingerprint under security-keys/. A v1 release is blocked until that key
is present, independently fingerprint-verified, and its rotation/revocation
procedure has been rehearsed. Never add a private key to this repository.
Include a description, the affected crate(s) and version(s), impact as you understand it, and a reproduction if possible.
Response targets (best-effort; this is a volunteer-maintained project):
- Acknowledgement within 7 days of the report.
- Triage and severity assessment (confirmed / not a vulnerability / needs more info) within 14 days.
- Fix or public advisory within 90 days for confirmed issues, sooner for critical ones; if a fix needs longer we will say so and agree on a disclosure date with you.
Coordinated disclosure is preferred: please allow the fix to ship before public discussion. Credit is given in the advisory unless you ask otherwise. There is currently no bug bounty.
In scope: the foctet-* crates in this repository, the wire format and key
schedule as specified in SPEC.md, the WASM/JS boundary, and the committed CI
supply-chain configuration. Out of scope: vulnerabilities in third-party
dependencies (report upstream; we will pick up the fix), and issues requiring a
compromised endpoint (see docs/THREAT_MODEL.md for the trust boundary).
While the project is in the 0.x Draft v0 line, only the latest published 0.x
release receives security fixes. Starting with v1, the current minor release
and the immediately preceding minor release receive security fixes for at
least 12 months after the newer minor is published. A major release receives
critical security fixes for at least 24 months after its successor is
published. The supported-version table in each advisory is authoritative when
a protocol/profile must be disabled sooner for safety.
- Confidentiality / integrity / authenticity of framed payloads and body envelopes via X25519 + HKDF-SHA-256 + XChaCha20-Poly1305, with the wire header authenticated as AEAD associated data.
- All-zero X25519 shared secrets are rejected in native handshakes, body envelopes (including streaming bodies), and archive recipient wrapping. Low-order recipient keys are rejected before sealing; malicious ephemeral keys while opening are reported as generic unwrap/authentication failures.
- Fail-closed sequence and key-id exhaustion on both the async (
FoctetFramed) and synchronous (SyncIo) paths — a frame is never emitted with a reused(key_id, stream_id, seq)nonce.SyncIoandFoctetFramedreserve a sequence before the first write and become terminal after a write or flush error, because local code cannot know whether the peer received the frame. Applications that need delivery semantics must use authenticated message IDs and idempotency. - No session-state restoration. Foctet does not provide a session-persistence format. After a crash or restart, applications must establish a fresh session; restoring traffic keys with reset or uncertain outbound sequence state can reuse a nonce and is unsafe.
- Terminal protocol failures.
Session, message/datagram endpoints,SyncIo,FoctetFramed, and WASM session endpoints reject all subsequent use after an inbound authentication, parser, replay, key, sequence, or session-control failure. Session closure drops active and retained traffic keys. Establish a fresh authenticated session; do not continue after a potentially diverged channel. - Published error policy.
docs/error-handling.mddefines the executable error dispositions and the required retry, rejection, or terminal-close action for Core, HTTP, and transport surfaces. - Replay protection via per-
(key_id, stream_id)sliding windows, committed only after AEAD authentication so a forged frame cannot desynchronize or DoS the receiver. The number of tracked windows is bounded (DEFAULT_MAX_REPLAY_WINDOWS) to prevent unbounded memory growth. - Authenticated-by-default native handshake. A default
SessionAuthConfigfails closed: an unauthenticated handshake requires an explicitSessionAuthConfig::unauthenticated_for_testing()/dangerously_allow_unauthenticated(true)opt-in, intended only for tests or for use inside an already-authenticated outer channel (e.g. mutually authenticated TLS). Identity authentication uses Ed25519 transcript signatures with pinned peer identities. - Optional context binding for body envelopes (
seal_body_with_context/open_body_with_context): an application-supplied context (for HTTP: method, authority, path, timestamp, message ID, …) is folded into the AEAD associated data so a captured envelope cannot be replayed onto a different request.
The implemented contracts below include explicit operational limits. Items described as pending or unsupported must not be relied on.
- HTTP anti-replay.
foctet-httpships a versioned protected-context schema (ProtectedContext,x-foctet-*carrier headers), a boundedReplayStorewith atomic check-and-insert (InMemoryReplayStore), and context-bound APIs (seal_request_with_context/open_request_with_context, plus Axum and Workers adapters) that bind method/path/query/message-id/timestamp/expiry into the AEAD and enforce single use. Multi-instance / serverless deployments have anAsyncReplayStoretrait (!Send-friendly for Cloudflare Workers) with a Redis backend (RedisReplayStore, atomicSET NX PX) and a Cloudflare Durable Object adapter (DurableObjectReplayStore). The stateless full-request family is gated behinddangerous-stateless-http. The low-levelseal_body/open_bodyprimitives remain stateless by design — production HTTP code must use the*_with_contextAPIs backed by an atomic shared, durable store. Cloudflare KV is not valid for replay decisions. Wrangler CI covers races, restart persistence, alarm expiry, and backend errors. High-level response APIs require the initiating request ID;docs/http-canonicalization.mdspecifies the proxy contract. - Forward-secret DH ratchet rekey. In-session rekey now performs a Diffie-Hellman ratchet step: each rekey mixes a fresh ephemeral X25519 output into a root-key chain, and rekeys alternate between the two peers (enforced by a turn flag, so the root chain cannot fork and both peers' ratchet keys rotate). This gives forward secrecy and, across an alternating rekey, post-compromise security in both directions. Operational note: under strictly one-directional traffic the alternation can stall after one step (the quiet side never takes its turn); rekey periodically from both ends for continued ratcheting. Byte-stream transports prepare one new ratchet generation, enqueue the old-key control frame, and only then commit; explicit rekeys flush before commit. Message/datagram workflows send the same transaction over a reliable encrypted control channel before adopting the new datagram key. An ambiguous output failure is terminal because Foctet has no rekey-delivery acknowledgement. Outbound rekey commit performs no fallible allocation; retained-key storage is reserved during the pre-delivery prepare phase.
- Datagram support. A dedicated datagram API
(
foctet_core::datagram::DatagramEndpoint: one bounded frame per datagram, size cap, authenticate-before-replay, loss/reorder tolerant) ships with a QUIC datagram adapter (foctet_transport::quinn::QuinnDatagramChannel) and a raw-UDP adapter over a connected socket (foctet_transport::udp::UdpDatagramTransport). Unconnected sockets are rejected, andnew_unvalidated_peermakes the 3x anti-amplification limit mandatory for server/listener handoff. Path-MTU discovery remains the caller's responsibility. Rekey-over-datagram is supported: the DH-ratchet rekey rides a reliable control channel andSecureDatagramChannel::rekey_from_sessionadopts the rotated keys, with retained previous keys so reordered old-key datagrams still decrypt. A browser-WebTransport datagram adapter now ships asfoctet_transport::webtrans_browser::BrowserWebTransportDatagrams(transport-webtrans-browser, wasm32) and is exercised in headless Chrome against a real native HTTP/3 WebTransport server, including reconnect, cancellation, backpressure, deliberate loss, and reversed delivery.docs/transport-matrix.mddefines path-MTU and sizing responsibilities. - WASM/TypeScript SDK (partial). The
foctet-wasmcrate ships awasm-bindgenAPI for the body envelope (seal/open, context-bound variants,KeyPair) and a framedFoctetSession(authenticated handshake, orderedsealMessage/openMessage, datagramsealDatagram/openDatagram, and in-session DH-ratchet rekey viaprepareRekey/commitRekey/handleControlMessage), with generated.d.ts, Node/browser/bundler builds, a Node interop test that opens Rust-produced envelopes, an in-browser runtime harness (foctet-wasm/examples/browser/index.html), and a headless-Chrome test suite in CI (foctet-wasm/tests/browser.rs). WASM clock limitation:wasm32-unknown-unknownhas no monotonic clock, so the age-based rekey threshold is disabled there; the frame-count and byte-count thresholds still apply, and a long-lived WASM session should still drive rekey explicitly when needed. Still pending: a published npm package and host-backed (non-extractable) key handling (documented as unavailable on current platforms). - Streaming HTTP bodies. A chunked streaming mode exists
(
foctet_core::body_stream, plusfoctet_http'sHttpStreamSealer/HttpStreamOpener): per-chunk AEAD with unique nonces, an authenticated final-chunk marker (truncation/extension resistance), ordering checks, and the same protected-context + replay binding as the one-shot path. Turn-key request and response wiring exists (StreamFrameDecoder,HttpRequestStreamReader,HttpResponseStreamReader, and the axum helperopen_request_stream, no whole-body buffering). Input buffering is capped to one maximum-sized undecoded frame and both readers require authenticated finalization after cancellation or EOF. - Wire format is unstable (
0.x, Draft v0). Even though vectors and interoperability fixtures are checked in CI, breaking wire changes may still occur until the v1 compatibility commitment begins.
Before using "production-ready" or "v1 stable" wording, all of the following must hold:
- All P0/P1 findings fixed and regression-tested (see the project review).
- Documentation, examples, and operational guidance aligned with the shipped surface.
- Authenticated peer identity or explicit authenticated-channel binding is mandatory for production constructors.
- HTTP has an authenticated protected context and replay defense, or is explicitly excluded from the production promise.
- The advertised transport matrix has real implementations and conformance tests.
- WASM/TypeScript are either truly shipped and tested or excluded from the claim.
- Dependency advisory monitoring, targeted hardening checks, CI coverage, and a vulnerability-response process are active.