Build2 publishers3 min readPublished
Every Radicle node through 1.10.3 sends private repository data unencrypted after the handshake
Radicle's node software never uses the session keys its Noise handshake creates, so private Git data crosses the network in cleartext. No release through 1.10.3 is patched, and teams should treat anything already fetched over clearnet as public.
The Engineer · Build desk

What happened
- On a live connection to a production seed, the first inbound byte after the handshake, at offset 32, is the ASCII 'r' of 'rad'.
- Frames sent after the handshake also carry no transport MAC, so anyone on the path can alter, drop or reorder them.
- Radicle disclosed a second flaw in which broken peer authentication lets an attacker spoof a Node ID and read private repositories.
- Radicle 2.0, still unreleased, is supposed to replace the current transport with QUIC and TLS.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Every private repository fetched or seeded over clearnet was readable to the ISP, transit provider or relay on its path, so any password, token or key it held needs rotating.
- contradiction Maninakis's post scopes the bug to confidentiality and treats the handshake as working proof of identity, while Radicle's account says a captured Node ID can be replayed, so the blog alone understates the risk.
- decision Because 1.x and 2.x nodes will not connect, a team cannot move to the fixed transport one node at a time without splitting its own network.
- constraint Since no setting or allow-list stops an on-path attacker, the only protections before 2.0 sit below Radicle, in tunnels or Tor and i2p routing.
Kostis Maninakis found the flaw the awkward way, by implementing the protocol correctly [6]. Radicle has no published byte-level wire spec. He spent a spring and summer reading heartwood's source line by line and rewriting it in TypeScript, byte for byte, so he could run a node inside a browser tab [5]. "My first transport implementation did what Noise says and encrypted everything past the peer-to-peer handshake. But as it turned out, nobody was talking to me," he wrote [6]. Not one real node in the wild could understand his encrypted channel [7].
The cause is in radicle-node's connection code. Every connection opens with a Noise handshake from the protocol family WireGuard uses, and it proves the peer holds the private key it claims [10]. Both ends finish with a pair of session keys. Radicle-node never uses them after that point, and each later message goes straight to the plain TCP socket [1]. Gossip, repository ids, the addresses of other nodes and the Git objects of private repositories all travel readable [2].
The content layer holds up better. An attacker on the path still cannot slip in altered repository contents unnoticed, because announcements are signed, Git objects are content-addressed, and signed references sit on top of both [9]. That layering is careful design. Maninakis cites it as the reason his disclosure is about confidentiality and not integrity [9].
Radicle's own account, as LWN summarizes it, widens the damage. An on-path attacker sees the Node IDs at both ends of a connection, and both are normally on the allow-list [12]. After reading whatever passes while watching, the attacker can present one of those IDs and fetch the whole repository on demand [12]. The Radicle team's description of the transport is that it "does not give the confidentiality it was expected to give" [22].
A later patch cannot recall what already crossed the wire. "We are publishing this before the security update is available. You can act on it today, and no fix we release later can undo an exposure that has already happened," the Radicle team wrote [13]. Marking a repository private limits who may fetch it from a seed. It does not change what an observer hears while allowed peers fetch it [16].
For a team that runs its own seed, I'd put every hop between laptops and that seed inside WireGuard, a VPN or an SSH tunnel now. It adds nobody new in the middle and works with any Radicle version [18]. Radicle-node 1.10.3 can also send connections to onion peers through a local Tor daemon's SOCKS port, with one block in ~/.radicle/config.json [19]:
``` { "node": { "onion": { "mode": "proxy", "address": "127.0.0.1:9050" } } } ```
Port 9050 is Tor's default, and node.i2p takes the same shape pointed at an i2p router's SOCKS port [19]. Both settings default to "mode": "drop", and that default skips onion and i2p peers entirely [19]. The Tor route covers only connections to onion peers, so for a clearnet seed the tunnel is the option that applies [19].
What to watch
- A RustSec advisory, which Maninakis says may follow the coordinated disclosure.
- A release date for Radicle 2.0, and whether anything lets 1.x and 2.x nodes interoperate during a migration.
- Whether the Node ID impersonation flaw is fixed on the 1.x line or only arrives with the new QUIC and TLS transport.