refactor: pure core for admission and authorization, and expose requires a grantee - #10
Merged
Conversation
cablehead
marked this pull request as ready for review
August 18, 2026 15:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extracts the security decisions from
peer.rsinto a pure core, and builds atest suite around them. 19 tests to 52, all under a second, no network.
Includes one behaviour change:
exposeno longer grants to every known peerwhen you leave
--tooff. See step 3.Why
peer.rsis 1010 lines and holds every security decision in the system. It hadno tests, because testing it meant standing up two daemons, two iroh endpoints,
a relay, and a network.
The 19 existing tests all lived in
grants,enroll,surface, andresolver. Those are the four modules that happen to be pure. Deleting theauthorization check at
peer.rs:370would have kept the suite green.Where the test list came from
docs/scenarios.mdis added here. It works two flows end to end, a shared buildbox and a laptop booting a vibenv, and states what has to be true for each. Four
of those statements became the test list:
Writing it is also what turned up the ambient grant in
expose. The filedescribes a proposed CLI (
invite/accept) that nothing implements yet, and ismarked as such. That rename is separate work.
What changed
1. Injectable endpoint.
Daemon::newhardcodedpresets::N0: public relaysand n0 DNS discovery, so any test that built a daemon reached the network. Split
into
new(production behaviour unchanged) andwith_endpoint, which takes anendpoint the caller already bound.
2. Pure core.
src/core/session.rsowns the grant table, the enrollmenttokens, and the admitted peer set. It performs no IO: methods take an event and
return actions the shell carries out. Four decisions moved across:
peer.rsno longer holdsGrantsorTokens. 20 unit tests cover thedecisions directly, in microseconds.
3.
exposerequires--toor--all. It used to default to every knownpeer, which is a default-allow inside a system whose ADR opens with "default
deny, no grant, no access". Adding a peer so you could reach its ports silently
made it a recipient of everything you exposed later.
--allstill exists andstill means every peer known at that moment, not a standing rule. Six parse
tests pin the argument group, including that repeated
--tois accepted and--to x --allis not.4. Live tests over loopback. Seven tests run two real daemons with relays
disabled and a
MemoryLookupseeded with each other's bound sockets. They coverwhat the pure core cannot reach: bytes crossing a granted tunnel, revocation
tearing a binding down, an unenrolled peer being refused, and
--hostbeinghonoured.
Two bugs found on the way
A peer added while its daemon was down was lost.
add_peerdialed beforecreating any record, so a failed dial left nothing behind: no peer, no retry
loop.
docs/scenarios.mdpromises the opposite. Fixed by recording first andletting the connection loop dial, which also made the first dial immediate
instead of waiting out a backoff.
A transient bind failure left a port dark forever.
bind_onelogged anddropped on the theory that the next announce would retry, but a peer whose port
set does not change never re-announces. Now retried five times, 200ms apart.
Verification
52 tests, 0.55s, 10 consecutive runs with no flakes.
The suite was mutation-checked rather than assumed. Replacing the grant check
with
if truefails 5 tests. SettingBIND_ATTEMPTSto 1 fails the bind-retrytest. Both were confirmed, then reverted.
Zero egress is inferred from iroh's config surface (relays disabled, address
lookup cleared, port mapping disabled), not observed:
unshare -nwas notpermitted in this environment and
stracewas absent.Not in scope
The CLI rename worked out in
docs/scenarios.md(invite/accept, droppingticketandlabel) is separate work. This branch keeps today's command namesapart from the
exposegrant change.Phase 2 of the sans-io migration is also separate: moving reconciliation and
projection into the core, which is what allows
bind_lockto be deleted.