YUME Internet freedom by FixCraft
On this page

Development changelog

These entries record development changes, including withdrawn version plans and test builds. Implementation status describes current behavior and qualification.

[Unreleased 0.3.0-dev1]

Native TCP and accepted TCP/UNIX byte channels report socket read errors as transport failures instead of directional EOF. Socket failures in either direction close the channel before delivery and settle queued operations once. Later reads retain the terminal failure. The direct TCP readiness probe also preserves errors it consumes. Orderly EOF and immediate cancellation keep their existing behavior.

The session engine retains a closing stream until its required peer settlement arrives when an abort crosses both directional FINs. A valid crossed settlement therefore preserves the authenticated session for later streams.

The experimental C ABI adds yume_config_validate_client_credentials to validate actual client credential files and composite private/public key pairs before application publication. It returns the local composite fingerprint without starting an endpoint or authenticating a server.

Native session startup clears its stored one-shot callback before delivery. Repeated embedded client starts and stops release their run owners and polling descriptors on libc++ as well as libstdc++.

Stopping device-backed ABI startup now settles its session-keeper waiter without waiting for the connection deadline. The public start returns YUME_STATUS_CANCELLED after owned work drains and leaves the endpoint STOPPED.

The product version moved to 0.3.0-dev1. yume and yumed compose native YTP/1 with schema-1 configuration, and transport v2 has been removed. The YTP/1, relay channel, schema, ABI and evidence-profile versions are independent axes. docs/IMPLEMENTATION_STATUS.md is the current boundary for what the 0.3 foundation implements, tests, and still gates.

Added

  • Community translations. The shared catalog accepts human-reviewed translations of languages the team does not maintain, with each language’s translators or studio credited. A language is listed from 80 % coverage, falls back to English per message, and never translates the route-consent, expert-warning and trust texts marked maintainer_review. Every language, maintained or community, now credits who translated it; the maintained ones credit FixCraft Inc. The desktop follows the system language and lists the credits on its Credits page. Android’s picker shows Native and Community groups apart, each language with its translators’ names. Only maintained languages gate releases.

  • Simplified Chinese. Simplified Chinese (zh-Hans) is a maintained language in both apps, with every message translated, the safety texts included. A catalog language may name its script, and Chinese must, so zh-CN, zh-SG and zh-Hans locales read it and Traditional locales stay English until Traditional Chinese has its own translation. Android no longer machine-translates Chinese, since its on-device translation produces Simplified Chinese only.

  • One palette and mark across the apps. config/palette.json is the single colour source for the desktop GUI, the Android app and the website, and scripts/yume_palette.py generates each one’s colours from it and checks the contrast every theme must keep. The colours are soft plum and sakura on the mark’s hue, with light and dark designed separately: in light, warm-white cards on a faintly pink page and blossom main buttons with plum text, and in dark, a plum night with a soft pink accent, plus a true-black variant on Android. Status colours are natural sage, apricot and rose. The desktop follows the system’s light or dark setting by default, as Android does. The mark keeps its original shape everywhere, filled with the solid colours it shows over white, and the Android launcher shows it on a warm-white tile. Notifications use a white outline of the mark in the status bar. Long Android explanations sit behind an info button, and each card keeps one short visible line. Desktop controls were revised to match: soft button states, a filled slider track, a chevron drop-down, and hairline cards. On the website the layered “what wraps your data” figure has light, evenly spaced rings whose labels clear their corners, the hero cloud is built the same way in both themes, and the header no longer draws a line across the section navigation.

  • Interface design pass. The desktop moves explanation out of the page body: the import note and the tuning measurement’s conditions sit behind ? buttons, kit actions are named icon buttons, and the Credits page lists components and translators in compact groups. Tuning labels, notes and measurements are translated on the desktop as on Android. On Android, each tuning slider ends at its axis’s highest level instead of five, the language picker lists System default before the Native and Community groups and uses more of the screen, report buttons stack at large text sizes, and a phone on its side gets a navigation rail.

  • Desktop connection settings. Three coupled sliders use the shared tuning table, with measured conditions, plain values and the session’s negotiated server cap. A confirmed expert editor applies one validated field at a time through yume-setup. Applying to a running client waits for process exit before restarting; stopped clients only save.

  • Existing client tuning. yume-setup tune --config FILE applies a --preset or one expert --set KEY=JSON after schema validation, with atomic replacement. Restart the client to apply it. The shared preset table adds stealth-16, measurement conditions, display fields and a deterministic slider coupling rule. Stealth remains the default.

  • yumed serves on several event loops. endpoint.event_loops sets how many threads serve sessions, by default the CPUs the daemon may run on, at most 16. One loop accepts each connection and hands it, before TLS, to the loop serving the fewest sessions, where it stays for its whole life. The admission replay cache, the credentials a reload publishes, each identity’s max_sessions, the direct-route budgets, the egress rate and the resolver helper hold across loops, a module’s streams run on their session’s loop, and --status shows the sessions on each loop. A cluster member or a server with a packet adapter keeps one loop.
  • Explanatory diagrams. Device bridge, circuits, cluster links, directional key rotation, GUI lifecycle, module streams, relay library, sealed kits and trust boundaries share one JSON source across web, Markdown and manuals.
  • libc++ CI gate. The native application, module libraries and shared ABI are built and tested with Clang and libc++, which Android uses. Matching LLVM archive tools preserve LTO symbols despite older runner-image plugins. Retry-timer allocation fixtures intercept the POSIX path Asio can select with libc++. The TCP provider’s DNS round-trip fixture uses a named IPv4 loopback answer through the resolver helper, and timeout diagnostics name the test and wait.
  • Device bridge in the C ABI. yume_endpoint_set_device attaches a TUN descriptor to a client endpoint, which then carries what enters the device on the kit’s ordinary services: one byte-stream OPEN per TCP connection and one packet OPEN per UDP destination, as the SOCKS5 adapter does for a local application. The host kernel terminates each TCP connection, so no user-space TCP stack and no local proxy hop is involved, and the server sees the same services, limits and OPENs as from a yume client of the same kit. Lookups to one resolver share a stream. Such an endpoint keeps its session up with the client’s backoff, resets connections made while no session is active, and yume_endpoint_retry_now starts a waiting attempt for an application that learns the network is back. A path that stops answering ends the session at YTP/1’s 30 s rotation acknowledgement deadline. yume_abi_device_integration runs it between two network namespaces: 300 parallel connections, UDP, shared lookups, refusals, session loss, a silent path, reconnect and restart.
  • Endpoint status, messages and sealed kits in the C ABI. yume_endpoint_get_status returns one snapshot of the session, its traffic, the latest failure and the wait before the next attempt. yume_endpoint_read_message reads the endpoint’s message feed, the lines yume --messages shows. yume_kit_open opens a kit yume --seal-kit sealed, so an application imports one file and a code instead of copying keys. A stop now cancels a client start in progress on another thread.
  • Identity route reservations. Direct TCP and UDP route capacity now reserves pending and active slots per authenticated identity across its sessions. Failed, canceled and closed opens release the reservation; a circuit exit accounts its authenticated previous hop, not the hidden client.
  • Circuit streams share a slow path evenly. When a circuit’s link or the client’s session was the bottleneck, one stream could take every cell slot: an exit’s destination read that completes at once read again before its sibling streams, and the client gave freed slots to its lowest stream. Both now serve waiting streams in turn, so parallel downloads and uploads through a circuit share its path evenly again.
  • Android build of the library. tools/build_android_shared_abi.sh builds libyume.so for the Android ABIs with the pinned NDK, the patched OpenSSL built static for each ABI and the vcpkg manifest’s dependencies. Verified builds require both a signed HEAD and a clean native source tree. Unsigned or modified sources require the explicit test-only --unverified-source option; Android records the native commit and dirty state in its build identity. The sources also build with Boost 1.90, whose timers lost the error-code form of cancel.
  • Configuration reference. docs/CONFIGURATION.md lists every schema-1 key with its role, bounds and meaning, and the tuning presets, which moved there from the development guide. tests/test_config_reference.py requires every key the parser accepts to be documented there and used by one of four complete documents under tests/fixtures/config, and yume_config_v1_closure puts an unknown key into every object of those documents and requires a failure at that object. tests/test_doc_paths.py requires every repository path the documents name to exist.
  • Contract pages first. CONTRIBUTING requires a recorded owner requirement and the interface’s contract page before a feature’s code. tests/test_contract_pages.py rejects a control request, control error code or ABI export that its page does not state, and the ABI page now names every exported function.
  • Guardrails that fail the build or CI. cmake/YumeLayering.cmake is an allowlist: every directory under src/ declares the layers its production sources may include, a new directory fails until it is declared, and every static or shared library must pin its direct link list. The CI job reachability builds the full graph at -O0 with one section per function and --gc-sections, and scripts/check_reachability.py then requires every first-party function to survive in a shipped program or to be retained in config/reachability.json with its reason. scripts/check_repository_hygiene.py checks whitespace in every committed file and each pushed commit’s format and signature. tests/test_wire_labels.py requires every wire, KDF and AAD label in the source to appear in its contract page and the reverse. The documentation drift checks and the documentation tooling tests are CTests as well as CI steps.
  • Sequence diagrams. The documentation compiler draws messages between two to four parties from one JSON file, as an animated SVG in both themes and as ASCII for manuals and Markdown. YUME explained now shows where traffic goes, how a connection opens and what wraps the data on the wire, and the architecture shows the direct route.
  • Relay channel library. The relay v2 end-to-end channel between two users (handshake, sealed records and hybrid ratchet) builds apart from transport v2 as yume_module_relay when YUME_BUILD_BASEFWX_MODULES is on. Its labels and wire format are unchanged, which tests check against known answers from the transport-v2 code and against an independent Python reference that pins every handshake and ratchet label. It also holds the encrypted chat history, the relay secret, peer trust, and the invite and directory records with their policies. Peer fingerprints changed from the transport-v2 identity domain to the YTP/1 one, so a peer’s fingerprint is the one yume-setup prints for its key. yume_module_files adds the relay file transfer source and receiver. They are the base of the planned chat and file modules.
  • Application codec library. The application codec core (the HTTP envelope, backend dialing rules and the Monero RPC descriptor) builds apart from transport v2 as yume_module_codecs for a planned codec module.
  • SOCKS5 upstream provider. providers/socks5_upstream.* reaches a server through a SOCKS5 proxy over any byte-channel provider, as transport v2’s client dialing did. It offers only the method its configuration implies, takes credentials that wipe themselves, and reads nothing past the proxy’s reply. A client configuration’s endpoint.socks5_proxy names the proxy’s numeric address and port and an optional protected credentials file with a username line and a password line, and a managed TUN excludes the proxy’s route from the tunnel.
  • Loopback cover fetch. providers/loopback_http_fetch.* fetches GET and HEAD from a loopback web server within fixed bounds, the base of a reverse-proxy cover. Unlike transport v2’s client, it refuses a backend that closes before a complete response. cover.reverse_proxy still fails start until the FrontDoor uses it.
  • Egress lists. Server direct_tcp and direct_udp destinations can add lists of JSON or VPN provider database files and a MaxMind country_database. The most specific entry decides and a deny wins a tie. Lists refuse more but never permit more than public and networks. This brings transport v2’s egress filter to the native daemon without archive unpacking or the compact country format, and it fixes v2’s reading of 28-bit MaxMind records.
  • Outer-carrier evidence. yume --outer-carrier-evidence PATH writes a payload-free report on its first connection’s HTTP/2 and WebSocket carrier when the run ends: frame types and sizes, pinned or redacted header values, timing, the quiet interval before the close and the close itself. It is the YUME arm’s input to the classifier comparison with the captured browser, ported from transport v2 without its benchmark mode.
  • Native YUME capture arm. tools/cover-node/capture_yume151_runs.sh captures native yume and yumed from one release bundle, and scripts/yume_carrier_workload.py runs the frozen workload through yume’s SOCKS5 port where the browser arm runs a page script. The classifier-input validator takes the YUME arm’s application volume from the driver’s measured result and no longer requires a GOAWAY that the browser capture never recorded. The TLS relay now also records each record’s time, type and length, and scripts/yume_classifier_features.py turns that timeline into the classifier gate’s feature document.
  • Shell completion. yume --completion bash and yumed --completion bash print a bash completion script for the program’s options, their fixed values and file arguments. scripts/yume_cli.py generates it from the manual sources with the help text, so the two cannot drift apart.
  • Control socket and yume --status. A client configuration may name control.socket, a UNIX socket where the running client serves its state to processes of its own user: the connection state, the server and its verified identity, session counts, traffic, local listeners and the last failure. yume --config PATH --status prints it. The socket follows the owner-only rules of local UNIX listeners, serves at most four connections, and answers one JSON request line with one JSON line (control protocol 1). That page is the GUI’s contract: fields and requests are only added, every status reply lists the requests its program takes, and an error names a stable code beside its text. yume-doctor checks the key, and the embedding ABI refuses a configuration that names one.
  • Cluster links. Servers of one operator form a cluster from a list the operator signs with a composite Ed25519 and ML-DSA-87 key (cluster 1). A server’s cluster section names the list, its signature, the operator key, a peer store with a PSK for each direction of each pair and a state file. yumed refuses a list that fails its signature, has expired or does not name it, keeps an authenticated YTP/1 link to every peer and closes them when the list expires. SIGHUP reloads the list and peer store and keeps every link whose inputs did not change. The server saves the highest serial it has loaded in the state file and refuses a list with a lower one, after a restart too. yume-setup cluster-init, cluster-add, cluster-remove and cluster-sign provision it, yume-doctor checks the section, and the embedding ABI refuses it. The links carry circuits, below.
  • yumed --status. control.socket is now available to the daemon, which reports its listeners, client sessions, cluster links and circuits there.
  • Circuit 1 codec and crypto. Circuit 1 specifies the circuits that will carry a client’s traffic through two or three cluster servers: fixed-size cells, a hybrid X25519 and ML-KEM-1024 handshake with each hop signed by its composite identity, one AES-256-GCM layer per hop and relay messages. The codec (circuit/) and the constructions (providers/circuit_crypto.*) are built, with vectors from an independent Python generator, real-key handshake tests and a fuzzer.
  • Circuits on cluster servers. Every cluster member serves yume.circuit: it answers each hop’s handshake, extends circuits over its links, relays cells, and, as an exit named by cluster.exit, carries their streams through a direct_tcp service’s destination policy. It serves the operator-signed routes view on yume.routes: server names, identities, exit marks and network tags, without addresses. The cluster section gains routes, routes_signature and exit, names starting with yume are reserved for these services, and a client needs the yume.circuit grant. Circuits are bounded per client, per link and per node, their streams’ windows are capped at an eighth of the session budget, and yumed --status counts circuits per link and refusals by bound. yume-setup cluster-add --exit, add-client --circuits and the routes view in cluster-sign provision it.
  • Circuits for clients. A client configuration’s circuits section (hops, min_hops, plain_http and the operator key, routes view and state files) sends every SOCKS5 CONNECT and forward to a destination through circuits of two or three servers that yume chooses from the routes view: distinct nodes, an exit last and no shared network tag, with measured latency, a spare and rotation. UDP ASSOCIATE and packet adapters are refused with circuits, and plain HTTP is refused unless allowed. A stream opens as soon as its BEGIN is on its way, so its first data follows without a circuit round trip, and a destination the exit refuses or cannot reach then ends the SOCKS5 connection. The client builds a circuit when its session starts. The largest cell of each direction, 16311 bytes forward and 16315 backward, and its framing fill exactly one TLS record. A client’s stream windows grow from 256 KiB to 1 MiB as its reader drains them. The client never shortens a route on its own: it proposes one, which yume --accept-route ID accepts over the control socket, or circuits.min_hops approves in advance. yume --status shows routes and proposals, and a proposal names its cost as a code beside the English text, yume-setup add-client --circuits writes the section and kit copies, and the C ABI refuses the section. scripts/yume_circuit_wan.py measures the direct session and circuits of two and three hops across emulated links, under any tuning preset, and serves them to scripts/yume_circuit_cable.py on another host. scripts/yume_circuit_capture.py captures them and describes the client session against a browser capture.
  • Sealed kits. yume --seal-kit DIR --output FILE seals a client kit into one file and prints a 25-character code, and yume --import-kit FILE --into DIR opens it on the client’s device with that code. The file is Argon2id and AES-256-GCM from the core’s OpenSSL 3.5, with no plaintext header and fixed parameters (sealed kit 1). Only a client configuration is sealed, and the import writes a new owner-only directory. yume-setup names the command after it writes a client. The format replaces the BaseFWX .yss container library that transport v2 kept client settings in, and that library is removed.
  • Control requests for the desktop GUI. yume --control-socket PATH names the control socket for one run, since an imported kit carries no socket path, and --status and --accept-route take it too. Both programs answer messages, the latest 256 lines they printed, numbered, paged within 63 KiB and tagged with an instance that changes on restart. yume answers stop, then stops as on SIGTERM. Its status gains posture: the transport, suite, evidence profile, security provider and crypto library, the tuning limits in use and, while connected, the session’s negotiated key epoch. All are additive under control protocol 1’s rules.
  • Desktop GUI. yume-gui (YUME_BUILD_GUI=ON, Qt 6.4 or later) is a new Qt Quick window, an unprivileged client of the control socket that links no YUME library. It imports sealed kits through yume --import-kit, starts a kit’s yume as a detached process that outlives the window, stops it with the stop request and waits for the process to end, and shows the connection, rates, listeners, circuits, printed lines and the fixed posture with the tuning preset it matches. A tray icon shows the selected kit’s state and connects or disconnects it. A shorter route is accepted only from a review of that exact proposal, which states what the route gives up in the interface language. Headless actions, a page capture, keyboard and right-to-left tests and a live cycle against a real daemon cover it (desktop GUI). Stopped kits can be renamed without replacing an existing kit; the GUI checks both control sockets before moving the directory and retains the selection. A client still draining after its socket closes remains active even past the stop warning. It is not packaged.
  • Modules. A server module adapter runs a program for one stream service. yumed gives it a listening UNIX socket as descriptor 3, sends each authorized stream as a connection that starts with the client’s identity, and restarts the program with backoff when it exits. The program runs without capabilities under no_new_privs, and the kernel kills it if the daemon dies. It still runs as the daemon’s user and can read the daemon’s files, so only trusted programs belong there. yume-doctor checks the adapter, and yume-echo-module is an example. See modules.
  • Local forwards. A client forward adapter listens on a loopback TCP port or a UNIX socket and turns each connection into a stream on its service, optionally to a fixed destination that the server’s direct_tcp policy checks. This replaces transport v2’s -L forwards. Their TCP listeners were not limited to loopback, while native forwards are. A UNIX socket only accepts the client’s own user. UDP forwards are not ported yet.
  • Per-client speed limits. A server’s limits.max_egress_mbps caps the stream payload the daemon relays, and clients share it by the weight in their authorized-keys entries. Every served stream is paced in both directions, whatever its service. A reload changes a weight for open streams at their next transfer. yume-setup sets both and yume-doctor checks them. This replaces transport v2’s egress_mbps limiter, which gave two busy clients half the configured rate between them and ignored their weights.
  • Bounded name-lookup shutdown. Client hosts and destination names now resolve in a separate helper process instead of Boost.Asio’s single in-process resolver thread. Cancellation settles immediately, one slow name no longer delays other lookups, and closing a runtime kills and reaps the helper, so SIGTERM or SDK stop no longer waits for a stalled getaddrinfo. The helper holds no inherited connections and drops every capability. yume and yumed serve as their own helper. The SDK installs yume-resolver, which embedding hosts pass as yume_runtime_options.resolver_program. Without it, a client that dials a name fails closed at start.
  • Per-identity session limits. An authorized-keys entry may set max_sessions. A new session beyond it replaces that identity’s oldest session, so a reconnecting client displaces its own half-open session instead of being refused. Without the field the daemon’s capacity is the only bound, as before. Doctor validates the field against the daemon’s bound.
  • Credential reload. yumed reloads its key stores and server keys on SIGHUP, and the Debian unit wires systemctl reload, validating first. Removed identities lose their sessions at once, established sessions apply changed grants to their next stream, and an invalid store keeps the previous credentials. TLS and admission key changes still need a restart.
  • More client bundles. yume-setup add-client issues another client for an existing server tree and extends its authorized-keys store in one all-or-nothing step that keeps the store’s file owner. remove-client revokes one, and a reload ends its sessions.
  • Client status for embedding. NativeClientRuntime::status() reports the connection state, last failure, verified server fingerprint, attempt counts and cumulative traffic from any thread, with an optional state-change callback. SessionEngine::traffic() supplies per-session byte counts.
  • Native operations guide. docs/OPERATIONS.md now covers the native programs. The transport-v2 operator material moved to docs/TRANSPORT_V2_OPERATIONS.md under its reference program names. Release verification, which describes the native release, stays on the native page. The duplicate website build notes were dropped in favor of the documentation source guide.
  • Native acceptance tooling. Native-only builds register setup, doctor and packaging regressions. Bounded libFuzzer targets cover YTP framing, AUTH and schema-1 configuration; numeric JSON overflow now reports a typed validation error. Optional isolated network tests exercise the actual systemd-resolved service and competing streams under kernel-induced latency and backpressure.
  • Cryptographic construction vectors. A public synthetic corpus and an independent generator cover transcripts, key schedules, record protection and directional rekey calculations. The internal crypto module exposes those deterministic constructions to tests without exposing them through the SDK.
  • Native packaging migration. Release archives and Debian definitions use schema-1 tools and configuration. GUI and helper build lanes explicitly select the retained reference graph. The Debian service waits for operator-provided credentials and validates configuration before starting; package installation does not enable or start it. Release qualification remains open.
  • Native terminal failure cleanup. The CLI reserves its runner-exception stop task before execution. Client reconnect and SOCKS accept retry failures that cannot recover close the runtime and report a typed terminal status, allowing the process to drain and exit. Ordinary failures keep backoff.
  • TCP allocation-failure completion. Pending read/write construction keeps callback ownership with the caller until storage exists. Allocation refusal settles each callback once and returns queue capacity for subsequent traffic.
  • Shared allocation test hooks. The remaining ten private new/delete replacements use the shared out-of-line helper, including optional aligned provider allocation interception and secret-buffer quarantine. Fault policies and wipe-before-release observations remain owned by their tests.

  • Development standalone runtimes. With every native provider enabled the default build installs yumed and yume. The daemon serves configured direct TCP/UDP adapters within their destinations. The client keeps one authenticated session with bounded reconnect and runs loopback SOCKS5 listeners. CONNECT streams join the existing direct-route bridge. Payload sent before the CONNECT reply stays in the socket until the remote endpoint accepts the route; the bridge preserves it and the client’s half-close. An OPEN deadline returns SOCKS5 0x06 (TTL expired); other cancellation keeps its existing status, and late acceptance cannot revive an expired request. yume_native_runtime_test carries a payload through SOCKS5, checks refusals and requires clean SIGTERM exits. Managed Linux packet/TUN adapters and experimental SDK streams, packets and routes are also implemented; arbitrary standalone named-service handlers remain unfinished.
  • SOCKS5 UDP ASSOCIATE. A client socks5 adapter that names a packet udp_service relays UDP through a loopback socket tied to the client’s TCP connection. Each destination opens its own authenticated packet stream, so identity grants and direct_udp destinations apply as they do to CONNECT. Backlogs use the transport-v2 UDP budget of 64 datagrams and 1 MiB, now owned by common/udp_queue_budget.hpp, and destinations are bounded, expire when idle and wait before a refused OPEN is repeated. The schema-1 parser, doctor and setup kit carry the new key. Endpoint and process tests cover traffic, refusals, source locking, the budget and relay closure.
  • Runnable development kit. Installed yume-setup and yume-doctor use schema 1. The generated kit starts native yumed and yume with SOCKS TCP/UDP. Managed TUN addresses, routes and DNS are explicit operator choices.
  • Client dial address. Schema-1 clients may set endpoint.connect_address to dial a numeric address while TLS and admission still authenticate host.
  • Evaluation tooling. scripts/ensure-ndpi.sh builds pinned nDPI 6.0, or newer sources such as --ref dev recorded by commit. scripts/yume_ndpi_smoke.py records nDPI output for one rootless loopback session with Ethernet-sized frames. scripts/yume_ethernet_smoke.py compares repeated untunnelled and tunnelled transfers over a direct link after a preflight that fails when the link is unavailable. It can capture the tunnel on the remote interface, with offloads off for wire-sized frames, and run nDPI on that capture. It names the remote host with a required --remote-host. scripts/yume_wan_emulation.py measures sessions across emulated wide-area links without root: netem delay, loss and rate limits in a router namespace between client and server, with session setup time, direct and tunnelled download rates and small request round trips per condition. Their results are observations, not classifier verdicts or benchmarks.
  • Configured egress destinations. Server direct_tcp and direct_udp adapters require destinations: public for globally reachable unicast addresses and networks for canonical CIDR prefixes. NativeEgressPolicy enforces them before DNS or socket creation, and for every resolved address when the route provider uses it. Unspecified, multicast and reserved addresses are never permitted, IPv4-mapped IPv6 is evaluated as IPv4, and public space excludes private, shared, loopback, link-local, documentation, benchmarking, 6to4, Teredo and NAT64 prefixes. The native endpoint no longer requires an application route callback, which can now only refuse more. Setup kits declare public destinations explicitly, and the native parser and yume-doctor-ytp1 share network test vectors. Direct adapters without destinations no longer parse.
  • Endpoint-owned native accepts. NativeEndpoint can keep a sized number of server starts pending on every listener, re-arming after each settlement and retrying refused or immediately failed starts after a delay. The schema-1 ABI backend uses it instead of its own loop. A listener that stops accepting, or a retry that cannot be scheduled while its listener has nothing pending, now closes the endpoint and is reported once. Previously the backend could leave such a listener idle without a report, or keep retrying a closed listener every 100 ms. Later ABI accepts report YUME_STATUS_INVALID_STATE until restart. Deterministic pacing tests and socket tests cover refusal, capacity recovery, re-entrant completion, two listeners and an injected OS accept failure.
  • Native YTP endpoint composition accepts schema-1 direct TCP/UDP declarations with explicit destination policies. Route dispatch supplies the engine’s selected provider, removing independent selection by the built-in handler. Refusal, failed-listener rollback, packet boundaries and retained receive credit have focused native regressions. Standalone CLI/daemon and public-ABI destination adapters remain unfinished.
  • Shared carrier build ownership. Native YTP and the remaining transport-v2 callers link one H2/WebSocket/wire-profile/observer library and one bounded-file library. Existing codec and file implementations are retained. The H2 library propagates its diagnostics declarations to callers; Release and MinSizeRel still compile diagnostics out. Existing WebSocket and bounded-file regressions now also run in the native-only build, with assertions enabled in Release.
  • Schema-1 embedding backend. With the shared ABI and every native YTP/1 provider enabled, yume_embed_ytp1 runs NativeEndpoint behind the blocking C ABI on its own execution thread. Schema-1 clients open and servers accept authenticated named byte streams with the composite peer identity. A server holds each authorized OPEN until the application accepts it. Undeclared, unregistered and unauthorized services are refused, clients cannot register services, and declared adapters or reverse-proxy cover fail start. The backend links no transport-v2 target or BaseFWX. Such a library’s compatibility manifest and build info name openssl35.ytp1-security and the loaded OpenSSL release, such as openssl-3.5.7, instead of unwired. yume_abi_ytp1_stream_integration provisions a kit and drives both roles. Packets, routed OPEN and standalone runtimes remain unfinished, and the backend has no production qualification.
  • Typed end of stream. Engine stream reads report the peer’s authenticated FIN as EndOfStream, separate from session end, abort and local close. The direct-route adapter half-closes a destination only on that status, and the ABI never reports a lost session as EOF, including after an earlier FIN. A thread-safe responder query keeps adapter-retained records and EOF subject to later termination.
  • YTP embedding deadlines and drain. Accept transfers local ownership without waiting for runner dispatch. Write shutdown bounds that dispatch and can be retried while its single FIN remains pending. Escaped runner exceptions stop the endpoint and drain accepted work; drain completion is published only after execution returns. Paused-executor regressions cover those cases, all-or-none writes and partial-read credit ownership.
  • Restartable native listener. The FrontDoor enables address reuse on POSIX, so a stopped server can rebind while earlier connections sit in TIME_WAIT.
  • Typed native listener failures. Socket setup distinguishes denied permission, occupied or unavailable addresses, resource exhaustion and unclassified failures through the engine and C ABI. Failed startup releases its socket; the ABI requires stop to settle FAILED before retrying, without disturbing another live listener. Unexpected exceptions no longer masquerade as memory exhaustion.
  • Server authentication deadline. Native server startup begins its bounded session-creation/AUTH phase at validated carrier promotion, so an idle accept cannot consume a late-arriving client’s budget. FrontDoor ingress bounds and client end-to-end startup deadlines remain enforced. Readiness refusal or allocation failure closes the promoted carrier before session security starts.
  • Resolved destination authorization. The native route provider requires policy for every selected numeric address before socket creation, including DNS answers and IPv4-mapped IPv6. Refusal, policy exceptions and reentrant cancellation fail the OPEN without connecting. Applications retain ownership of destination rules; standalone adapter composition remains unfinished.
  • Allocation-failure test pairing. Nothrow scalar/array allocations use the same test allocator as throwing forms and matching deallocation. TLS tests exercise observers, countdown and sustained failure across those forms; production TLS allocation and sanitizer diagnostics are unchanged.
  • Release fixture validation. Native TLS test credentials are generated and checked even when release builds define NDEBUG.
  • Native YTP session composition. Protected schema-1 credential loading and per-identity service policy now feed a shared native endpoint, with bounded admission/AUTH starts, owned session lifetimes and explicit executor drain. It directly composes the existing TCP/TLS/H2, FrontDoor and OpenSSL providers without BaseFWX. Standalone CLI/daemon adapters and the YTP public ABI remain unfinished. Explicit destination handlers can share the native endpoint and its route provider; TCP/UDP egress uses the same execution owner and reserved cancellation as ingress.
  • Stream acceptance and settlement. OPEN success waits for peer service acceptance; routed handlers wait for destination establishment. Ordered close acknowledgement bounds retained stream state across crossed data, credit and cancellation. This intentionally corrects unfinished YTP/1 wire semantics; transport-v2, crypto domains and independent version axes are unchanged.
  • Native startup and teardown. Setup supplies complete cover pages and profile assets; static loading snapshots bounded routes and startup rejects missing priming files. IPv6 listeners use IPv6-only sockets. Bootstrap and engine teardown retain completions under selected allocation failures, and repeated terminal provider/endpoint cleanup is inert after final drain.

  • TLS/H2 failure settlement. H2 close, cancellation and credit return use reserved control tasks shared with the native TCP executor. TLS/H2 retain completion ownership through construction, registration and diagnostic allocation failures. The native front door supplies both dispatch paths from its existing context; provider tests exercise one-shot and sustained allocation denial. This changes internal C++ provider seams, not the wire or C ABI.
  • Native YTP/1 FrontDoor candidate. An opt-in listener adopts TCP through the shared channel owner and serves an operator-supplied immutable static site. Ordinary TLS 1.2/1.3 and HTTP/1.1/H2 cover remain separate from strict TLS 1.3/H2 promotion. Actual SNI/exporter binding, shared replay reservations and a connection-lifetime guard gate admission; rejected and saturated admission uses the configured cover. Promotion transfers the existing TLS and H2 state after output/timer drain and preserves cover handling and owner lifetime. Standalone YTP session and public-library backend wiring remain unfinished; this adds no production or stealth-advantage claim.
  • TLS duplex and callback ownership. Independent underlying read/write state prevents duplicate reads during concurrent TLS output. The TLS dispatcher retains its state when an immediate callback releases the final channel handle.
  • H2 callback allocation failures. nghttp2 callbacks contain allocation failures and retain a nonallocating failure marker, including when diagnostic storage is unavailable. Header, DATA, close and client-priming regressions exercise one-shot and sustained allocation failure in the shared codec.

  • Explicit native TCP execution ownership. The experimental provider and accepted-channel owner share one caller-owned execution context. Reserved control dispatch replaces allocating strand cleanup, initiation stays on the declared context, and shutdown drains callbacks before the runner ends. Partial writes are initiated explicitly so a later allocation failure is handled by the provider. This changes the source-level provider factories; it does not introduce a native YTP endpoint or alter the public C ABI.

  • YTP ingress components. The TLS client shares browser-profile configuration with transport v2 and still requires negotiated TLS 1.3/H2. Client H2 admission binds a fresh proof to the live TLS connection with its own domain. Accepted TCP sockets reuse the bounded client-channel implementation. The separate native FrontDoor composes ingress and cover; the YTP session runtime and ABI endpoint remain unfinished. These components establish no stealth advantage.
  • YTP setup TLS certificates. Development kits generate P-256 certificates with SHA-256 signatures to match the browser TLS profile. Doctor separates TLS leaf and trust checks from the unchanged composite YTP identity rules.
  • YTP TLS failure settlement. Closing the underlying channel during cancellation preserves the original failure. Terminal state transitions deliver pending completions without requiring another I/O event; late transport callbacks cannot replace the terminal status or feed more TLS data.
  • Shared documentation sources. Topic .doc files under docs/src/en_US/ own text, web titles and catalog metadata. One sync renders Markdown, manuals, web pages, diagram assets, CLI help and completion. Locale paths prepare for translations; only en_US is active. See the authoring guide.
  • YTP/1 replacement foundation. Dependency-pure engine and protocol kernel, numeric config schema 1, the role-neutral C ABI v1 candidate with a transport-v2 backend that carries authenticated named byte streams, and the opt-in TLS 1.3, HTTP/2 duplex carrier, hybrid session-security, and Asio byte-channel provider candidates. None of it is a live YTP/1 endpoint.
  • Own liboqs dependency edge. YUME’s ML-KEM key generation in core/security/inner_crypto.cpp links liboqs through YUME’s own CMake discovery (yume_liboqs, YUME_HAS_OQS) instead of inheriting it from the BaseFWX build. YUME_REQUIRE_OQS fails configuration when liboqs is missing.
  • libFuzzer harnesses for the untrusted parsers. YUME_BUILD_FUZZERS (Clang-only, never installed) builds yume_fuzz_h2_probe_decoder, yume_fuzz_client_config, and yume_fuzz_server_config. Each asserts that its parser rejects malformed input rather than throwing, because the H2 probe decoder runs on an Asio worker where an escaping exception does not reach the session. tests/fuzz/run_fuzzers.sh seeds the corpora, enforces a per-target budget, and fails on any artifact, so CI, a build host and a local run are the same invocation. docs/DIAGNOSTICS.md records it.
  • Fuzz instrumentation reaches parser libraries. The fuzz profile selects ASan+UBSan and coverage instrumentation for source-built dependencies as well as the harnesses. Instrumenting only the entry point left out-of-line parser bodies without coverage feedback or sanitizer checks. Other sanitizer selections require a separate build directory. Optimized sanitizer builds retain frame pointers. The ratchet no longer stores an unused copy of its constructor role; directional setup still uses the original parameter.
  • ThreadSanitizer and fuzzing are CI gates. A thread-sanitize job builds with YUME_SANITIZE=thread and runs the full suite, and a fuzz job builds the Clang harnesses and runs each briefly against the seeded corpus. Neither had ever run automatically, so a data race or a parser crash could reach main with nothing objecting. thread-sanitize is the one lane not on ubuntu-22.04: it ships nothing, and that image’s libtsan 11 reports a false double lock against mutexes in recycled heap storage.

  • Fuzzing reaches what a client sends before it authenticates. yume_fuzz_h2_admission covers the admission path and the :authority check, yume_fuzz_cover_site the cover site’s answer to any method and target, and yume_fuzz_websocket the WebSocket framing of an admitted stream in either role, fed in pieces. Each checks a property beside crashes: an accepted path builds back to the same text, only GET and HEAD of the site’s own files succeed, and the codec never hands back more bytes than it was fed. CI’s fuzz job runs all seven harnesses.

Changed

  • Debian service lifecycle. Package configuration creates the locked daemon account before its state directories. Upgrades restart a running daemon and leave a stopped daemon stopped. Removal stops the daemon and loaded module instances. Each module instance has a separate dynamic user. Both packages require the exact patched OpenSSL runtime version used for their build, so stock cannot replace it while satisfying their dependencies.

  • Cover method refusals match the reference. The static cover answers methods other than GET and HEAD with Node’s fixed 405 headers and Method Not Allowed\n body. Authenticated H2 admission still precedes static-site handling, and GET/HEAD route and not-found behavior is unchanged.

  • Modules run under systemd as users of their own. A module adapter now names the module’s listening socket instead of a program and its arguments. yumed checks at startup and with --validate that only its own user and root can reach or replace the socket, then connects once per stream with the same identity header. The Debian package ships yume-module@.socket and yume-module@.service, which run each module as a dynamic user without network, capabilities or access to the daemon’s files. The in-daemon launcher, its restart backoff and the yume-module entry are removed, so modules no longer run as the daemon’s user.

  • H2-only cover. The public front door now requires ALPN h2 over TLS 1.2 or 1.3. HTTP/1.1-only offers fail negotiation. A connection without ALPN gets the Node reference’s fixed 239-byte 403 refusal and is closed without serving the site. The HTTP/1.1 site handler is retired. YTP promotion still requires TLS 1.3 and authenticated admission.

  • One switch for developer diagnostics. -DYUME_DEV_DIAGNOSTICS=ON compiles the timing helpers and the new key-rotation benchmark probes for the whole build, and nothing else does: the timing helpers no longer follow the Debug and RelWithDebInfo build types. With the probes, a build records why each rotation started, the provider’s rekey steps and the records that waited, when YUME_BENCH_PROBE_FILE names a file. ./ezbuild.sh --dev and CI’s sanitizer jobs turn the option on.
  • Native setup and doctor. yume-setup and yume-doctor are C++ programs built beside yume and yumed, with the same actions, options, outputs and file formats as the Python tools they replace. Keys, certificates and signatures come from the pinned OpenSSL through the readers yume and yumed use, and every key is read back by them before it is written, so a kit is made and checked with no Python or openssl command: stock Ubuntu 24.04’s OpenSSL 3.0 cannot make ML-DSA keys. yume-doctor parses with the configuration parser itself, so it reports the parser’s own wording, and the tuning presets come from config/tuning_presets.json as the GUI’s do. Both have manuals and help generated from them, take --option=value, and print the help with a usage error, which exits with 2. The release archive carries the programs, and its quick start drops Python and the OpenSSL command.
  • The cover tools’ tests run on the pinned Node. They model the Node 24.18 cover server of the evidence profile, and ran on whatever Node was installed. scripts/ensure-node.sh downloads that release and checks its SHA-256, test builds and CI provision it, and on any other Node the tests fail with the reason. The resolver test takes its missing name from its test helper, since a host without DNS answers a missing name with a temporary failure.
  • BaseFWX pin 471f5fd. The module libraries build on BaseFWX with the documentation tooling’s sequence type, restyled figures and lighter layered figures, and a B512 stream encoder that no longer trips a false GCC 13 -Wstringop-overflow warning in CI’s Release build. The pin also takes the six BaseFWX commits after 8a8ab66.
  • Reload reaches armed accepts. yumed keeps up to four accepts armed on each listener, and each held the credentials loaded before a SIGHUP. A client added by add-client, or a changed PSK, failed its first attempts until those accepts were used up, which cost the client its backoff. Every session now takes its credentials from the endpoint’s current factory when AUTH starts, so the reload reaches it on the first attempt.
  • Receive-window autotuning. A session returns consumed credit in one update per half window instead of two records per received record. A stream window grows from 256 KiB while the application drains it within two round trips, up to two thirds of limits.max_queued_bytes, and the connection window up to the whole budget. A peer’s grants are checked against the protocol’s 2^30 bound rather than the local window, so a client whose budget is smaller than its server’s no longer fails the session on the server’s first grant.
  • Fewer round trips on far servers. yumed now sets TCP_NODELAY on the connections it accepts, as yume already did on its own. Nagle had held small records such as AUTH, credit and REKEY_INIT behind unacknowledged data for a round trip. The endpoint’s rekey timer also rotates a used epoch without waiting for a send, so a request after an idle moment no longer waits a round trip on each side for REKEY_ACK.
  • Tuning presets. yume-setup init --preset selects stealth, the default, balanced, fast or max, which set the session budget, the epoch size and the credit returns on both sides of a kit. add-client copies the server’s settings and yume-doctor names the preset a configuration matches. config/tuning_presets.json lists the presets with a speed, security and stealth level each, for a later interface.
  • Client run settings. yume accepts --connect, --socks-address and --socks-port. Each replaces one configuration key for one run before the configuration is validated, so the value meets the file’s checks and an error names its key. None can change the wire image, authentication, encryption, admission, destination policy or leak protection, and yumed accepts none. --validate prints every key a run setting can replace, with its value and where it came from. The yumed(8) manual also printed the client’s SIGNALS section beside its own and now has only its own.
  • Packet channels return credit by whole packets. A packet cannot be split to spend the last of a stream’s credit, so a sender whose packets do not divide the receive window is left short of one packet while the receiver waits for half the window. It then got one return, about half a window, per round trip. On a packet channel the engine now returns at half of the whole packets that fit, by the largest packet the peer has sent. With circuit cells of 16311 and 16315 bytes, four streams through one circuit moved 52 Mbit/s at 40 ms per hop without this and 98 with it.
  • Key rotation without a pause. A sender sent nothing protected between REKEY_INIT and REKEY_ACK, so every rotation cost a round trip, about 29 % of the time at 200 ms with 500 ms epochs, and a request that met a rotation took an extra round trip. A rotation now starts once an epoch has used half its payload, half its records or half its 500 ms send lifetime, and the sender keeps using the old epoch, within its limits, until the ACK arrives. The receiver keeps the old inbound root after accepting INIT and wipes it at the peer’s first record of the new epoch. Epoch limits are unchanged, a direction holds at most one old root, and an INIT in the epoch it replaces now ends the session.
  • The carrier closes like the captured browser. A stopping yume used to drop its TLS connection. Its carrier now sends one HTTP/2 PING when it has read nothing for more than ten seconds, then a masked WebSocket CLOSE of the captured 18 bytes. The server echoes it and ends the stream, as the captured Node server did, and the client ends the connection when the echo arrives or after three seconds. The same PING rule, Chromium’s, applies to any write after such an idle. Neither side sends GOAWAY, which the capture does not show and Chrome sends only on errors. The server used to close the connection before its echo was written. The endpoint no longer cancels the client’s TCP I/O on close, so the CLOSE is not cut off.
  • Idle keys rotate only on request. The endpoint’s timer rotated every key epoch that had carried records once it was 250 ms old, so each burst of traffic ended with a rekey exchange of about 1.7 KB each way. The matched stealth capture shows the browser session sends nothing there. The timer now runs only with the new limits.idle_epoch_rotation, which the fast and max presets set. Without it the next send rotates the epoch and a request after more than half a second of quiet waits a round trip for the new key. The measured preset rates were taken with the timer on in every preset. The side that answers such a request now starts its own rotation beside its REKEY_ACK, so the answer does not wait another round trip.
  • More of Chrome’s page load. The client now sends its preface PING in a TLS record of its own, as the captured Chrome session does, and fetches /favicon.ico once the WebSocket is open, with the headers and priority of the captured stream-9 request. scripts/generate_transport_profiles.py takes that request from the fixture’s observations.
  • The server picks the cover server’s cipher. OpenSSL’s default made yumed follow the client’s order and answer Chrome with AES-128-GCM, where the captured Node server answers with AES-256-GCM. The ServerHello is not encrypted, and the first native stealth capture showed the difference. yumed now applies Node 24’s own order over the client’s: AES-256-GCM, ChaCha20-Poly1305 and AES-128-GCM under TLS 1.3, and Node’s cipher string under TLS 1.2. The registry’s new cover_server_tls holds both, and the generator refuses a TLS 1.3 order that would not pick the captured ServerHello’s suite from the captured ClientHello.
  • Connection credit of dropped data. A DATA or PACKET record that started a key rotation waited behind it with its connection credit already taken. If its stream closed before REKEY_ACK arrived, the record was dropped without returning that credit, while the peer, which never saw the record, still counted it as granted. Each drop shrank the session’s usable window by a record, until the sender held less credit than its next record while the receiver waited for half its window, and every stream stalled. A soak of repeated parallel downloads slowed from about 190 to 126 Mbit/s and stalled after 16 to 18 minutes. The dropped record’s credit now comes back.
  • H2 carrier window follows the session budget. The admitted carrier’s receive window was a fixed 8 MiB. The carrier keeps each record’s H2 credit until its sink completes and nghttp2 returns credit at half its window, so a session with a budget above 4 MiB moved at most 4 MiB per round trip whatever its budget. The window is now twice limits.max_queued_bytes, at least 8 MiB and at most 128 MiB, and the carrier’s retained-byte bound grows with it. The default 4 MiB budget keeps 8 MiB.
  • More frequent credit returns, on request. A receive window returns credit once half of it is consumed, so a window that can no longer grow lets a far sender move about half of it per round trip. limits.credit_returns_per_window, 2 by default, 4 or 8, returns credit every quarter or eighth of such a window instead, for about three quarters or seven eighths of it per round trip and that many more upstream credit records.
  • Cancelled writes on an aborted stream. When the peer aborted a stream with queued writes, the engine released the stream’s queued bytes at once and then failed the writes one at a time. A write cancelled in between, for example by a handler reacting to the first failure, was released a second time. The session’s queued-byte count wrapped below zero, and the next record found the queue full and ended the session or refused its writes. Fast far-path downloads and a soak of repeated parallel downloads hit it.
  • Negotiated epoch size. limits.max_epoch_bytes, a power of two from 1 MiB, the default, through 64 MiB, sets how much protected payload one directional key epoch may carry. The capability manifest, now schema 2, signs it in the AUTH flight, and both directions use the smaller of the two sides’ values, so neither side can make the other rotate less often than it allows. Rotation waits one round trip for REKEY_ACK, so on a far path a session carries about one epoch per round trip. A larger epoch trades key granularity for speed there. The record limit stays at one record per 2 KiB of the epoch and the 500 ms age limit is unchanged. Schema-1 manifests are rejected and the YTP/1 vectors changed with it.
  • Session queue budget. limits.max_queued_bytes accepts 64 KiB to 64 MiB. The parser and yume-doctor had accepted up to 64 MiB while the session engine refused more than 16 MiB, its largest single buffer, so a larger budget passed --validate and doctor and then stopped yumed and yume at start. The engine now has its own 64 MiB bound for queued and held bytes, the parser owns the configured bound, and the runtime checks at compile time that the engine accepts it. A larger budget buys far-path speed with memory, since a session’s receive windows grow up to it.
  • Native failure handling and memory bounds. Expired ABI opens no longer enter the session while waiting for dispatch. Startup and stream operations settle their callbacks and state when allocation fails. Buffer growth stays within its bound, and write queues charge retained capacity. The public listener retries temporary descriptor or memory exhaustion. Share-import errors omit decrypted JSON, file-name checks need no allocation, and relay rekey and history cleanup keep secret storage under a wipe guard. History reads reject duplicate or nested fields without building a JSON tree.
  • Unqualified cover profiles are refused. A schema-1 document that names a cover profile other than the one the build qualifies now fails to load, in yume, yumed and the C ABI alike. Before, the programs accepted such a document and ran with the built-in profile anyway, while yume-doctor already refused it.
  • Session closure and reconnect. The native client reconnects from an endpoint notification instead of checking session state every second. Teardown settles pending engine callbacks before the endpoint frees the session slot and reports closure on its context. Failed starts, and sessions that end sooner than the longest backoff after authenticating, keep exponential backoff. A path that drops each connection right after AUTH can no longer drive a tight reconnect loop that uses up the server’s shared admission replay entries.
  • Empty UDP datagrams. The direct-route bridge drops an empty datagram from a destination instead of closing the flow, since a YTP packet cannot be empty.
  • GCC 11 allocation hooks. The AUTH publication and admission replay regressions now use the shared out-of-line test hooks as well. GCC 11 CI had reported the AUTH publication test’s inlined malloc/free pair as a mismatched new/delete call. Both keep their injection points and cases.
  • DNS route refusal. Asynchronous OPEN permission failures now send the existing unauthorized CLOSE code, so SOCKS5 returns 0x02 instead of host unreachable. A Linux process test runs the production daemon with controlled resolver answers, checks mixed allowed and denied answers in both orders, verifies that refused routes open no sockets, and carries traffic afterward.
  • Build and tooling portability. The direct-route archive applies the shared ABI’s non-LTO policy only when the native backend is composed, preserving Release test links against LTO-built engine libraries in other configurations. Documentation pipeline fixtures use cleanup compatible with Python 3.10. Test-only allocation-failure hooks stay out of line. Optimized GCC 11 builds had inlined them and reported their malloc/free pair as a mismatched new/delete call. The server teardown regression now uses those shared hooks too, retaining its caller-thread failure scope and all teardown checks.
  • Replacement work and embedding boundaries. Contributor and automation guidance require source-backed decisions, review of the proposed solution, capability-based retirement, and consumer-independent interfaces. Build help no longer makes historical transport-v2 parity a replacement prerequisite. The source map identifies admission’s v2 token coupling and separate replay check so reuse does not silently import another protocol’s contract.
  • Neutral example configuration. The client example uses the reserved server.example.invalid placeholder. Server certificate/key examples use /etc/yume/server.crt and /etc/yume/server.key.
  • Removed the inactive ABI log callback. The experimental runtime options no longer accept a logging callback that was stored but never called. Its record type and level constants are removed with it. Rebuild candidate consumers against the current header; event callbacks remain supported. Exported functions, protocol domains and independent versions are unchanged.
  • Codec permission documentation. Per-key authorization uses allow_codecs; the old allow_monero_rpc field is rejected by the closed metadata parser. Server configuration and CLI codec controls remain separate.

  • Transport-v2 configuration parity. Server saves preserve shaping inputs, metadata/capture paths resolve beside the configuration, and nonempty cover roots/backends activate HTTP cover validation in both readers. Client saves replace cleared/default values and stale split codec endpoints. Overridden server security booleans, client codec endpoints and the Android pin alias remain type-checked. Nonzero client shaping fails facade validation, and serialization failures preserve the destination. Parsing remains separate from runtime acceptance; schema 1 and the Android alias retirement stay separate.

  • TransportCore shutdown transfers stream callbacks and drains queued writes without allocating. Session close releases the socket if its close deadline cannot be armed, cancels TLS and HTTP idle deadlines, and releases codec sockets, receive credit and response reservations. Closed sessions refuse late idle rescheduling. TCP and UDP streams own their pacing timers so close releases delayed writes without waiting for the pacing deadline. Regressions cover live reads/waits, codec deadline setup, terminal close and subsequent executor destruction under allocation failure. Broader async ownership remains open.
  • AUTH frame declarations are checked at the header against the 64 KiB record budget, allowing at most 256 extra padding bytes. The server rejects other frame types before unauthenticated payload reads. Frame encoding and decoding also check lengths before narrowing or adding a peer-controlled length.
  • Private PEM loading and partial share JSON construction register cleanup before later allocations. Composite PEM export allocates the returned vector once, signing contexts use RAII, and mutable-buffer erasure covers retained capacity. Failure regressions observe storage before it is freed, including OpenSSL memory-BIO growth failure. Library scratch erasure remains unproven.
  • Transport-v2 admission refuses saturation without evicting live replay entries and rolls back partial insertion on allocation failure. AUTH rejects empty admin fields, requires exact ML-KEM-1024 widths and enforces the server-info cap on both encoding and parsing. Identity admission no longer leaks a slot on allocation failure, and optional last_seen writes cannot turn a published AUTH decision into a rejection. Valid wire bytes and cryptographic domains are unchanged.
  • Control registration, lifecycle IP checks, and operator-proof URL parsing avoid Boost’s allocating noexcept string-view overload, which could abort the process on allocation failure. Control and directory parsers share field predicates while retaining their limits, accepted values, and diagnostic order.
  • Share-bundle construction wipes partial secret copies. Copy and move constructors delegate to a completed empty object before assignment, so allocation failure runs secret cleanup before member destruction. The old constructor failure handlers accessed already-destroyed strings. A focused regression checks secret bytes at deallocation on success and at each allocation failure, including a later public-field copy.
  • The control reference describes the current C++/IPC interface. Removed claims about deleted generic C request calls, cached sizing retries, and queued-handler cancellation. Local socket timeouts do not provide an overall operation deadline or rollback.
  • Filter archives and reloads preserve publication boundaries. Native liblzma/libarchive processing replaces shell tar execution, with compressed, decoded, payload, member, filesystem-node, decoder-memory and elapsed-work limits. Failed loads retain previous rules and files; successful reloads replace an immutable snapshot instead of appending into active rule vectors.
  • File and cover boundaries fail closed. POSIX bounded/private readers reject embedded NUL paths and open nonblocking before rejecting FIFOs. Static-root reads pin each directory descriptor, refuse symlink components, and take file bytes and modification seconds from the same handle. HTTP byte-range integers reject overflow. Unsupported platforms refuse confined static reads and archive extraction; Linux filter archives use bounded native decoding and confined publication.
  • Captured HTTP responses preserve their bodies. Header-only LF normalization replaces whole-response rewriting. A bounded HTTP parser refuses invalid or incomplete framing, and H2 conversion decodes chunked bodies before DATA emission.
  • CLI and embedded validation agree on explicit null. Known transport-v2 fields reject null through the shared closed-key validator. Daemon identity loading uses the canonical key-management parser and bounded reader while retaining the five-resource startup/reload transaction.
  • Embedded operator proof is refused explicitly. The server runtime reports an invalid argument for anonym=true; generation, refresh, and logging policy remain owned by yumed. GUI controls reflect that boundary, and Bash completion treats the operator token argument as a file path.
  • Standalone security regressions now execute their assertions and link the production helpers they exercise. The early-declared operator-token and curl-transport tests explicitly undefine NDEBUG in optimized builds, and the static-site regression links yume_core for the shared bounded reader. The warnings-as-errors qualification build caught both build-graph gaps.
  • Operator proof tokens are file-only. The inline anonym_token config key and --operator-proof-token argv surface are refused. External proof tokens now come from anonym_token_file / --operator-proof-token-file, loaded through an owner-only, no-follow, regular-file check with a 4096-byte bound.
  • BaseFWX dependency evidence is current and CI-enforced. The YUME pin now names remotely reachable BaseFWX 8a8ab66, whose current workflows include the retired-media repair missing from the previous pin. The deterministic source-dependency SBOM is regenerated from that manifest, and CI now runs its drift checker before building.
  • Authorization metadata uses a closed schema. Permissions belong under permissions, codec access uses allow_codecs, and scheduling uses weight. Unknown fields and alternate spellings are rejected.
  • Configuration and key stores accept their current writer formats. Client configuration uses threads, udp, and app_codec; Android’s tls_pin_sha256 remains accepted until its config writer changes. Relay keys require the generated JSON record, and GUI material metadata requires schema 1 with random IDs. The unused inner_dual, inner_required, and --inner-required server inputs are removed. Inner encryption remains mandatory. The carrier diagnostic uses one --client-profile option.
  • Client configuration keys are a closed set. The yume CLI parser and the facade parser used by the GUI and the C ABI share one key table in src/config/client_document_keys.hpp. An unknown key is an error, a retired key names its replacement, and integral fields reject values they cannot represent instead of wrapping. Both parsers previously ignored unknown keys, so a misspelled tls_pin silently disabled pinning.
  • Obfuscation jitter and worker threads are bounded. obfs_jitter_ms and threads were only checked for representability, so a config could ask for roughly 49 days of delay on every outbound frame or a thread count that exhausts the process limit at start. Both parsers now enforce policy::kMaxObfsJitterMs and policy::kMaxIoThreads.
  • Inline obfs_secret is refused at parse time. The client and server configuration fields that carried an inline admission secret were rejected at start since transport v2; the field and its plumbing are gone and every parser now rejects the key with the file-based replacement named.
  • Server teardown contains cancellation failures before joining. RuntimeController::stop() records the first failed stage without allocating. Successful cancellation drains shutdown handlers normally; a cancellation failure stops the I/O context before joining so a pending accept cannot hang the caller. Later status reads format the diagnostic. Local IPC socket-path cleanup also avoids allocation during destruction. IPC shutdown retains the listening descriptor until its serving thread joins, removing an accept/stop data race and premature descriptor reuse. This closes the teardown abort and join-hang paths without claiming graceful session cleanup after failed cancellation.
  • A cancelled endpoint start reaches STOPPED. The C ABI no longer strands a handle in STARTING when a runtime stop races a start; the endpoint moves to STOPPED and emits a CANCELLED event.
  • Registration shape and H2 regression test renamed. The only control registration format the client sends was called “legacy”; it is now ControlRegistration, and the retained transport-v2 H2 regression test is yume_transport_v2_h2_regression_test.
  • Share-file KDF label is pinned. New .yss share files always request Argon2id explicitly, so the BASEFWX_USER_KDF environment variable can no longer downgrade them to PBKDF2 or fail encoding with an unsupported label. Existing files are read from their serialized label as before.
  • Manual pages and front-door documents name the product version. The .TH headers and the README, documentation map, and status page state 0.3.0-dev1 and describe 0.2.0-dev6 only as the transport-v2 wire. tests/test_project_metadata.py now fails when they drift.
  • Third-party attribution corrected. The GUI credits page names OpenSSL as the ML-DSA-87 provider and the downstream OpenSSL patch overlay, and no longer calls BaseFWX the core crypto engine or claims ML-DSA comes from liboqs.

  • No built-in cover page, and a cover source is required. yumed contained a compiled redirect stub that probes reaching the decoy path could identify. Startup now refuses unless one of upstream_response_dir, upstream_response, real_root, or real_index_path is set alongside real_backend for real_http operation. Ordinary HTTP/1.1 and recognized HTTP/2 GET/HEAD requests already reach the same backend. Separate partial/malformed and admission-disabled probe paths can use the decoy. The cover reference records their remaining differences.
  • Cover material has explicit file and cache bounds. A configured index, static asset, or captured response is capped at 8 MiB and read through the regular-file, no-follow bounded reader. Rotated captures are limited to 256 matching files, 4,096 directory entries, and 64 MiB retained in aggregate, so a bad operator path or periodic reload cannot grow the daemon without a ceiling.
  • Packet-mode DNS has no default. The resolver was silently 1.1.1.1 when none was configured, making a third party the observer of every hostname a tunnelled client resolved. --packet-egress tun now refuses to start until an IPv4 resolver is named.
  • Server configuration keys are a closed set. The yumed CLI loader and the facade parser used by the GUI share one key table in src/config/server_document_keys.hpp, over the same predicate as the client set. An unknown key is an error instead of “leave the default”, and several of those defaults are security decisions.
  • Server obfs_jitter_ms and threads are bounded. The server loader accepted any representable value, so a config could delay every outbound batch by up to roughly 49 days or request an unbounded worker count. Both now use policy::kMaxObfsJitterMs and policy::kMaxIoThreads, matching the client.
  • Configuration and start failures are typed. A failure attributable to one member now carries an RFC 6901 JSON pointer in both dialects, malformed input is YUME_STATUS_PARSE_ERROR and a document that parses but does not validate is YUME_STATUS_INVALID_ARGUMENT, and yume_endpoint_start reports the runtime’s typed outcome instead of collapsing every failure to YUME_STATUS_IO_ERROR. A refused bind is now distinguishable from a transient failure without parsing the diagnostic text.
  • The release lane is linux-desktop-0.3.0. It accepted only v0.2.0 tags while the product was 0.3.0-dev1, so it could not ship the tree.
  • The plain attach control command is named truthfully. Its refusal called it “legacy” while admin.attach is a different operation, not a newer generation of the same one.
  • HPACK integers no longer wrap on the unauthenticated carrier probe. decode_hpack_varint accumulated shifts and additions without an overflow check, so a peer could declare a string length near 2^64 that made decode_hpack_string’s bounds check wrap and pass, reaching std::string::assign with that length. The resulting exception escaped Session::on_h2_probe_read into the Asio worker, which aborts the process (see the worker-containment entry below). A wrapped value could also resolve to a static-table index the wire never carried. The decoder now rejects both operands before evaluating them, compares remaining length by subtraction, and bounds the header count and aggregate bytes one block may retain. The probe read contains a decoder throw and serves the masquerade response. Reachable before authentication.
  • A throwing completion handler no longer aborts the process. The server and client worker threads ran io_context::run() as the thread body. An exception that escapes a completion handler propagates out of run(), and an exception that escapes a thread entry point calls std::terminate, so any peer able to reach a throwing handler could abort the daemon. Both workers now go through runtime::run_worker, which contains the exception, re-enters run() for queued work, and records the occurrence in a rate-limited warning. The worker does not identify or close the offending session; another pending operation can still retain it. Containment is a backstop: a handler at a trust boundary must still reject bad input rather than throw.
  • One frame-payload bound, not three. server::detail::kMaxFrameSize was a third independent spelling of the same 16 MiB limit on the same header field, alongside the outbound constant and a bare literal in the prefetch classifier. All three now resolve to protocol::kMaxFramePayloadBytes.
  • Control frames are bounded before allocation. protocol::read_frame resized the payload to a peer-supplied 32-bit length, so a hostile server could ask a client for 4 GiB before anything parsed. The cap now belongs to core/protocol, which defines the header, and is shared by the synchronous reader, the asynchronous transport path, and the prefetch classifier that previously repeated the number by hand.
  • The optional TLS fingerprint diagnostic is bounded. Its HTTP/1.1 fetch appended to EOF and its HTTP/2 fetch allocated each peer-declared frame, accumulated DATA, and waited for END_STREAM, all without a limit or a deadline, so a redirected endpoint could hang or exhaust the client. The fetch moved to core/stealth/tls_verify_fetch.hpp with total, per-frame, and frame-count caps plus a wall-clock deadline, and the socket carries a receive timeout. It runs only under openssl-diagnostic with tls_fingerprint_verify.
  • MaxMind record parsing is valid C++. MmdbValue held std::vector<std::pair<std::string, MmdbValue>>, and std::pair does not admit an incomplete member type the way std::vector admits an incomplete element type. GCC accepted it; Clang rejected it, which blocked every Clang-based analysis of the server tree. The key now lives on the value and the vector holds the element type directly.
  • The vendored nlohmann tree is complete, so CI builds again. third_party/nlohmann_json/nlohmann/ shipped json.hpp but not json_fwd.hpp. That directory is first on the include path, so config/v1/config.hpp’s #include <nlohmann/json_fwd.hpp> fell through to whatever the build host had installed while json.hpp came from the bundle. A host whose distribution nlohmann matched the bundled 3.11.3 built fine, which is why every developer and build-host lane was green while every CI lane failed inside the vendored header: Ubuntu 22.04 ships 3.10.5, which declares basic_json and json_pointer differently. Upstream publishes json.hpp and json_fwd.hpp as two standalone release assets and only the first was vendored, so the second is now vendored beside it, byte-identical to the upstream asset. Take the release asset rather than the multi-header distribution’s file of the same name, which pulls nlohmann/detail/abi_macros.hpp from a tree this directory does not carry. third_party/nlohmann_json/PROVENANCE.md records both URLs and hashes, and a metadata test pins them and enforces that the vendored tree satisfies every <nlohmann/...> include in the sources and in the vendored headers themselves. The dependency and its version are unchanged, so the notices and SBOM are too.
  • The tray builds without a tray. gui/platform/tray.cpp defined write_icon_for_status and remove_icon_files unconditionally, but only the Linux AppIndicator path uses them. Any build without that path, which includes every host missing libayatana-appindicator, left them orphaned and failed under -Werror=unused-function. They are now guarded by YUME_GUI_TRAY_LINUX, matching where they are used. The Windows, macOS and no-tray paths never referenced them.
  • The GUI icon refresh acknowledges system() properly. gui/platform/app_icon.cpp deliberately ignores the status of four best-effort desktop-cache refreshes and said so with a (void) cast, which GCC does not accept as acknowledgement of warn_unused_result the way Clang does. It went unseen locally because Ubuntu enables _FORTIFY_SOURCE by default at -O2 and Debian does not, and fortify is what marks system() as __wur. The status is now assigned and discarded through one helper. The behaviour is unchanged: these are backgrounded commands whose status describes the shell, and a missing tool is the expected case.
  • -Wstringop-overread is demoted to a warning on GCC 11. GCC 11 propagates an exact string length proved at the call site into libstdc++’s std::string move assignment, then reports an overread against the short-string branch a string of that length can never take. It fired on client/transfer/share_file.cpp, where a 64-character secret is validated and then moved. The diagnostic’s location is inside the system header, so an in-source pragma does not suppress it, and GCC 12 and later do not emit it. Demoted rather than disabled, and only for GCC 11, so the release toolchain still prints it if it ever fires on something real. This had been invisible because every CI build lane failed earlier, in the vendored JSON header.
  • The CI OpenSSL guard counts lanes instead of assuming three. release_preflight.py required exactly three occurrences of the dependency-setup block and of -DYUME_STATIC_OPENSSL=ON. The invariant is that every lane which configures CMake carries both, so the literal count went stale as soon as a lane was added and would also have passed if one lane were deleted and another added in its place. It now derives the lane count and names the mismatch.
  • A Clang build of the whole tree links again. yume_anonym_curl_transport_test used yume_apply_warnings where every other non-ABI target uses yume_apply_perf_opts, so it linked the LTO-built yume_core without -flto. GCC hides that by using the linker plugin automatically. Clang does not, and GNU ld reported “file format not recognized” on a bitcode archive. The ABI targets remain the deliberate exception, because their inputs are built without LTO.
  • yume-packet-quick examples are valid input. --listen requires a numeric IPv4 address, which the helper renders into nftables and UFW rules, but the documentation, the parser’s own error message, and the test fixtures all used a hostname. The test was also never registered, so two failing cases passed CI; it now runs in CTest.

Removed

  • tools/yume_setup.py and tools/yume_doctor.py. The native programs replace them, and the tests that specified them run against the programs.
  • The transport-v2 benchmark sandbox. yume_bench_isolation.py, yume_bench_provenance.py and yume_bench_exec_guard.py had no user after the v2 benchmark went. The capture scripts’ tests remain.
  • Code no shipped program reached. The reachability check found Tunnel::status_age_ms in yume-gui, ClientCircuit::hops, H2Carrier::capture_observer_active, H2Carrier::RejectCarrier (the front door answers a failed CONNECT from the cover, so a separate refusal would only mark the carrier), the four-argument SessionEngine::async_open that only tests called, and untested move assignments of Kit and LayerCipher, which hold secrets.
  • The stream CLI help writer. It served the transport-v2 CLI layouts, with interpolated runtime constants from a header that no longer exists. Every layout generates constant help and completion strings.
  • Transport v2. Its wire, AUTH v2, security modes, client and daemon, GUI facade and control API, federation, host controller, the Go Chrome TLS helper, the v2 benchmark and self-test drivers, the multi-target and Windows build scripts, and their documents and manuals are gone. Commit 9070b0a is the last that contains all of it. What YUME still needed moved first: the relay channel, file transfer, codecs, the share container, egress lists, per-client speed limits, forwards, the SOCKS5 upstream and the loopback fetch are native, and config/yume.json and config/yumed.json are now the schema-1 examples.
  • The transport-v2 desktop GUI. Its source in src/gui was built on the removed v2 facade and could not build since. Every page depended on that facade, so a native GUI is a new design rather than an update. The source and its two unregistered GUI test scripts are readable in commits up to cff6905, and YUME_BUILD_GUI is gone.
  • Inline real_secret and the --real-secret flag. The cover-backend secret could be passed in argv, where the process table exposes it, or written into the configuration file. Both server loaders now refuse the key and name real_secret_file, which is read at start or created with fresh random contents.
  • Five unused server configuration aliases. allow_monero_rpc, codec_allow, deny_default, exposure_check, and the combined monero_rpc_backend spelling had no writer and named no consumer. The equivalent CLI flags remain, because a flag name and a config key are separate surfaces.
  • docs/release/RELEASE-NOTES-1.1.md. It described a stable 1.1 release that never happened and was installed into every package. Git history keeps it.
  • Unused dependency-manifest parsing in CMake. The BaseFWX repository and revision fields were parsed and never used; ezbuild.sh remains the enforcement point for the pin.

Fixed

  • Retained relay storage and file receives. History uses full SHA-256 names for every instance and peer, keeping literal IDs separate from digest names. Trust-commit retries repeat the directory flush before reporting success. File-receive failures clean up before allocating diagnostics, and unrepresentable receive deadlines are rejected before state is created.

  • Capture cleanup and evidence failures. Circuit and nDPI capture tools release earlier children and listeners when later startup or cleanup fails. Missed capture deadlines remain failures after forced cleanup. nDPI reports require complete packet accounting, and the TLS evidence relay keeps its forwarding writes within the idle deadline.

  • Setup rollback under allocation failure. Cluster edits and client enrollment remove exclusively created files when later bookkeeping fails. Files referenced by a published replacement remain available if a later directory flush fails.

  • Credential failure reporting. A refusal keeps its typed status when copying its diagnostic runs out of memory, instead of terminating startup.

  • Test-fixture failures. Module socket construction removes its temporary directory on failure. An unexpected circuit event-loop exception fails the test, while synchronous task exceptions still reach their callers.

  • Build-directory ownership. Builds and cleans lock the same canonical directory, including symlink aliases, and refuse to proceed without a usable shared lock directory.

  • ABI channel publication across restart. A stream or packet open/accept that finishes after endpoint stop is cancelled before publishing its handle, even when a new start has reused the same backend.

  • Sealed-kit input and cleanup. Directory reads stay beneath a pinned root, enforce the file-count limit while enumerating children, and read file bytes and executable permission from the same descriptor. Temporary file plaintext and generated code buffers are wiped on failure as well as success.

  • Circuit completion and receive limits. Circuit streams retain callback delivery after their circuit owner is released and report allocation failures through their status channels. Pool handoffs keep one completion owner through conversion and provider initiation. New streams and growing windows share the circuit’s aggregate receive budget, and the pool selects a circuit with room for the initial grant.

  • Shorter-route consent. Pending proposals are discarded when their session or verified routes view changes, fails or expires. Stale proposal IDs cannot authorize a shorter route. Previously accepted hop policies keep their documented lifetime until the configured route length works again.

  • Engine control records under a full outbound budget. A session no longer fails when data or pending writes fill its outbound byte budget and the engine must publish its own acceptance credit, credit return, FIN, terminal CLOSE or settlement. Ordinary records leave room for one such record, and the rest wait as bounded per-stream and per-connection obligations sent in order as earlier records complete. A window keeps at most credit_returns_per_window grants waiting to be sent. Connection credit no longer competes with PONG for max_control_messages, closing streams stay counted until their owed records are sent, and a local OPEN that does not fit fails alone with ResourceExhausted.

  • Progress beside an unread stream. The receiver returns already-consumed connection credit when the peer has less than one maximum frame left, including when the last arriving record belongs to the unread stream. Previously the held stream could leave less usable credit than the normal return threshold, stopping useful streams indefinitely. Held records keep their credit, and queue budgets, receive windows and preset values retain their bounds.

  • Installed SDK discovery paths. The pkg-config gate now reuses the authenticated stream, route and packet probes already used by the CMake gate. Both consumer paths preserve selected language and sanitizer options, and C++ runtime link options stay out of C commands. Each fresh prefix retains command results and nested CMake inventory/JUnit evidence.

  • ABI authentication totals survive session end and stop. Restores the intended per-start lifetime of sessions, including stopped backend replacement. An accepted STARTING transition begins a new counter generation at zero, including when that start fails, is cancelled or expires; calls rejected before that transition preserve the old generation. Device reconnections accumulate in the same generation and retain the matching failed_attempts through stop. Sticky failure diagnostics and handle-wide byte totals keep their separate lifetimes.

  • ABI session-end status. A client without a device publishes ENDED with the ended session’s typed reason after recording its lifecycle line. A status read during notification delivery could previously pair ENDED with a failure retained from an earlier start.

  • Release publication requires trusted signatures on the annotated tag, source commit, artifacts, aggregate checksums and final manifest. The manifest is generated after artifact signatures exist and classifies aggregate checksum signatures separately. Uploads refuse existing asset names and never replace them. The obsolete independent-review approval input is removed.
  • Circuit failure reports survive queued delivery. A hop ends its backward write direction after its failure report instead of immediately aborting the stream and discarding unread cells at the previous hop. Crossed forward cells drain within the existing stalled-circuit timeout, preserving both failure attribution and bounded credit release.
  • Server accept failures survive memory exhaustion. A served endpoint’s terminal accept failure reaches the main context through a reserved task, so an allocation failure cannot lose the typed notification and leave the daemon running with a closed endpoint. The task retains the runtime state through its callback.
  • Kit queries respect callback restrictions. The C ABI’s sealed-kit queries enforce the existing callback rule: file count returns zero, and file access returns YUME_STATUS_INVALID_STATE without changing the caller’s output. Reads outside callbacks are unchanged.
  • Cover fixture startup cleanup. A reference server that misses its startup deadline is killed and reaped before the test reports failure, so its process and piped output cannot keep the test runner alive.

  • ThreadSanitizer reported races inside OpenSSL. Concurrent handshakes on one library context read and extended OpenSSL’s name map, which publishes entries through atomics that an uninstrumented OpenSSL hides from TSan, so CI’s thread-sanitize job failed now and then on a race between ossl_ht_get and ossl_ht_insert. The TSan lanes now build the pinned, patched OpenSSL with ThreadSanitizer, selected by YUME_OPENSSL_SANITIZE=thread in scripts/ensure-openssl.sh, into a separate static-only cache prefix. Nothing is suppressed, and TSan now checks YUME’s own OpenSSL use as well.
  • yume-doctor refused a circuits client on a cluster member. It checked each capability against the configured services only, while yumed also grants yume.circuit on a member of a cluster, so a server with a client from add-client --circuits failed the doctor. It now follows the daemon’s rule and still refuses the grant on a server outside a cluster.
  • The release workflow’s tests did not link. Built as release.yml builds them, with LTO on Ubuntu 24.04’s GCC 13, the TUN network test lost its sd_bus stand-ins, which nothing in the test calls, before the linker’s --wrap pointed the library’s calls at them. They are now kept as used. CI’s jobs build the test without LTO and never showed it.
  • cluster-sign could publish a list the nodes refuse. It now checks the signed list and routes view with the nodes’ own reader before replacing any copy, so a record edited into a host or name the reader refuses leaves every node and the serial unchanged.
  • The GUI could crash when a page came back into view. The kit list, the local listeners and the circuit routes are Repeater rows, rebuilt whenever their model changes, also while their page is hidden. Qt 6.4’s layouts can keep a destroyed row and dereference it when the page is shown again (QTBUG-111792, fixed in Qt 6.6). The rows now sit in plain Columns that the layouts see as one item. CI’s GUI job crashed once, and Qt 6.4.2 crashed in 1 of 43 runs, and at once under ASan.
  • The Overview’s Local access card collapsed on Qt 6.4. On Ubuntu 24.04’s Qt 6.4.2 the card showed only its heading, and the SOCKS5 row drew without its address, when the listeners arrived while the card’s row was still hidden. The hidden row kept each card at its preferred width of one pixel, Qt 6.4 did not lay the list out again once the row was shown, and a Column skips rows of width zero. Every Repeater list now goes through one RowList that takes its width from its parent, which also covers the circuit routes, the kit list and the Credits lists.
  • IPv4 literals with leading zeros. The configuration accepted 010.0.0.1 as an IP literal. The TCP provider could not parse it as a number and passed it to the system resolver, which reads 010 as octal, so the client dialled 8.0.0.1. Configuration and cluster lists now refuse a leading zero, as the C ABI already did, and both use one check.
  • A stale OpenSSL error ended a busy TLS session. OpenSSL reports a TLS call’s outcome through the calling thread’s error queue, and the TLS channel did not empty that queue first. Any OpenSSL failure left on an I/O thread, such as a reload refused for a bad list signature or a circuit handshake with a malformed key, turned the next session’s ordinary wait for data into a fatal error, and a cluster link carrying circuits closed. The channel now empties the queue before each TLS call. yume_tls13_secure_channel_test leaves an error before every step, and the four-daemon circuit test refuses a reload under load.
  • A circuit build outlived its pool. Closing the client’s circuit pool or giving it a new session left a circuit that was still being built running until its 30-second build bound, and an open cancelled while it waited for that build settled only when the build ended. Both now end at once.
  • Callbacks that ran twice under libc++. Several components moved a callback out of a member and later tested or settled that member again, relying on the moved-from std::function being empty. libstdc++ empties it, but libc++, which Android links, leaves a small target in place. A closing TLS channel then delivered its spent handshake completion without end, so stopping an Android endpoint never returned. Records deferred across a rekey completed a second time with Cancelled, which ended a busy stream mid-transfer. A session’s start and a stream’s OPEN, the HTTP/2 carrier’s creation, sends and receives under allocation failure, a route bridge’s acceptance and a circuit stream’s open could each complete twice. Each now empties the member explicitly. The whole suite was run against clang and libc++, and against libc++ headers patched to abort on any use of a moved-from callback. Four tests that assumed libstdc++’s allocation sizes or moved-from state were corrected, and the tests’ allocation hook no longer fails the storage of the exception it throws.
  • yume-gui took a starting client for an ended one. Right after starting yume, the window read an empty command line for a process inside execve, as a wrapper that runs yume is for a moment, reported “yume stopped while starting” and failed the connect. A process with an empty command line now counts as running unless it has ended or is a kernel thread.

[Unreleased 0.2.0-dev6]

Product and transport versions were rebaselined to reflect YUME’s experimental maturity. This transport is a hard break from 0.2.0-dev5, and dev6 has one evidence-backed chrome151-node24-v1 identity. No dev5 compatibility or downgrade mode exists.

Added

  • Generated transport-profile registry. Capture manifests and H2 evidence now generate immutable C++ and Go registries consumed through browser-neutral API. Bounded schema/path/uniqueness checks and negative metadata tests reject stale output or unsafe fixture references. The authenticated dev6 ID and all wire/crypto domains remain unchanged.

  • Single-source dependency manifest. BaseFWX repository, exact revision, and minimum compatible version now live in config/dependencies.json and are consumed by CMake, local build scripts, CI, CodeQL, and release preflight. The dependency remains reproducibly pinned; duplicated workflow literals and the unsafe CI fallback to a floating branch are removed.

  • Five-run Chrome 151 / Node 24 evidence. Five fresh normal-Chrome profiles cover TLS outcome, ordered H2 settings and headers, page/assets, RFC 8441, bidirectional bulk flow control, 42-second idle behavior, and graceful close. A committed validator separates stable identity fields from measured timing and flow-control distributions.

  • Authenticated transport profile. Admission HMAC input, schema-3 AUTH challenge/response/confirmation records, the signed TLS-exporter transcript, initial root, and per-frame AES-GCM AAD now all bind the exact chrome151-node24-v1 identifier. Stale identifiers, schema-2/dev5 records, missing fields, and configured profile mismatches fail closed without a fallback.

  • Fail-closed Linux 0.2.0 release lane. The linux-desktop-0.2.0 profile builds only glibc Linux x86-64, statically embeds the checksum-pinned patched OpenSSL 3.5.7, and retains the exact-Go-1.26.5 Chrome helper as an optional comparison backend. It emits yume-amd64-linux.tar.xz with the client/helper, licensing, quick-start, and machine manifest, plus a separate yumed-amd64-linux. The preflight rejects other platforms and variants, incomplete bundles, version/tag drift, unequal clean helper rebuilds, or relaxed PQ, Argon2, and LZMA requirements. Publishing is disabled by default and requires explicit independent-review and RC-gate acknowledgements.

Changed

  • Experimental version rebaseline. Product, package, release-profile, and authenticated transport identity now use 0.2.0-dev6; AUTH v2, relay-v2, ABI v1, helper IPC v1, and crypto domain schemas remain independently versioned. The obsolete v1.0 tag was withdrawn because it named an unstable, incorrect project state.

  • Go helper dependency floor. The opt-in uTLS helper now requires Go 1.25 module semantics while retaining the exact Go 1.26.5 build toolchain. Its indirect golang.org/x/crypto dependency moves from 0.36.0 to 0.52.0 and golang.org/x/sys from 0.31.0 to 0.45.0, leaving GitHub’s reported vulnerable module ranges while preserving the pinned uTLS and wire protocol versions.

  • Native OpenSSL ClientHello emitter and helper retention. The default openssl-chrome151 backend opts into an additive, default-off patch on the exact OpenSSL 3.5.7 source. Its emitted-byte gate closes all six pinned ClientHello structure rows across 12 SSL objects sharing one context, including per-connection GREASE, edge placement, extension shuffling, [0] point formats, both real key shares, and Brotli-only certificate compression. The pinned Go/uTLS helper remains an explicit optional comparison backend until the native path passes full handshake, exporter, validation, HRR/resumption, lifecycle, soak, same-session, classifier, and reproducibility gates. There is no fallback between backends.

  • One coherent identity. Chrome is rebased to exact Google Chrome 151.0.7922.71 and official Node 24.18.0. The incomplete Firefox/Safari presets and dead rotation state were removed instead of being carried as unsupported claims. The native ClientHello structure is now closed, but complete-session qualification and a capture of the 0x001b algorithm value remain explicit gaps. The helper has passed local wire/performance, lifecycle, process-scale, reconnect, and segmented-soak gates but remains opt-in pending matched WAN, same-session stealth, independent review, and the remaining RC gates.

  • Bounded TCP producer backpressure. SOCKS and TCP forwarders now advance local reads on transport-write completion, preventing fast producers from overflowing the bounded application queue. The benchmark failure path is SIGPIPE-safe and always joins its receive workers, so failures are reported instead of ending in std::terminate.

  • Qualified helper boundary. The production Go entry point still applies Linux-only no_new_privs and adopts connected TCP/IPC descriptors 3 and 4, while its connection core is injectable for real TLS qualification. Wrong CA, hostname, leaf pin, ALPN, and exporter behavior now fail through fixed, bounded IPC errors; detailed verification causes stay local. Native launcher tests cover unsafe file modes and symlinks, partial control and plaintext I/O, post-ready half-close/cancellation, child teardown, and repeated fd/zombie balance. Connection IDs use the BaseFWX RNG abstraction; the explicit no-BaseFWX build retains the YUME CSPRNG fallback.

  • Bounded Linux process and soak qualification. At the exact clean signed lifecycle checkpoint, the 1/10/50/100/256-client Chrome-helper ramps completed with exact bytes and zero unexpected failures. The 256-client run held 256 clients plus 256 helpers concurrently and transferred 65,536 MiB. A 1,000/1,000 sequential reconnect storm transferred 2,000 MiB with no helper, zombie, fd, or thread growth. A 2,333-second full-speed batch moved 112,640 MiB per direction at 816.607 Mbit/s aggregate with zero byte mismatch, timeout, interruption, or unexpected server error. The soak used seven consecutive segments to preserve the signed endpoint’s 16,384 MiB per-invocation bound; it does not claim one uninterrupted >16 GiB connection or WAN behavior.

Fixed

  • Core receive backpressure and bounded queues. Both H2 roles transfer move-only receive-credit ownership through ordinary TCP, UDP, service, codec, packet, forwarding, and federation sinks. Slow consumers plateau and resume without automatic credit. Cover requests, pre-auth serialized wire, service writes, packet batches, and client UDP paths have independent bounded admission, cancellation, shutdown wakeup, and recovery behavior.

  • Strict peer-controlled schemas and stream ownership. Generic OPEN, client SOPEN/ROPEN, CONTROL registration/lifecycle, relay, codec, and auth metadata reject unknown fields, wrong scalar types, over-length strings, and invalid ranges without escaping the session boundary. Client and server stream IDs reserve atomically through map publication; collisions never replace local, pending, control, federated, or retired owners.

  • Durable configuration and authorization snapshots. Client/server config saves use owner-only temporary files and durable atomic replacement on the qualified POSIX path. Authorization-key and metadata operations share canonical persistent sidecar locks; daemon startup/reload parses one locked five-resource visitor/operator/admin snapshot before publishing it. ABI config/lifecycle/stream/packet outcomes now derive from typed internal statuses rather than diagnostic strings.

  • Relay and listener lifecycle hardening. Relay v2 binds composite peer identity, hybrid ML-KEM-1024/X25519 establishment, ratchet state, record counters, invite lifetimes, target policy, and trust persistence. Outbound files are hashed and streamed from one pinned regular-file descriptor, and POSIX receive publication is confined, no-clobber, bounded, and atomic. Federation duplicate peer IDs and reverse-listener accept ownership now fail without orphaning links, retaining sessions, or spinning cancellation paths.

  • EXEC fail-closed cleanup. Direct and relayed command execution remain unavailable regardless of reserved policy fields or build option. Clients do not advertise or enable inbound EXEC, and the former detached worker is removed pending a cancellable, joinable, sandboxed design.

  • Exceptional-path key lifetime. Superseded inner keys, AUTH transcripts, relay pending/KDF/channel state, ratchet roots/message keys, PSKs, exporters, and raw shared secrets are scoped or explicitly wiped across failure, throw, close, and teardown paths covered by the stabilization tests.

  • Sanitized ABI integration. Linux address-sanitizer builds resolve the selected compiler’s ASan and C++ runtimes and preload them in loader order for the Python/ctypes strict-C integration orchestrator. Instrumented libyume is therefore never loaded late into an unsanitized Python process, and the sanitizer suite does not depend on an undocumented caller environment.

  • Parallel federation fixture isolation. The real two-node federation integration gives each daemon the fixture’s private HOME and XDG runtime, matching its client. Independent build matrices no longer contend for one user-level daemon instance lock when their federation tests overlap.

  • GUI facade synchronization boundary. Client status now exposes the configured security mode, composite/hybrid-ratchet posture, TLS backend, rekey window, peer fingerprint, and capabilities rather than obsolete light/heavy/off state. Chat open/send/close/history honor real channel and peer identities, and the unused callback promises are removed. Log subscribers run outside the sink lock with exception containment; the GUI resolver is owned through shutdown; and macOS bundle versions derive from the project version.

  • Hardened BaseFWX dependency pin. The pinned BaseFWX revision is now e6ffbb79daa02bf62c31c3ae6513d5c603ec8dcd. It retains repeat-safe Argon2 discovery, fail-closed crypto/format limits, safer caller-buffer primitives, peer KDF policy coverage, and corrected Java streaming-test nonce discipline. It also supplies the owned explicit-nonce ChaCha20-Poly1305 operation used by YUME’s existing relay-history format, propagates Java benchmark worker failures, bounds whole-file benchmark concurrency, and defaults retired compatibility performance rows off. YUME’s consumed wire contracts are unchanged.

  • Bounded operator-proof HTTPS. External proof URLs now use one strict HTTPS authority/target grammar across the in-process and static-Linux curl transports. The native path verifies the configured DNS name or IP literal, applies a 30-second end-to-end request deadline, constructs and parses HTTP with bounded Beast messages, and rejects bearer-token controls before any connection is attempted. Remote responses require an object containing a bounded canonical signature; untrusted API error text is not reflected into daemon errors.

  • Helper crash and truncation lifecycle. After posix_spawn, the parent retained duplicate copies of the child-side IPC and connected TCP descriptors. Those copies masked EOF/HUP when a helper crashed or returned a truncated response, delaying failure until the handshake timeout and keeping the TCP peer open. The parent now closes both duplicates immediately after a successful spawn while preserving RAII cleanup on every failure path.

Remaining dev6 limitations

  • openssl-chrome151 is the development default and runs without the helper; this is not a release-qualification claim. Matched WAN evidence, one uninterrupted >16 GiB connection, exact Chrome 151.0.7922.71 same-session recapture, external classifier/active-probe evidence, and independent security review remain release gates. Installed Chrome 151.0.7922.108 is functional-only evidence and does not replace or rewrite the dev6 fixture. Android, GUI, Windows, macOS, ARM, OpenWRT, static builds, and Debian archive publication are outside the first official 0.2.0 scope.

[Unreleased 0.2.0-dev5]

Hard break from 0.2.0-dev4: AUTH now carries each endpoint’s accepted ratchet policy. Exact version equality rejects mixed dev4/dev5 peers; no compatibility mode exists.

Added

  • Authenticated security modes. Extreme preserves the dev4 default (256 KiB, 512 application frames, 500 ms sender-active). Normal uses 8 GiB, 262,144 frames, and 60 seconds. Soft uses 256 GiB, 8,388,608 frames, and 30 minutes. Ultimate accepts exact bounded JSON values, including arbitrary millisecond values such as 4281. Each direction uses the component-wise stricter local/peer policy. ML-KEM-1024, X25519, the random PSK, HKDF labels, AES-GCM/AAD, and one-use per-frame keys are unchanged. Challenge critical field 8 carries the server policy; response critical field 5 carries the client policy and moves the Ed25519 signature to field 6.
  • Current BaseFWX 3.8 development pin. YUME now pins remotely reachable BaseFWX commit 4692d4ce4edec2aa9835d04ad9ff6c3ad3ab9374; the consumed crypto APIs and wire contracts are unchanged from the prior pin.

[Unreleased 0.2.0-dev4]

Hard break from 0.2.0-dev2 and 0.2.0-dev3: the exact transport version changed, so dev2, dev3, and dev4 binaries do not interoperate at admission or AUTH. No compatibility mode exists or is planned.

Added

  • TLS-exporter channel binding for AUTH. The Ed25519 signature input and the establishment root now cover a 32-byte RFC 8446 section 7.5 exporter (EXPORTER-yume/2.0/auth-channel-binding/v1) that each endpoint derives from its own live TLS object and never transmits. A malicious endpoint that terminates TLS with a client and holds compatible admission and inner PSK material can no longer forward that live AUTH exchange to a second server: the two connections have independent exporters, so the relayed signature does not verify at the far end. Both peers require TLS 1.3 and a finished handshake; a peer that cannot produce a 32-byte binding fails AUTH. There is no unbound mode to negotiate. The signature domain moves to yume/2.0/auth-signature/v2 and the root label to yume/2.0/root/v2, so a dev3 peer fails loudly instead of downgrading.

  • Bounded multi-epoch ratchet window. A direction may keep several authenticated, strictly contiguous future epochs in flight or prepared instead of one, negotiated per connection through the AUTH transcript and configured with --rekey-window / rekey_window (1..64, default 8). A byte-saturated direction rises from 256 KiB to window * 256 KiB per rekey round trip: roughly 35 Mbit/s to 280 Mbit/s at 60 ms RTT by the protocol model. Every per-epoch limit is unchanged — 256 KiB, 512 application frames, 500 ms of activity — and the receiver still enforces the byte and frame limits on authenticated plaintext. Gaps, duplicates, reordered ACKs, and offers past the advertised depth stay fatal. Offers are paced by application progress so a filling window does not emit a burst of rekey records. The public implementation status records what remains unmeasured.

Changed

  • Stealth and cryptographic claims now match the implementation. One immutable Chrome 151/Debian 13 + Node 24 profile supplies the production TLS selection, User-Agent/client hints, capture-backed H2 opening, assets, and cover identity. Profile rotation remains rejected. Chrome/BoringSSL TLS parity and traffic padding remain evidence-driven work, and the 500 ms ratchet limit is sender-active rather than a twice-per-second wall-clock promise.
  • The untrusted-server boundary is explicit. Client AUTH sends a public Ed25519 identity plus transcript signature, not the private key; ephemeral hybrid exchanges provide conditional forward secrecy against later persistent-file compromise. yumed remains the terminating single-hop proxy with access to decrypted YUME stream bytes. AUTH is now TLS-exporter-bound, so live forwarding by a malicious compatible endpoint is closed, but that does not make the terminating node blind to traffic it exits.
  • Client identity files are owner-only by contract. --keys-gen serializes to memory and creates both halves through the exclusive owner-only writer at mode 0600, wipes the private PEM, and refuses to replace an existing path instead of silently overwriting an identity. Loading reads the private key through an already-validated descriptor and rejects symlinks, foreign ownership, group/world permission bits, and out-of-range sizes, so a poorly protected key can no longer sign an AUTH transcript. Enforcement is Linux/POSIX; Windows identity loading fails closed.
  • AUTH v2 records carry the negotiated window. The challenge gains critical field 7 and the response gains critical field 4, moving the Ed25519 signature to field 5. The client’s advertised depth is inside the signed record, so the negotiation is covered by the existing transcript signature.
  • An acknowledged epoch is prepared, not entered. A direction advances when the current epoch can no longer carry the next application frame, so each prepared epoch delivers its whole byte budget.

Fixed

  • Rekey ACKs are sealed in wire order. SessionRatchet::Open used to seal the REKEY_ACK on the read path. A directional chain assigns sequence numbers at seal time, so a data frame sealed afterwards on the write path could reach the wire first and the peer would close the session with “replay or unexpected epoch/sequence”. OpenResult::control_response is now plaintext and is sealed by the caller’s ordered write path like any other frame. The defect predates the window but was only reachable in practice with several exchanges outstanding.
  • Federation link stream ownership. The dialing loop’s TLS stream is now heap-owned and shared with the write path by a counted reference instead of a pointer into the loop’s stack frame. The lifetime was already guarded, but a counted reference removes the class of defect entirely.

[Unreleased 0.2.0-dev2]

Dev2 was the first hard-break 2.0 desktop transport line. It was superseded by dev3 before release; neither development version is a stable release.

Added

  • Bounded server capacity controls. Administrators can set worker threads, aggregate tracked sessions, default bulk-key sessions, aggregate accepts per second, and an optional weighted-fair egress cap. Default tracked and bulk limits are 256 and 64 respectively; unlimited behavior must be explicit.
  • Regular individual/bulk keys and a physical operator trust store. Shared bulk credentials are separately counted per connection and cannot receive controller, exec, LAN/full-control, privileged codec/service, or federation policy. Only individual keys in the separate operator store may receive outbound admin policy.
  • External benchmark resource telemetry. Localhost, LAN, and WAN harnesses can sample server/client CPU time, fair per-core CPU, RSS/peak RSS, threads, and host CPU/RAM context. Multi-client runs are bounded by default and require explicit acknowledgement above the safe local ceiling.

Changed

  • Directional rekeys are pipelined without widening their blast radius. Each sender prepares the next authenticated ML-KEM-1024 + X25519 + PSK epoch while bounded current-epoch traffic remains. The existing 256 KiB, 512-frame, and 500 ms limits remain independently enforced; writes block only if the ACK has not arrived at the hard boundary. Exact version equality rejects dev1 peers rather than silently mixing the two scheduling contracts.

  • AUTH policy lookup uses immutable snapshots. Regular and operator key/policy files are parsed and validated before atomic startup/reload publication. Failed reloads preserve the complete previous snapshot, and the same public key cannot appear in both stores.
  • Capacity logic is modular. Thread-safe identity admission and weighted egress controllers are independent server runtime units with focused tests. The optional limiter is not constructed when egress shaping is disabled.
  • Developer timing is centralized and absent from production binaries. Debug/RelWithDebInfo builds share stopwatches, batched samples, asynchronous intervals, and event macros across connection, AUTH, ratchet, scheduler, TLS, H2, and WebSocket boundaries. Runtime collection remains opt-in. Release/MinSizeRel preprocess the hooks and detail construction away, while /proc resource sampling remains external to the measured processes.

Security

  • Authentication remains Ed25519 over the canonical transcript, and admission still occurs before ML-KEM decapsulation. Rekey pipelining does not change AEAD AAD, HKDF labels, hybrid secret composition, or directional usage limits. The dev2 version bump deliberately changes when the existing rekey records are scheduled and accepted.

[Unreleased 1.1 historical work]

Target: v1.1. Do not tag until the remaining release work and remote validation are complete.

Added

  • Static-site masquerade cover (--real-root <dir>). yumed serves GET/HEAD for real files under one root with correct MIME, Content-Length, Last-Modified, nginx-style ETag, and Accept-Ranges, so the decoy is a coherent multi-asset site instead of “/ => 200, everything else => 404”. Path resolution rejects traversal, encoded-slash/backslash, control bytes, over-length targets, and symlink escape (canonicalized against the root); a per-response size cap bounds one cover reply. The same root/index backs both the HTTP/1.1 probe and the H2 decoy, with HTTP/1.1 keep-alive across a page’s assets (bodyless GET/HEAD only, bounded by a per-connection request cap and an idle timeout), conditional GET (If-None-Match/If-Modified-Since -> 304), and byte Range requests (-> 206 Partial Content, or 416 when unsatisfiable). Implies --real; pair with --hide-in-the-crowd nginx for the closest header fit.

Changed

  • License: YUME source, apps, daemon, proxy, GUI, and libyume are AGPL-3.0-or-later. Build scripts and CMake entry points now carry matching source headers.
  • Masquerade is now the active-probe boundary. --public-node requires obfs plus a nonempty shared secret. Missing, malformed, wrong-key, bad-order, or SNI/authority/listener-port-mismatched H2 admission stays outside AUTH. Empty-secret structural admission remains development-only.
  • The H2 opening is standards-oriented, not exact-browser branding. HPACK indexes, SETTINGS/ACK ordering, END_STREAM behavior, authority validation, mandatory client ACK of server settings, serialized fallback writes, and client decoy classification were corrected. Synthetic web profiles are not native nginx/Apache/etc. implementations.
  • TLS profile rotation now advances after successful connections. The carrier User-Agent follows the active profile unless explicitly overridden.

Fixed

  • Preauth privilege promotion. Self-signed peers admitted for configured named services now persist as PreauthServiceOnly; a central gate confines them to service.v1 OPEN/DATA/CLOSE and PING/PONG.
  • Caller-blind admin attach. Modern and legacy paths now share the trusted relay + caller outbound + target inbound authorization predicate.
  • Bounded shutdown and queues. Added a five-second session-close deadline, 64-per-service/256-total pending service limits, four-worker client EXEC cap, and best-effort sensitive-buffer erasure. Sanitizer and soak validation remain outstanding; detached EXEC workers are bounded but not cancellable/joined.

[Withdrawn v1.1 development plan]

Added

  • --cluster-join <spec> + --cluster-bootstrap on yumed. Friendly shorthand over the existing --peer '<json>' federation surface: --cluster-join [id@]host[:port][?pin=<sha256>] parses into the same FederationPeer JSON the daemon already consumes. --cluster-bootstrap marks a node as a cluster entry point so federation works without an outbound peer list. Bracketed IPv6 is supported with an explicit valid id@ prefix. Implies --federation-enable.
  • --cluster <host[:port]> on yume (client) as a friendly alias for --server + --port.
  • --public-node on yumed: hardening preset for internet-facing daemons. Rejects --allow-exec / --allow-local-ip / --control-full / --no-inner / --no-obfs, requires --auth-keys and a nonempty --obfs-secret, defaults --hide-in-the-crowd to nginx, and applies bounded session/Argon2 defaults.
  • --hide-in-the-crowd <profile> on both binaries: HTTP-layer disguise. Server profiles (nginx, nginx-stable, apache, caddy, cloudflare, express, gunicorn, none, yumed) are synthetic templates with profile-specific header order, charset, extras, and body shapes; they are not native server implementations. Client profiles (chrome, firefox, safari, edge, curl, wget, yume) set the User-Agent in stealth probes; when unspecified, the UA follows the active TLS preset.
  • Profile-driven disguise on every non-YUME probe. Pre-1.0 yumed closed the connection on HTTP probes when --real wasn’t set — TLS-handshake-then-immediate-close is a textbook DPI signal. Now serves a profile-matching 404 even without --real, by routing through Session::send_disguise_404.
  • yume-net-map new read-only CLI tool: connects to a yumed admin socket and renders the current node + its federation peers as an ASCII fan/spoke diagram (Unicode box-drawing or --ascii fallback). --json mode for downstream tooling. Auto-discovers local sockets under $XDG_RUNTIME_DIR/yume, /run/yume, /tmp/yume.
  • scripts/yume_disguise_check.py automated profile-fidelity test: spins up yumed once per profile, probes with curl, validates Server header regex / extra headers / body length range / canonical body substrings. --dpi mode adds nDPI flow classification (needs tcpdump capability and libndpi-bin). CI-runnable in <60 s.
  • scripts/yume_bench_wan.py virtual-WAN benchmark with DPI comparison: two ip netns connected by a veth + tc-netem WAN profile, runs the same workload over yume and as a curl/chromium baseline, runs ndpiReader on captured pcaps, emits a side-by-side report (markdown or JSON).
  • Native embed C ABI in libyume.so.1. include/yume/yume.h now exposes opaque yume_client, yume_server, and yume_stream handles, JSON lifecycle/status helpers, fixed-buffer last-error reporting, and direct named service stream read/write calls for C/C++ embedders.
  • Authenticated named service streams. Clients can open project-neutral proto: "service.v1" streams such as example-service-v1; servers must explicitly enable the application-defined service in config, authorize it per key with allow_services, and register it through the C ABI before accepts are queued.
  • Stream peer metadata in the native ABI. yume_stream_peer_json exposes the accepted stream’s service, authenticated Ed25519 SPKI SHA-256 fingerprint, peer/session ids, and remote address so embedders can bind sessions to their own device registry.
  • Embed JSON bind address support. yume_server_start_json accepts listen_address with listen_port, allowing loopback-only native tests and embedded local services without a wildcard bind.

Changed

Browser profiles: The withdrawn v1.0 test-release block below documents the TLS profiles shipped at 1.0. Current builds use the bumped versions listed in this section.

  • Historical release line moved to 1.1. At the time, the now-withdrawn v1.0 tag named the first test release and the tree targeted 1.1 as its first stable line.
  • BaseFWX pin currently names legacy-history 3.7.0 commit ddf31617aab6bb9ac87129295564d6736e48c601, carrying Java Argon2id support, fixed Argon2 lane defaults across runtimes, secret-hygiene hardening, and the blackbox plugin ABI surface. The SHA remains fetchable but is not on the rewritten current main/v3.7.0 lineage; choose and validate the intended canonical release commit before shipping 1.1.
  • Inner crypto secret hygiene. Introduced basefwx::crypto::SecureBytes — move-only RAII owner that wraps Bytes and SecureClears on destruction. Replaces the SecretGuard raw-pointer pattern at every KEM-touching call site in yume (server_derive_key, validate_pq_keypair) and basefwx (filecodec.cpp 4 sites, keywrap.cpp 2 sites). Eliminates a use-after-free class fixed in 66153f6 by structural change rather than placement discipline. Existing string-secret SecretGuard sites are unchanged.
  • TLS browser-profile labels corrected to match the captured shapes: Chrome 131, Firefox 126, Safari 18, and Edge 123.
  • JA4 diagnostics made canonical. Fingerprints now use FoxIO’s a_b_c format, GREASE filtering, sorted lower-case cipher/extension hex lists, SNI/ALPN exclusions, and original-order signature algorithms. Focused official-vector and match-threshold tests cover the implementation.
  • X-Yume-Blob HTTP response header removed. It had zero consumers in the tree; the substring “Yume” was a passive fingerprint for any layer-7 inspector. The anonym blob still ships in the body (zero-width <span> + HTML comment) where the actual readers look.

Fixed

  • Use-after-free on KEM secret wipe. SecretGuard stores raw Bytes* pointers and SecureClears them from its destructor. Reverse-construction-order destruction meant a SecretGuard declared BEFORE the locals it tracked accessed already-freed vector storage on scope exit. Manifested as malloc(): unaligned tcache chunk detected immediately after PQ keypair validation on --pq-auto-generate startup. Closed first by reordering (commit 66153f6), then by structural replacement with SecureBytes (commit 58c39a7).
  • Pre-existing yume_server link break from the f6db161 session.cpp split: epoch_now_ms was anonymous-namespace-local in session.cpp but referenced from session_control.cpp. Exposed at file scope in session.hpp; duplicate copy in federation_link.cpp removed.
  • Argon2 admission was not aggregate. argon2_env_limits() previously returned {0,0,0} by default, making the parameter guard a no-op. It now seeds per-derivation ceilings (time=12, memory=512 MiB, parallelism=8), and positive environment values may deliberately lower or raise them. The server additionally reserves each Argon2 derivation against manager-owned aggregate memory/job limits before allocation; move-only RAII leases release accounting on all exits. Defaults are 512 MiB aggregate and four jobs, configurable through CLI or JSON. The unused has_argon2_limits gate was removed. The client also checked its local cap in that implementation.

[withdrawn v1.0] - 2026-05-16

Former tag commit: https://github.com/FixCraft-Inc/yume/commit/82735dc12b17e7bc72592e32e74f52afd4b46247

Added

  • First public test release of YUME (Yume Universal Multiprotocol Engine) — an open-source post-quantum stealth transport that tunnels TCP and UDP through real TLS 1.3 sessions using the project’s browser-oriented presets. The client (yume), daemon (yumed), proxy, GUI, and libyume surface are AGPL-3.0-or-later and build from this tree.
  • Three-layer stealth stack stacked on top of TLS 1.3, all on by default and toggleable independently:
    • Browser-oriented JA3 shaping plus the original project-local, pre-canonical JA4-like diagnostic via genuine OpenSSL 3.5 ClientHello configuration. --profile chrome (Chrome 131) is the default; --profile firefox (Firefox 126) and --profile safari (Safari 18) are selectable, plus per-N-connection rotation via --tls-stealth-rotate / --tls-stealth-rotation-interval.
    • HTTP/2 carrier handshake (--obfs) with the then-current project SETTINGS, WINDOW_UPDATE, and a HEADERS frame opening a POST to /<token>/<nonce>. The token is HMAC-SHA256(K, sni || hour_epoch || "yume-obfs-v2") truncated to 16 bytes hex; 1.0 allowed optional peer-pinning via --obfs-secret and accepted ±1 hour of clock skew.
    • Real HTML facade (--real) so a browser hitting the same :443 with GET / HTTP/1.1 is served a real HTML page (or a Wikipedia redirect by default). YUME and a normal website coexist on a single port.
  • Post-quantum KEM/DEM inner crypto via BaseFWX 3.6.4 (separate library; current dependency metadata is centralized in config/dependencies.json):
    • ML-KEM-768 (NIST FIPS 203 / Kyber-768) encapsulation for session keys when a master public key is configured.
    • AES-256-GCM AEAD with a 12-byte nonce and 16-byte tag.
    • Argon2id or PBKDF2-HMAC-SHA256 as an optional work factor over the high-entropy ML-KEM shared secret. The YUME transport handshake does not mix in a user password / PSK.
    • HKDF-SHA256 for all subkey derivation.
    • Ed25519 for client authentication via --auth / --auth-keys.
  • Live key hopping at 1–4 Hz (--hop-interval, default ~500 ms). Each window encrypts with an HKDF(master, hop_index) derivative. This separates derived hop keys but is not forward secrecy: compromise of the retained master reveals every window. Disable for latency-sensitive paths with --no-hop.
  • Five routing modes, combinable:
    • --socks [addr:]port — SOCKS5 listener on the client side.
    • Local TCP/UDP forward (--proxy host:port -> local:port).
    • --run <cmd …> — spawn a command and pipe its stdio through the tunnel.
    • Android VPN capture (separate yume4a APK that captures all OS-level traffic).
    • Server-side reverse port tunneling via yumed --reverse-port-min / --reverse-port-max.
  • Server-side egress options: direct, Tor outbound (yumed --proxy tor://…, optional obfs4 bridge), and yumed-to-yumed federation links over mutual TLS 1.3 (--federation-enable, --federation-auth-key, --federation-anonym-ca, --peer).
  • Anonym / no-log mode (yumed --anonym) for run-by-third-party endpoints: drops connection-level logs before disk, decoupled CA / sub-key flow (--anonym-ca-key, --anonym-sub-key), optional --anonym-token rate-limiting that doesn’t tie tokens back to user identities, operator key management via --operator-keys.
  • Optional Dear ImGui-based desktop GUI (yume-gui) for Linux, macOS, and Windows. Ships as a portable single-file .exe on Windows (static MinGW runtime + x64-mingw-static vcpkg triplet).
  • 9-target release matrix: linux-amd64 (dynamic + static), linux-armv7, linux-armv8, busybox-amd64-static, busybox-armv7-static, busybox-armv8-static, openwrt-mips (maintainer-attached), macos-arm64, windows-amd64 (CLI tarball + portable GUI .exe). Android via yume4a (~28 MiB arm64-v8a APK).
  • Minimal footprint: the static BusyBox build runs on routers with as little as 128 MiB of RAM and no glibc.
  • Release pipeline with full pre-publish self-test:
    • light-tests host-build + yume --version / yumed --version smoke pre-gate.
    • GUI cross-builds for the three desktop OSes (continue-on-error so a GUI dep regression doesn’t block CLI release).
    • Static-link assertion on every *-static artifact (file must not say “dynamically linked”; readelf -d must show no NEEDED entries).
    • --version self-test on every published binary (native amd64 directly, ARM via qemu-aarch64-static/qemu-arm-static, MIPS via qemu-mips-static, PE via wine if available else PE32 header check, tar.xz CLI bundles extracted and tested).
    • GPG-signed *.sig per artifact plus aggregate SHA256SUMS.txt, MD5SUMS.txt, release-manifest.json.
  • Project documentation included the main README, setup, security, routing, operations, packaging, permissions, release notes, and project website.

Changed

  • The 1.0 test build included its wire, authentication-key, anonym CA/sub-key, and yume-obfs-v2 token formats. The proposed 1.1 stable line was withdrawn.
  • Server-to-server federation links are pinned to TLS 1.3 only (no fallback to earlier TLS versions) — see commit f13fbdb.
  • Build pipeline rewritten end-to-end vs the BETA cycle: smoke-gate before heavy builds, GUI added for desktop OSes, dynamic “busybox” artifacts dropped (they were misleadingly named — a glibc-dynamic binary can’t run on a real busybox/musl target), only verified-static *-busybox-static ships, macOS job is matrix-ized so Intel can be added in a follow-up by uncommenting one matrix entry.
  • Website’s assetMap aligned with the new release-artifact set: GUI download cards for Linux / macOS / Windows added; dynamic-busybox cards dropped.

Fixed

  • Federation TLS regression: an earlier commit used the wrong TLS-method constant (tls_client instead of tlsv13_client); pinned in f13fbdb.
  • 240-commit history through the ALPHA → BETA cycle accumulated and addressed: TLS-fingerprint correctness, HTTP/2 obfs token rotation, key-hop window seam handling, federation handshake edge cases, anonym-mode metadata leaks, Android VPN capture restart bugs, Windows MinGW cross-build deps, BusyBox static-link reliability across glibc/musl toolchains, GUI font rendering on Wayland vs X11 (URW Gothic Demi double-bold fix), CodeQL high-severity findings, OpenWRT SDK feed wiring, and assorted CI/website hash-rendering bugs.

Notes

  • Threat model recap (full version in docs/STEALTH.md and docs/EXPLAINED.md): YUME defends the transport. The route you choose decides who can see the client, who can see the target, and how much trust is placed in the YUME server. YUME does not by itself provide anonymity — combine with Tor egress, Tor-over-YUME, or YUME-Tor-YUME for that.
  • OpenWRT MIPS is intentionally not built in CI because cross-builds against the OpenWRT SDK are slow and brittle on hosted runners; maintainers attach the MIPS artifacts manually when a release is cut. The static BusyBox builds cover most embedded use.
  • Intel macOS had no 1.0 build. Rosetta 2 runs x86-64 software on Apple silicon; it cannot run an arm64 binary on an Intel Mac.
  • Windows GUI cross-build is best-effort in 1.0: marked continue-on-error: true because the GUI-specific vcpkg packages (Freetype, GLFW3) on a fresh runner can take significantly longer than the CLI path and may time out. If the cross-build fails the CLI tarball still ships and the GUI lands in a follow-up.
  • Performance: one April 2026 WAN run measured about 234 Mbps download and 36 Mbps upload on the 1.x SOCKS path. CPU cost was not isolated, so the prior “<1 % typical, <5 % always” wording was not supported by that dataset. This historical result does not describe the current transport.
  • The proposed 1.x compatibility policy was withdrawn with that development line. Current transport contracts are documented separately.
  • License: AGPL-3.0-or-later across YUME binaries, source, and libyume. Bundled BaseFWX code follows its own split policy: LGPL library/API/runtime, GPL standalone tools, MIT OR Apache-2.0 example plugin templates. See LICENSE.