Skip to content

Releases: b4lol/umbra

v1.0.0-alpha.4 — mesh/Nym transports, PQ-MLS group cells, GUI skeleton, Decoy Vault

Choose a tag to compare

@b4lol b4lol released this 16 Sep 11:58

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_member costs 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-mesh and umbra-nym serve-nym can now receive PQ-MLS group
    cell frames (Welcome/Commit/application messages) — previously only
    the Tor umbra serve path could. Extracted the transport-agnostic
    group-frame parsing/processing (GroupInboundContext, InboundEvent,
    handle_group_frame) out of serve.rs into a new, feature-ungated
    umbra-cli::group_inbound module, since it was found gated behind
    the tor feature — unreachable from a mesh-only build or from
    umbra-nym-cli, which deliberately builds umbra-cli with neither
    tor nor mesh enabled. mesh_serve.rs grants the same
    groups/keypackages sandbox 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
    0x00 user text / 0x01 roster sync) riding the group's own AEAD
    channel close the Welcome-joined member's empty-roster gap; every
    add/remove fans out a full roster snapshot (sender's own entry
    included — umbra group create gained --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) and umbra group rotate (manual on-demand self_update re-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 a umbra-nym user they were on the Sandbox TESTNET.
    umbra group subcommands 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::Malformed at 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 serialized MlsGroup blob 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-nym NDJSON sent confirmation: send-nym now
    prints the same {"event":"sent","bytes":N,"frames":N} success event
    as mesh 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 a send-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 — and fuzz/, which stays at the root because
    cargo-fuzz hardcodes the <project>/fuzz/ location. All in-repo
    references and the workspace exclude/.gitignore entries were
    updated accordingly.

Added

  • Unmanaged pluggable-transport support (TODO B.1, ADR-030):
    umbra serve / send --onion / tui accept --pt-socks 127.0.0.1:PORT + repeatable --bridge "…" (or a bridges file 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-proxy obfs4 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 in components/pt-proxy/tests/govectors/).
  • pt-proxy obfs4 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-BE from 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 against tests/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-proxy iat-mode traffic shaping (roadmap step 5): the length
    and inter-arrival-time distributions behind obfs4's iat-mode 0/1/2.
    Go math/rand.Rand helper 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's common/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...
Read more

v1.0.0-alpha.3 — interactive TUI client + live-verified outbound Tor

Choose a tag to compare

@b4lol b4lol released this 03 Sep 07:15

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, tor feature): 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 in Zeroizing
    buffers. Hardening order mirrors serve (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-client was compiled with onion-service-service (hosting) but
    WITHOUT onion-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 tui now requires --keystore and takes --nickname
    (default umbra-tui) for its persistent inbound onion identity; the
    command and the tui module are gated behind the tor feature.
  • serve: the address wait and the accept loop are extracted as the
    shared wait_for_address / inbound_loop helpers (reused by the
    TUI); seeds are Arc-shared. NDJSON behaviour unchanged.
  • tor_send: the send core is extracted as send_over (bounded
    120 s connect, one PQXDH session) so the TUI reuses it; the CLI flow
    is unchanged.
  • peers: new list_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_live self-send PASSED on the real
    Tor network (2026-09); the alpha.2 just live-test identity-persistence
    result stands.

Umbra v1.0.0-alpha.2

Choose a tag to compare

@b4lol b4lol released this 02 Sep 07:25

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 of zero-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-test passed on the real network.

License: GPL-3.0-or-later

Umbra v1.0.0-alpha.1

Choose a tag to compare

@b4lol b4lol released this 31 Aug 20:13

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 suite

License: GPL-3.0-or-later