Skip to content

v0.6.0 — the Shrine surface reviewed, and two consumer bugs closed

Choose a tag to compare

@github-actions github-actions released this 29 Aug 07:01
· 2 commits to main since this release

The standing security review was dated 2026-07-20 and scoped to "the Rites (Epistle/Conduit/Reliquary)" — it predated Signet, the Pilgrimage, the site rites, the listener and multi-Shrine entirely. This release is that review's findings, plus the two issues raised against 0.5.0.

⚠️ Breaking for IDataChannel adapters

IDataChannel gains int MaxMessageBytes. Return 0 for unknown or unbounded and nothing changes. Only affects code implementing that interface — an IVessel consumer is unaffected.

A Shrine can no longer be held open by saying nothing · High

The serious finding. A visit slot was taken before the accept and released only when the whole visit ended, and nothing ended a visit but the peer — no idle deadline anywhere. One address could complete MaxConcurrentPilgrimages handshakes, go quiet, and hold every slot for as long as it liked. Cost to the attacker: that many Toll solves. Cost to the site: total unavailability, with no detection and no recovery.

New option Default What it bounds
PilgrimageIdleTimeout 5 min A visit quiet in both directions
MaxPilgrimagesPerAddress 8 Concurrent visits per source, checked before the handshake

Both directions matters: inbound-only would have been the obvious implementation and would have hung up on the Auspice, where a Pilgrim attends a feed and then only listens. There's a test asserting a feed keeps its visit alive well past the deadline.

The overlay path has held a global cap and a per-peer budget since the first review; the Shrine listener was modelled on that loop's shape without carrying over its Wards.

The subnet fence is containment, and now holds everywhere

AllowedSubnets documents itself as "subnets this node is allowed to connect to and accept from", and beacon dialling was already fenced. The Shrine paths were not. Both AcceptPilgrimageOverVesselAsync overloads and PilgrimageOverVesselAsync now apply it, so an outbound visit is contained too. A vessel with no IP endpoint still passes — nothing to filter on, as with an onion or hostname beacon.

Closing a session never throws · #5

DisposeAsync wasn't idempotent, and a caller reached that without disposing twice: the far side tears the session down, and their single explicit dispose lands on something already gone.

Fixed in five types, not the two reported — ArcanumSession had the identical one-line body and the same bug on the channel path, and Vessel and NoiseVessel were no better.

A transport too small for the rites says so at connect · #6

A rite's ceiling is a constant of this library; what a DataChannel carries is negotiated per association. Nothing reconciled them — DataChannelVessel emits one message per frame and never fragments — so a peer negotiating below 192 KiB would have a legal frame refused on the wire.

RiteTransport.EnsureCarriesRiteFrames now refuses such a transport when you connect, naming both numbers. Same bargain as every other ceiling: fail at the rite with a reason, never deep inside a transport. Byte-stream vessels set their own frame size and aren't subject to it.

The CupriWebRTC adapter reports 0 today. The negotiated value lives on SctpAssociation, and WebRtcChannel does not expose it, so the adapter cannot read what the two ends agreed. Reporting 0 is the honest answer rather than a guess — refusing on a number we can't see would break every pairing that currently works. Once CupriWebRTC surfaces it, returning it from the adapter is the whole of what's left.

Also

  • A node hosting several Shrines publishes them with one link each (Intone(..., signet)), rather than one link naming them all — which would tell anyone holding it that those sites are co-hosted.
  • ResolveShrine's loop always runs to the end; returning on first match let a visitor time how many sites an endpoint hosts.
  • Correction: transports-and-limits.md called the WebRTC 256 KiB "fixed". It is what this stack offers; the effective limit is the negotiated minimum, and the interoperable floor is 64 KiB. That mattered because the surrounding advice was given on the strength of it.

466 unit tests green.