Repository navigation
Releases: b4lol/umbra
Release list
v1.0.0-alpha.4 — mesh/Nym transports, PQ-MLS group cells, GUI skeleton, Decoy Vault
The off-grid and group increments land: Wi-Fi Direct mesh and Nym mixnet
transports, PQ-MLS group cells with roster sync/removal/rotation, the
Wayland GUI skeleton with Engine/UI process separation, Decoy Vault,
hardware-key-gated keystores, per-transport privacy profiles shown at
runtime, and secret-scanning CI. The repository layout was reorganized
(topical docs under docs/, the C PT proxy under components/pt-proxy/).
Added
- Large-group scalability benchmark (TODO B.2.5, information-only —
no code changed as a result):crates/umbra-group/tests/scalability_benchmark.rs
measures the group creator's own wall-clock/peak-memory cost of
building a 10- and a 50-member cell and sending one group message.
Result: every per-call cost (create_group/export_keypackage/
add_member/send_group_message) is flat regardless of group size —
add_membercosts the same whether fanning out to an empty roster or
a 48-member one (~16ms/recipient difference), and peak RSS grows by
only ~2 MiB from N=10 to N=50. The dominant, N-independent cost is
each call's ~19-38s of real Argon2id KDF. Confirms TreeKEM's own
path-update cost is effectively O(log N) in practice AND that
Umbra's own per-member delivery fan-out (the a priori suspected
bottleneck) is negligible at these sizes — no engineering follow-up
(session reuse, parallel fan-out) is warranted by these numbers. - Mesh/Nym transport group-inbound wiring (TODO B.2.4):
umbra serve-meshandumbra-nym serve-nymcan now receive PQ-MLS group
cell frames (Welcome/Commit/application messages) — previously only
the Torumbra servepath could. Extracted the transport-agnostic
group-frame parsing/processing (GroupInboundContext,InboundEvent,
handle_group_frame) out ofserve.rsinto a new, feature-ungated
umbra-cli::group_inboundmodule, since it was found gated behind
thetorfeature — unreachable from a mesh-only build or from
umbra-nym-cli, which deliberately buildsumbra-cliwith neither
tornormeshenabled.mesh_serve.rsgrants the same
groups/keypackagessandbox exception the Tor path already had;
umbra-nym-cli's bridge now returns a full inbound-event enum instead
of raw plaintext bytes. Each transport keeps its own NDJSON emitters
(group-text/group-joined/group-updated/group-roster-synced),
matching the established per-transport duplication convention. One
hermetic test per transport, each driving a real Welcome/application
frame through the transport's own entry point. - Group-mode follow-up increment (TODO B.2.1/B.2.2/B.2.3, ADR-033
addendum):RosterSync— typed application-level group payloads (tag
0x00user text /0x01roster sync) riding the group's own AEAD
channel close the Welcome-joined member's empty-roster gap; every
add/removefans out a full roster snapshot (sender's own entry
included —umbra group creategained--self-name), adopted under
a strictly-newer-epoch staleness guard.umbra group remove
(membership revocation via Remove Commit; the removed member keeps
pre-removal traffic but cannot decrypt anything newer) andumbra group rotate(manual on-demandself_updatere-key). No ACL by
design — the single-cell trust model is co-equal. The hermetic
3-party end-to-end test now covers the full create→add→sync→send→
reverse-send→remove→rotate flow with zero test-side roster patching,
including the post-removal no-decrypt proof. - Per-transport privacy/trust profiles, shown at runtime
(crates/umbra-cli/src/privacy.rs, THREAT_MODEL.md "Per-transport
privacy/trust profiles"): every transport (Tor, Wi-Fi Direct mesh,
Nym Sandbox/mainnet, PQ-MLS group cells) carries a fixed
three-dimension rating —
anonymity/metadata protection, content confidentiality, network
maturity — on a closed STRONG / MODERATE / LIMITED / NONE scale,
deliberately not collapsed into a single score so the operator sees
the nuances and makes the transport decision themselves. Printed on
stderr by every send/serve command before sandboxing (always on, no
opt-out; stdout's NDJSON contract untouched) and shown persistently
in the TUI footer. Closes a real information gap: nothing at runtime
previously told aumbra-nymuser they were on the Sandbox TESTNET.
umbra groupsubcommands print the group profile, which carries the
partial-PQ ciphersuite caveat (ADR-033) and the B.2.1 roster gap. - Secret-scanning CI (
.github/workflows/secret-scan.yml,
.gitleaks.toml, SECURITY.md "Secret Leakage Prevention and
Response"): every push and pull request is scanned with gitleaks
over the FULL git history (pinned, SHA256-verified binary; findings
redacted from logs), plus a weekly scheduled re-scan as detection
rules evolve; a failing scan blocks the merge. GitHub-side secret
scanning with push protection complements it at push time. Local
pre-push equivalent:just secrets(CONTRIBUTING.md verification
chain step 9). - Group-persistence schema versioning (TODO B.2.6): the
AEAD-encrypted on-disk group-state and key-package blobs now carry an
explicit inner schema-version field (SCHEMA_VERSION = 1, decoded
with a serde default so pre-versioning blobs still decrypt) — but any
version other than the current one is rejected with a loud
GroupError::Malformedat load time rather than being silently
misinterpreted; a future v2 gains a clean migration hook. The OpenMLS
dependency family (openmls,openmls_libcrux_crypto,
openmls_traits,openmls_memory_storage) is exact-pinned in the
workspace manifest because the serializedMlsGroupblob format is
not guaranteed stable across upstream releases. Two regression tests
prove pre-versioning blobs load as a loud version error, not a silent
misdecode. umbra-nym send-nymNDJSON sent confirmation:send-nymnow
prints the same{"event":"sent","bytes":N,"frames":N}success event
asmesh send/send --onion(the byte/frame counts come back from
the bridge, not a guess), closing the stdout-contract gap with the
other transports. The stale TODO note about asend-nym
temp-directory leak was removed — the ephemeral connect path is
in-memory only and never touches disk.
Changed
- Repository layout: topical documentation (ARCHITECTURE,
CRYPTOGRAPHY, DECISIONS, TODO, THREAT_MODEL, …) now lives under
docs/; the C pluggable-transport proxy moved to
components/pt-proxy/. The root keeps only the GitHub-convention
files (README, LICENSE, CHANGELOG, CONTRIBUTING, SECURITY) plus build
configuration — andfuzz/, which stays at the root because
cargo-fuzz hardcodes the<project>/fuzz/location. All in-repo
references and the workspaceexclude/.gitignoreentries were
updated accordingly.
Added
- Unmanaged pluggable-transport support (TODO B.1, ADR-030):
umbra serve/send --onion/tuiaccept--pt-socks 127.0.0.1:PORT+ repeatable--bridge "…"(or abridgesfile next
to the keystore, read pre-sandbox) and configure arti's unmanaged
(loopback SOCKS5) transports — Umbra never spawns or links PT
binaries; non-loopback endpoints and half-configured setups fail
closed. PT protocol names are derived from the bridge lines. components/pt-proxy/skeleton: a standalone, loopback-only SOCKS5 proxy
component in C under ADR-030's scoped, owner-granted language
exception (process-isolated, hardening-flag build, ASan/UBSan gate
target). The SOCKS5 front-end (RFC 1928, no-auth CONNECT, bounded
parsing, exact reply codes, deadline-guarded upstream dial) is
implemented and integration-tested (components/pt-proxy/tests/socks5.sh, also
clean under ASan/UBSan).pt-proxyobfs4 client handshake (roadmap step 3): ntor variant- Elligator 2 representatives, implemented in C against the Go
reference (lyrebird) as the wire authority — libsodium (system) for
X25519/HMAC-SHA256/HKDF-SHA256/SHA-512/CSPRNG, vendored Monocypher
4.0.3 for Elligator 2 only. Verified BYTE-EXACT against fixtures
dumped from the Go reference (make vectors, normal + ASan/UBSan
builds; regeneration recipe incomponents/pt-proxy/tests/govectors/).
- Elligator 2 representatives, implemented in C against the Go
pt-proxyobfs4 framing + packet layer + relay (roadmap step 4):
XSalsa20-Poly1305 frames with SipHash-2-4-DRBG length obfuscation
(in-house streaming SipHash, cross-checked against libsodium's
one-shot variant and the Go DRBG block sequence), nonce
prefix|counter-BEfrom 1 with fatal wrap, the Bider
length-countermeasure, the packet layer (payload / PRNG-seed /
unknown types), and the poll-driven bidirectional relay wired into
the SOCKS5 path — the proxy now carries real traffic (iat-mode 0).
Verified byte-exact against Go framing fixtures (make vectors) and
end-to-end againsttests/mockbridge.c, a reference-faithful
test-only obfs4 server (make relay-test: 1 KB / 5 KB / 100 KB echo
round-trips + fail-closed mid-handshake cut, normal and ASan/UBSan
builds).pt-proxyiat-mode traffic shaping (roadmap step 5): the length
and inter-arrival-time distributions behind obfs4's iat-mode 0/1/2.
Gomath/rand.Randhelper semantics (Int63/Int31n/Float64/Perm)
replicated over the obfs4 SipHash-2-4-OFB DRBG (src/gorand.c) and
the uniform weighted-distribution tables via Vose's alias method
(src/probdist.c), both pinned BIT-EXACT against fixtures dumped
from lyrebird'scommon/probdist(make vectors). Per connection:
distributions seed locally and RESET on the bridge's PRNG-seed packet
(iat seed = SHA-256 of the length seed, Go parity); inter-chunk
delays are scheduled via poll deadlines — never slept inline — with a
bounded 4-slot pending queue that backpressures the SOCKS5 client;
paranoid mode (2) chops every burst at sampled lengths and pads the
tail up with Go padBurst semantics (resample on wrap; a sampled 0 is
redrawn where...
v1.0.0-alpha.3 — interactive TUI client + live-verified outbound Tor
The interactive Ratatui TUI client ships, and live-verifying its network
path caught a real alpha.2 defect: outbound .onion connects were
impossible (missing arti onion-service-client feature). Both
directions of the Tor transport are now live-verified.
Added
- Interactive Tor TUI client (
umbra tui,torfeature): live
inbound onion feed and compose-and-send over Tor in one sandboxed
process. The UI thread owns the terminal; a Tokio runtime runs
bootstrap, the accept loop, and outbound sends in the background over
channels. Tab cycles the peer selection; the log is bounded
(400 lines); per-session plaintext copies move inZeroizing
buffers. Hardening order mirrorsserve(ADR-025): memory locks
before the keystore read, Landlock zero-FS + [Tor tree rw, /etc ro,
/dev/tty] and Seccomp before the runtime starts. - Live TUI background-path test (
crates/umbra-cli/tests/tui_live.rs,
#[ignore]d): self-send through the client's own onion service —
PASSED on the real Tor network (2026-09), closing the outbound
live-verification gap below.
Fixed
- Outbound onion connections were impossible on the live network:
arti-clientwas compiled withonion-service-service(hosting) but
WITHOUTonion-service-client(connecting to.onion), so every
send --onion/ TUI send failed at connect time with "feature
onion-service-client not compiled in". The alpha.2 outbound flow had
never been live-tested; the gap is now closed in code and verified by
the self-send live test.
Changed
umbra tuinow requires--keystoreand takes--nickname
(defaultumbra-tui) for its persistent inbound onion identity; the
command and thetuimodule are gated behind thetorfeature.serve: the address wait and the accept loop are extracted as the
sharedwait_for_address/inbound_loophelpers (reused by the
TUI); seeds areArc-shared. NDJSON behaviour unchanged.tor_send: the send core is extracted assend_over(bounded
120 s connect, one PQXDH session) so the TUI reuses it; the CLI flow
is unchanged.peers: newlist_names(sorted record names; a missing directory
is an empty list) for pre-sandbox peer loading.
Verification
- 122 test cases across 19 integration suites; hermetic CI unchanged
(4 required checks). Live:tui_liveself-send PASSED on the real
Tor network (2026-09); the alpha.2just live-testidentity-persistence
result stands.
Umbra v1.0.0-alpha.2
Umbra v1.0.0-alpha.2
Status: alpha. The cryptographic core is complete and CI-verified; the onion-identity persistence is now live-verified on the real Tor network.
Highlights since alpha.1
- Interactive Tor flows:
umbra serve(inbound onion-service daemon, NDJSON events) +umbra send --onion(outbound, 64 KiB ceiling) — persistent onion identity, field-verified: two consecutive bootstraps published the same address. - Burst-level cover traffic (ADR-005): Poisson-driven DUMMY_COVER frames interleaved with real frames on every send path; wire-indistinguishable; receivers destroy them silently.
- Best-effort register scrub:
asm!zeroing of caller-saved registers at PQXDH/skipped-key/GuardedBuffer boundaries (mitigates the upstream removal ofzero-call-used-regs; residuals documented). - Security hardening: bounded inbound reassembly (64 KiB — anti lock-exhaustion DoS), time-bounded handshake reads, /dev/tty Landlock rule fixed (latent production failure), Seccomp gains Arti's state-mutation syscalls.
Security note
A throwaway test onion identity briefly appeared in git history during the live-test round; the key was shredded on disk, the artifacts untracked, and the history rewritten (the identity is destroyed and never reused). Test identities are generated fresh per run.
Verification
- 117 test cases / 18 integration suites; hermetic CI 4 required checks green.
- Live:
just live-testpassed on the real network.
License: GPL-3.0-or-later
Umbra v1.0.0-alpha.1
Umbra v1.0.0-alpha.1 — Section A (MVP) scope complete
Status: alpha. The cryptographic core is complete and CI-verified (39/40 TODO A tasks; one blocked upstream). The interactive product surface (GUI, Android) and live-network field testing are not done — see the honest-scope table in README.md.
Highlights
- PQXDH (X25519 + ML-KEM-768, ML-DSA-65-signed pre-keys) + Double Ratchet with bounded skipped-key store, hostile-header bounds and transactional decrypt (Signal spec §3.5)
- SMP verification with identity-fingerprint binding and per-session transcript-SSID mixing (relay-resistant)
- Embedded Arti Tor v3: outbound + inbound onion services, persistent onion identity (0700 native keystore), strict Vanguards-Lite pinning, inbound hs-pow with a memory-bounded queue
- Client hardening: Landlock zero-FS sandbox, Seccomp allowlist with IPv4/UNIX-STREAM-only network kill-switch (IPv6/UDP/DNS-53 fail at kernel level), mlockall/PR_SET_DUMPABLE/RLIMIT_CORE, GuardedBuffer, Argon2id keystore, SAS pairing, peer records
- Pipe transport:
umbra send --peer NAME | umbra recv(binary or --json NDJSON) - Verification: 32+ hermetic suites, proptest, dudect-style constant-time suite, 4 fuzz targets, ASan nightly CI, weekly mutation testing
Known limitations (authoritative: TODO.md)
- CPU register zeroing blocked upstream (rustc removed the flag)
- Cover-traffic pump not yet wired into interactive flows
- Persistent onion identity is mechanism+tests only (no production call site)
Verification
just check # fmt + clippy (-D warnings) + full test suiteLicense: GPL-3.0-or-later