v0.41.0-beta.6
Pre-release-
Desktop restore discovers reachable workspace sources and supports an explicit server address when automatic discovery does not find one.
-
Security audit fixes (
planning/security-audit-2026-09.md, #1384).
POST /iroh-syncrequires a signed agent with write on the drive and only
answers POST;SYNC_PUSHentries are checked against the admitted drive;
https://host/agents/{key}subjects bind the path key to the signing key
and are never fetched from other hosts; a genesis can no longer append to
a parent through its ownwritearray, squat another agent's
did:ad:agent:resource, or reserve top-level server paths; the request
origin used for token binding honoursHost/X-Forwarded-*only for the
configured domain, its tenants and loopback; IrohAUTHproofs name the
responder node (drive#nodeId, unbound proofs accepted only from paired
peers);/forget-peerneeds write on a drive the peer was paired for;
path-only auth signatures are refused; plugin zips and bookmark bodies go
through the SSRF guard with size caps; blob responses are hash-checked;
ephemeral relays require a subscriber; ACME errors instead of panicking
and certificates are renewed daily on disk (restart to serve); the
desktop and Android builds bind127.0.0.1;config.tomlis written
0600; wasmtime 47. API change:/plugin-listand the plugin UI files are
read as the calling agent, so clients must sign that request (the
data-browser does) or carry the session cookie. -
Fix: live collaboration stopped for the author of a document after a peer
edited it. The server stampslastCommitunder its own Loro peer after
applying a commit; the fan-out only forwarded the author's own bytes and
never echoed to the author, so an edit built on the stored snapshot could
not apply there. TheUPDATEnow carries everything the apply added and
reaches the author too (CommitResponse::fanout_delta). -
Commits are signed envelopes, not a queryable event log. Ordinary content
commits are not stored as resources after apply (genesis and
rights/parent/destroy stay). Loro binaries are not KV-index keys. The
/commitscollection is not created. UI reads author/dates from the
resource, not by fetchingdid:ad:commit:rows. A creation commit is
retained whether or not the client flaggedisGenesis. Clients no longer chain
previousCommit; apply no longer has a previous-commit validation gate.
History no longer offers a "Show Commit" link at a discarded envelope. -
Signed envelopes live on the resource (
Tree::Envelopes). Every
signed commit's JSON-AD is kept per resource, keyed by createdAt and
signature, in the same transaction as the state it signs. Not a
resource, not indexed.--envelope-retention/ATOMIC_ENVELOPE_RETENTION
islatest(the envelope that produced the current state; default) or
all(every envelope: a signed audit log).GET /history-attribution?subject=
(read-gated) answers who signed which Loro change, verified with the
apply code, plus whether every change is covered. Rust builder commits
andcreate_didnow tag their Loro change like the browser does, so
History maps versions to signers. The destroy envelope on the tombstone
(added forSYNC_DIFF.removeCommits) is now the subject's latest row
in this tree. Envelopes do not yet travel in bulk sync or the vault. -
Missing-drive bootstrap is no longer a free pass (OQ5). A
SYNC_PUSHor live write for a drive this node has never stored goes
throughadmit_unknown_drive:Publicnever creates one (even on
OpenPolicy); Owner mode enrolls only the owner; an authenticated
first-sync on an open node still works. AUTH-before-SYNC_PUSHalready
closed the unauthenticated wire; this closes the library path. -
SUB/UNSUBare engine-owned. Parse andcheck_readlive in
handle_frame_full; the WebSocket handler registers the connection
with the commit monitor only when the engine admits the subscription.
The0x20/0x21wire is unchanged (anonymousSUBon a public
share link still works). -
One subscription actor.
LoroSyncBroadcasteris gone; Loro
ephemera and drive presence fan out fromCommitMonitor(one mailbox,
oneUnsubscribeAllon socket close). Wire and behaviour unchanged. -
Bulk
SYNC_DIFF.removecan carry a signed destroy. When the sender
still holds the destroy commit on the tombstone,removeCommitsmaps
that subject to the JSON-AD envelope and the receiver applies it as a
peerCOMMIT. A bad signature does not fall back to the unsigned
tombstone path. Senders without the envelope still send a subject-only
remove[]entry (admission-gated). The envelope is only handed to a
session that may read the drive; a signed destroy that is already
stored here, or that predates the current resource's genesis, is
refused as a replay. The browser applies the envelope as a local-cache
write and removes the resource either way. Requiring an envelope on
every delete still waits onTree::Envelopes. -
AtomicTransport/SyncSessionfirst slice. The engine loop is
callable over any byte-pipe (lib/src/sync/transport.rs,
SyncSession::serve). Iroh and WebSocket still have their own
lifecycles; the outbox port and FRBopen_sync_sessionare not this
change.