Completed Work
Phase 1: Configuration Infrastructure
Goal: Externalize all hardcoded values to YAML configuration files.
Deliverables:
-
internal/configpackage for loading YAML configs - Sample configuration files in
configs/ - Updated
.gitignorefor config files - Refactored home-node/client-node/relay-server to use configs
Key Files:
internal/config/config.go- Configuration structsinternal/config/loader.go- YAML parsingconfigs/*.sample.yaml- Sample configurations
Phase 2: Key-Based Authentication
Goal: Implement SSH-style authentication using ConnectionGater and authorized_keys files.
Deliverables:
-
internal/auth/gater.go- ConnectionGater implementation (primary defense) -
internal/auth/authorized_keys.go- Parser for authorized_keys - Integration into home-node and client-node
- Protocol-level validation (defense-in-depth)
- Relay server authentication (optional)
Security Model:
- Layer 1: ConnectionGater (network level - earliest rejection)
- Layer 2: Protocol handler validation (application level - secondary check)
Phase 3: Enhanced Usability - keytool CLI (superseded)
Goal: Create production-ready CLI tool for managing Ed25519 keypairs and authorized_keys.
Status: Completed (keytool features merged into shurli subcommands in Phase 4C module consolidation; cmd/keytool/ deleted)
All keytool functionality now lives in shurli subcommands: shurli whoami (peerid), shurli auth add (authorize), shurli auth remove (revoke), shurli auth list, shurli auth validate (validate). Key generation happens via shurli init.
Phase 4A: Core Library & Service Registry
Goal: Transform Shurli into a reusable library and enable exposing local services through P2P connections.
Deliverables:
- Create
pkg/sdk/as importable package-
network.go- Core P2P network setup, relay helpers, name resolution -
service.go- Service registry and management -
proxy.go- Bidirectional TCP-to-Stream proxy with half-close -
naming.go- Local name resolution (name to peer ID) -
identity.go- Ed25519 identity management
-
- Extend config structs for service definitions
- Update sample YAML configs with service examples
- Refactor to
cmd/layout with single Go module - Tested: SSH, XRDP, generic TCP proxy all working across LAN and 5G
- UX Streamlining:
- Single binary - merged home-node into
shurli daemon - Standard config path - auto-discovery (
./shurli.yaml->~/.shurli/config.yaml->/etc/shurli/config.yaml) -
shurli init- interactive setup wizard (generates config, keys, authorized_keys) - All commands support
--config <path>flag - Unified config type (one config format for all modes)
- Single binary - merged home-node into
Phase 4B: Frictionless Onboarding
Goal: Eliminate manual key exchange and config editing. Get two machines connected in under 60 seconds.
Deliverables:
-
shurli invite- generate short-lived invite code (encodes relay address + peer ID) -
shurli join <code>- accept invite, exchange keys, auto-configure, connect - QR code output for
shurli invite(scannable by mobile app later) -
shurli whoami- show own peer ID and friendly name for sharing -
shurli auth add/list/remove- manage authorized peers -
shurli relay add/list/remove- manage relay addresses without editing YAML - Flexible relay address input - accept
IP:PORTor bareIP(default port 7777) in addition to full multiaddr - QR code display in
shurli init(peer ID) andshurli invite(invite code)
Security hardening (done as part of 4B):
- Sanitize authorized_keys comments (prevent newline injection)
- Sanitize YAML names from remote peers (prevent config injection)
- Limit invite/join stream reads to 512 bytes (prevent OOM DoS)
- Validate multiaddr before writing to config YAML
- Use
os.CreateTempfor atomic writes (prevent symlink attacks) - Reject hostnames in relay input - only IP addresses accepted (no DNS resolution / SSRF)
- Config files written with 0600 permissions
User Experience:
# Machine A (home server)
$ shurli invite --as home
=== Invite Code (expires in 10m0s) ===
AEQB-XJKZ-M4NP-...
[QR code displayed]
Waiting for peer to join...
# Machine B (laptop)
$ shurli join AEQB-XJKZ-M4NP-... --as laptop
=== Joined successfully! ===
Peer "home" authorized and added to names.
Try: shurli ping homePhase 4C: Core Hardening & Security
Goal: Harden every component for production reliability. Fix critical security gaps, add self-healing resilience, implement test coverage, and make the system recover from failures automatically.
Security (Critical)
- Relay resource limits - replace
WithInfiniteLimits()with configurableWithResources()+WithLimit(). Defaults tuned for SSH/XRDP (10min sessions, 64MB data). - Auth hot-reload - daemon API
POST /v1/authandDELETE /v1/auth/{peer_id}reloadauthorized_keysat runtime. - Per-service access control -
AllowedPeersfield on each service restricts which peers can connect. - Rate limiting on incoming connections and streams - libp2p ResourceManager enabled. OS-level: iptables SYN flood protection (50/s) and UDP rate limiting (200/s).
- QUIC source address verification - reverse path filtering (rp_filter=1), SYN cookies for TCP flood protection.
- Key file permission check on load - refuse to load keys with permissions wider than 0600
- Service name validation - DNS-label format enforced (1-63 lowercase alphanumeric + hyphens)
libp2p Upgrade (Critical)
- go-libp2p v0.47.0 - AutoNAT v2, smart dialing, QUIC improvements, Resource Manager
- AutoNAT v2 - per-address reachability testing with nonce-based dial verification
- Smart dialing - address ranking, QUIC prioritization, sequential dial with fast failover
- QUIC as preferred transport - 1 fewer RTT on connection setup (3 RTTs vs 4 for TCP)
- Version in Identify -
libp2p.UserAgent("shurli/<version>")set on all hosts - Private DHT - migrated from IPFS Amino DHT to private shurli DHT (
/shurli/kad/1.0.0)
Self-Healing & Resilience
Inspired by Juniper JunOS, Cisco IOS, Kubernetes, systemd, MikroTik:
- Config validation -
shurli config validateparses config, checks key file, verifies relay address - Config archive - auto-saves last-known-good config on successful startup. Atomic write.
- Config rollback -
shurli config rollbackrestores from last-known-good archive - Commit-confirmed pattern (Juniper JunOS / Cisco IOS) -
shurli config apply <new-config> --confirm-timeout 5mapplies config and auto-reverts if not confirmed. Prevents permanent lockout on remote relay. - systemd watchdog integration -
sd_notify("WATCHDOG=1")every 30s with health check - Health check HTTP endpoint - relay exposes
/healthzwith JSON: peer ID, version, uptime, connected peers -
shurli statuscommand - version, peer ID, config path, relay addresses, authorized peers, services, names
Batch Deliverables
Batch A - Reliability:
-
DialWithRetry()- exponential backoff retry (1s -> 2s -> 4s) for proxy dial - TCP dial timeout - 10s for local service, 30s context for P2P stream
- DHT bootstrap in proxy command - Kademlia DHT (client mode) for direct peer discovery
-
[DIRECT]/[RELAYED]connection path indicators in logs - DCUtR hole-punch event tracer
Batch B - Code Quality:
- Deduplicated bidirectional proxy -
BidirectionalProxy()+HalfCloseConninterface (was 4 copies, now 1) - Sentinel errors - 8 sentinel errors across 4 packages
- Build version embedding -
shurli version, ldflags injection - Structured logging with
log/slog
Batch E - New Capabilities:
-
shurli status- local-only info command -
/healthzHTTP endpoint on relay-server -
shurli invite --non-interactive- bare invite code to stdout, progress to stderr -
shurli join --non-interactive- reads code from CLI arg, env var, or stdin
Batch F - Daemon Mode:
-
shurli daemon- long-running P2P host with Unix socket HTTP API - Cookie-based authentication (32-byte random hex,
0600permissions, rotated per restart) - 18 API endpoints with JSON + plain text format negotiation
- Auth hot-reload, dynamic proxy management
- P2P ping, traceroute, resolve - standalone + daemon API
- Service files: systemd + launchd
Batch G - Test Coverage & Documentation: Combined coverage: 80.3% (unit + Docker integration). Relay-server binary merged into shurli.
- 96 test functions covering CLI commands
- All 18 API handlers tested
- Docker integration tests with coverage
- Engineering journal with 43 ADRs
- Website with Hugo + Hextra, 10 blog posts, 40+ SVG diagrams
Batch H - Observability:
- Prometheus
/metricsendpoint (opt-in via config) - libp2p built-in metrics exposed (swarm, hole-punch, AutoNAT, relay, rcmgr)
- Custom shurli metrics (proxy bytes/connections/duration, auth counters, hole-punch stats, API timing)
- Audit logging - structured JSON via slog for security events
- Grafana dashboard - 56 panels across 11 sections
Pre-Batch I Items
Pre-I-a: Build & Deployment Tooling:
- Makefile with build, test, clean, install, service management
- Service install for Linux (systemd) and macOS (launchd)
-
make check- generic local checks runner from.checksfile -
make push- runs checks before git push
Pre-I-b: PAKE-Secured Invite/Join Handshake: Upgraded the invite/join token exchange from cleartext to an encrypted handshake inspired by WPA3’s SAE. The relay sees only opaque encrypted bytes during pairing. Zero new dependencies.
- Ephemeral X25519 DH + token-bound HKDF-SHA256 key derivation + XChaCha20-Poly1305 AEAD
- Invite versioning: v1 = PAKE-encrypted, v2 = relay pairing code
- v2 invite codes encode namespace for DHT network auto-inheritance
- 19 PAKE tests + 11 invite code tests
Pre-I-c: Private DHT Networks:
- Config option:
discovery.network: "my-crew"for isolated peer groups - DHT prefix becomes
/shurli/<namespace>/kad/1.0.0 - Nodes with different namespaces speak different protocols and cannot discover each other
- Validation: DNS-label safe (lowercase alphanumeric + hyphens, 1-63 chars)
Batch I: Adaptive Multi-Interface Path Selection
Probes all available network interfaces at startup, tests each path to peers, picks the best, and continuously monitors for network changes. Path ranking: direct IPv6 > direct IPv4 > STUN-punched > peer relay > VPS relay. Zero new dependencies.
- I-a: Interface Discovery & IPv6 Awareness -
DiscoverInterfaces()enumerates all network interfaces with global unicast classification - I-b: Parallel Dial Racing - parallel racing replaces sequential 45s worst-case. First success wins.
- I-c: Path Quality Visibility -
PathTrackerwith per-peer path info: type, transport, IP version, RTT.GET /v1/pathsAPI endpoint. - I-d: Network Change Monitoring - event-driven detection of interface/address changes with callbacks
- I-e: STUN-Assisted Hole-Punching - zero-dependency RFC 5389 STUN client. NAT type classification (none/full-cone/address-restricted/port-restricted/symmetric).
- I-f: Every-Peer-Is-A-Relay - any peer with a global IP auto-enables circuit relay v2 with conservative limits
Post-I-1: Frictionless Relay Pairing
Eliminates manual SSH + peer ID exchange for relay onboarding. Relay admin generates pairing codes, each person joins with one command.
- v1 cleartext deleted - zero downgrade surface
- Extended authorized_keys format - key=value attributes:
expires=<RFC3339>,verified=sha256:<prefix> - In-memory token store (relay-side) - SHA-256 hashed tokens, constant-time comparison, max 3 failed attempts before burn
- v2 invite code format - 16-byte token, relay address + namespace encoded. Shorter than v1 (126 vs 186 chars)
- Connection gater enrollment mode - probationary peers (max 10, 15s timeout) during active pairing
- SAS verification (OMEMO-style) - 4-emoji + 6-digit numeric fingerprint. Persistent
[UNVERIFIED]badge until verified. - Relay pairing protocol -
/shurli/relay-pair/1.0.0stream protocol. 8-step flow. -
shurli relay pair- generates pairing codes with--count N,--ttl,--namespace,--expires - Daemon-first commands -
shurli pingandshurli traceroutetry daemon API first, fall back to standalone - Reachability grade - A (public IPv6), B (public IPv4 or hole-punchable NAT), C (port-restricted NAT), D (symmetric NAT/CGNAT), F (offline)
Zero new dependencies. Binary size unchanged at 28MB.
Post-I-2: Peer Introduction Protocol
Relay-mediated peer introduction with HMAC group commitment. When a new peer joins via relay pairing, the relay pushes introductions to existing peers in the same group.
-
/shurli/peer-notify/1.0.0protocol for relay-pushed introductions - HMAC-SHA256(token, groupID) proves token possession during pairing
- Relay notifies all group members when a new peer joins
Pre-Phase 5 Hardening
8 cross-network bug fixes discovered during live hardware testing. Fixed before Phase 5 implementation.
- 5 NoDaemon test isolation fixes (tests conflicting with live daemon)
- 3 stale Homebrew tap references updated (satindergrewal -> shurlinet)
- Cross-network connectivity verified across 3 machines
Phase 5: Network Intelligence
Smarter peer discovery, lifecycle management, and network-wide presence. Three sub-phases: mDNS for instant LAN discovery, PeerManager for reliable reconnection, and NetIntel for presence announcements with gossip forwarding.
5-K: mDNS Local Discovery
Zero-config peer discovery on the local network. When two Shurli nodes are on the same LAN, mDNS finds them without DHT lookups or relay bootstrap. Uses platform-native DNS-SD APIs (dns_sd.h via CGo on macOS/Linux) to cooperate with the system mDNS daemon instead of competing for the multicast socket.
- zeroconf.RegisterProxy for mDNS service advertising (dnsaddr= TXT records)
- Platform-native browse via dns_sd.h (mDNSResponder on macOS, avahi on Linux)
- Zeroconf fallback for Windows, FreeBSD, and CGO_ENABLED=0 builds
- mDNS-discovered peers checked against authorized_keys (ConnectionGater enforces)
- Config option:
discovery.mdns_enabled: true(default: true) - Explicit DHT routing table refresh on network change events
- Dedup, semaphore-limited concurrent connects, 10-minute address TTL
5-L: PeerManager
Background reconnection of authorized peers with exponential backoff. Watches the authorized_keys file for changes and maintains connections to all known peers.
- Periodic sweep of authorized peers with configurable interval (default: 30s)
- Exponential backoff per peer (30s -> 60s -> 120s -> 300s max)
- Authorized_keys file watcher with debounced reload
- Graceful shutdown with in-flight connection draining
5-M: NetIntel (Network Intelligence Presence)
Lightweight presence protocol using direct streams. Each peer publishes its presence (addresses, capabilities, uptime) to connected peers at regular intervals. Gossip forwarding with TTL propagates presence through the network without requiring direct connections to every peer.
-
/shurli/netintel/1.0.0stream protocol for presence announcements - Periodic publish (default: 5 minutes) with immediate publish on address change
- Gossip forwarding: fanout=3, maxHops=3, dedup by message hash
- In-memory peer presence table with 15-minute TTL
- GossipSub activation deferred until go-libp2p-pubsub supports go-libp2p v0.47+
Industry References
- Juniper JunOS
commit confirmed: Apply config, auto-revert if not confirmed. Prevents lockout on remote devices. - Cisco IOS
configure replace: Atomic config replacement with automatic rollback on failure. - MikroTik Safe Mode: Track all changes; revert everything if connection drops.
- Kubernetes liveness/readiness probes: Health endpoints that trigger automatic restart on failure.
- systemd WatchdogSec: Process heartbeat - systemd restarts if process stops responding.
libp2p Specification References
- Circuit Relay v2: Specification - reservation-based relay with configurable resource limits
- DCUtR: Specification - Direct Connection Upgrade through Relay (hole punching coordination)
- AutoNAT v2: Specification - per-address reachability testing with amplification prevention
- Hole Punching Measurement: Study - 4.4M traversal attempts, 85K+ networks, 167 countries, ~70% success rate
Phase 6: ACL + Relay Security + Client Invites
Production-ready access control, relay security hardening, and async client-generated invites. 7 batches, 19 new files, ~3,655 lines of new code. The relay can now be sealed at rest, unsealed remotely, and invite permissions are cryptographically attenuation-only.
6-A: Role-Based Access Control
Three-tier access model for relay operations:
-
roleattribute onauthorized_keysentries (admin/member) - First peer paired with relay auto-promoted to
role=admin(if no admins exist) - Role display in
shurli auth listwith[admin]/[member]badges - Invite policy config:
admin-only(default) /open
6-B: Macaroon Core Library
HMAC-chain capability tokens. Zero external dependencies (stdlib crypto/hmac, crypto/sha256).
-
Macaroonstruct with New, AddFirstPartyCaveat, Verify, Clone, Encode/Decode - Caveat language parser with 7 types:
service,group,action,peers_max,delegate,expires,network -
DefaultVerifier()with fail-closed design - Attenuation-only: each caveat chains a new HMAC-SHA256, making removal cryptographically impossible
- 22 macaroon tests + 10 caveat tests
6-C: Macaroon Integration + Attenuation-Only Invites
Async invite deposits with attenuation-only permissions.
-
DepositStorewith Create/Get/Consume/Revoke/AddCaveat/List/CleanExpired/Count - Deposit states: pending, consumed, revoked, expired (with auto-expiry on access)
- 4 new relay admin endpoints for invite management
- CLI:
shurli relay invite create/list/revoke/modify - Attenuation-only: admin can restrict or revoke before consumption, but can never widen permissions
6-D: TOTP Library
RFC 6238 time-based one-time passwords. Zero external dependencies.
- Generate, Validate (with skew window), NewSecret, FormatProvisioningURI
- 11 tests including RFC 6238 test vectors
6-E: Passphrase-Sealed Relay Vault
Protects relay root key material at rest.
- Argon2id KDF (time=3, memory=64MB, threads=4) + XChaCha20-Poly1305 encryption
- Sealed (watch-only): routes traffic, serves introductions, cannot authorize new peers
- Unsealed (time-bounded): full operations, processes invite deposits, auto-reseals on timeout
- Hex-encoded seed phrase recovery (32 bytes as 24 hex-pair words)
- Root key zeroed from memory on seal
- 5 new relay admin endpoints for vault management
- CLI:
shurli relay vault init/seal/unseal/status - 14 vault tests
6-F: Remote Unseal Over P2P
Admin can unseal the relay remotely without SSH.
-
/shurli/relay-unseal/1.0.0P2P protocol - Admin-only access check, iOS-style escalating lockout (4 free, 1m/5m/15m/1h, permanent block)
- Prometheus metrics:
shurli_vault_unseal_total{result},shurli_vault_unseal_locked_peersgauge - CLI:
shurli relay unseal --remote <name|peer-id|multiaddr>(short name resolution) - 11 unseal tests (wire format, lockout escalation, permanent block, message formatting)
6-G: Yubikey HMAC-SHA1
Optional hardware 2FA via ykman CLI (zero C dependencies).
- Availability detection, challenge-response, graceful fallback
- 6 tests
Phase 7: ZKP Privacy Layer
Zero-knowledge proof privacy layer using gnark PLONK on BN254. Peers prove “I’m authorized” without the relay learning which peer they are. 27 new files, 18 modified, ~91 tests. Two new dependencies: gnark v0.14.0, gnark-crypto v0.19.0 (pure Go).
7-A: ZKP Foundation
Poseidon2 Merkle tree, membership circuit, prover/verifier, key management.
- Native + circuit Poseidon2 hash wrappers (BN254 parameters: width=2, 6 full rounds, 50 partial)
- Sorted Merkle tree builder with power-of-2 padding, max depth 20 (1M+ peers)
- PLONK membership circuit: 22,784 SCS constraints, 520-byte proofs
- Root extension for trees with depth < 20 (pad through unused levels)
- KZG SRS generation with filesystem caching
- Proving key (~2 MB) and verifying key (~33.5 KB) serialization
- High-level prover and verifier with public-only witness support
- 37 tests + 7 benchmarks across 5 test files
- ZKPConfig added to SecurityConfig and RelaySecurityConfig
7-B: Anonymous Relay Authorization
Challenge-response protocol for anonymous authentication over libp2p streams.
-
/shurli/zkp-auth/1.0.0binary wire protocol (4-phase handshake) - Single-use challenge nonces with 30-second TTL, cryptographic randomness
- Relay ZKP handler with stream processing and deadline management
- Client-side ZKP auth (stream-based proof generation)
-
POST /v1/zkp/tree-rebuildadmin endpoint (vault-gated) -
GET /v1/zkp/tree-infoadmin endpoint (always available) - 9 new Prometheus metrics (prove, verify, auth, tree, challenges)
- 15 tests (7 challenge store + 8 wire protocol)
7-C: Private Reputation
Range proofs on peer reputation scores. Prove “my score >= threshold” without revealing the exact score.
- Deterministic
ComputeScore: 0-100, four components (availability, latency, path diversity, tenure) - Range proof circuit: 27,004 SCS constraints (+4,220 over membership for range comparison)
-
AnonymousModeandZKPProoffields onNodeAnnouncement - RLN extension point: types + interface for future anonymous rate limiting
- 5 new Prometheus metrics (range prove/verify, anonymous announcements)
- 25 tests (14 scoring + 11 range proof including 2 end-to-end PLONK)
7-D: BIP39 Seed-Derived Deterministic Keys
Deterministic PLONK key generation from BIP39 seed phrases. Solves the key incompatibility problem discovered during physical testing.
- Pure-stdlib BIP39: generate, validate, seed derivation (256-bit entropy, 24-word mnemonic)
-
SetupKeysFromSeed:SHA256(mnemonic)-> gnarkWithToxicSeed-> deterministic SRS -> same keys anywhere -
shurli relay zkp-setupcommand with--seedflag and interactive prompt -
GET /v1/zkp/proving-keyandGET /v1/zkp/verifying-keyrelay API endpoints - ProvingKey/VerifyingKey naming convention (renamed from PK/VK throughout)
- 14 tests (11 BIP39 + 3 seed determinism)
Phase 7 Key Numbers
| Metric | Value |
|---|---|
| Membership circuit constraints | 22,784 SCS |
| Range proof circuit constraints | 27,004 SCS |
| Proof size | 520 bytes |
| Full auth round-trip (internet) | ~1.8s proving + 2-3ms verification |
| Prove time | ~1.8s |
| Verify time | ~2-3ms |
| Circuit compile | ~70ms |
| Proving key | ~2 MB |
| Verifying key | ~33.5 KB |
| New Prometheus metrics | 14 |
| New/modified files | 27 new, 18 modified |
| Tests | ~91 |
Phase 8: Identity Security + Remote Admin
Unified BIP39 seed architecture, encrypted identity keys, session tokens, full remote admin over P2P, and MOTD/goodbye protocol. 22+ new files, 27+ modified files. Physically tested across 3 nodes (laptop, home-node, relay VPS).
8-A: Unified BIP39 Seed
One 24-word mnemonic derives everything via HKDF domain separation.
- BIP39 24-word mnemonic generation (256-bit entropy)
- HKDF domain separation:
shurli/identity/v1-> Ed25519 key,shurli/vault/v1-> vault root key - SRS derivation from seed -> ZKP keys (deterministic PLONK setup)
-
shurli recover --seedrecovers identity from seed phrase -
shurli relay recoverrecovers relay identity + vault + session from seed
8-B: SHRL Encrypted Identity
All identity keys are password-encrypted at rest. No unencrypted keys.
- SHRL format:
[SHRL][version:1][salt:16][nonce:24][ciphertext] - Argon2id KDF (time=3, memory=64MB, threads=4) + XChaCha20-Poly1305
- Old raw
identity.keyfiles rejected with clear error message -
shurli change-passwordre-encrypts with new password (atomic write) - Same-password rejection on change-password
8-C: Session Tokens
Machine-bound auto-decrypt for daemon auto-start without password.
- SHRS format with machine-bound encryption (HKDF from install-random + machine ID)
-
shurli lock- runtime daemon state only, does NOT delete .session -
shurli unlock- verify password, unlock sensitive ops -
shurli session refresh- rotate token with fresh crypto material -
shurli session destroy- revoke auto-start on this machine - macOS machine ID via IOPlatformUUID (ioreg), Linux via /etc/machine-id
8-D: Remote Admin over P2P
All 24 relay admin endpoints accessible over libp2p streams.
-
/shurli/relay-admin/1.0.0protocol (replaces relay-unseal protocol) -
--remote <peer-id|name|multiaddr>flag on all relay subcommands - Admin role check via authorized_keys
- Local-only path blocklist (vault-init and totp-uri blocked over P2P)
- Relay auto-generates BIP39 seed on first
relay serve
8-E: MOTD/Goodbye Protocol
Ed25519-signed relay operator announcements.
-
/shurli/relay-motd/1.0.0protocol - Wire format:
[version][type][msg-len][msg][timestamp][Ed25519 signature] - 3-stage goodbye lifecycle: set, retract, shutdown (with grace period)
- Client-side signature verification, timestamp bounds, sanitization
- Persisted goodbyes with signature re-verification on load
- Auto-push to newly connected peers
8-F: CLI Enhancements
-
shurli doctor- health check + auto-fix (completions, man page, config) -
shurli completion- bash, zsh, fish (user-local install by default) -
shurli man- troff man page (user-local install by default) -
--skip-seed-confirmflag (skips quiz, keeps mandatory password)
Phase 8 Physical Testing
34 verification items across 3 sessions on 3 physical nodes:
- 30 physically tested (ALL PASS)
- 4 covered by unit tests only
- 2 bugs found and fixed (same-password acceptance, completion/man sudo requirement)
Phase 8B: Per-Peer Data Grants
Timeline: 2026-03-20 to 2026-03-22
Replaced binary relay_data=true with time-limited, per-peer capability grants using macaroon tokens. Node-level enforcement as the true security boundary.
Phase A - Node-Level Grant Store
-
GrantStorewith HMAC-integrity persistence, monotonic version counter - Stream-level enforcement in
OpenPluginStreamandhandleServiceStreamInner - CLI:
shurli auth grant/revoke/extend/grantswith man pages and completions - 30s re-verify during active transfers
- Share-grant separation with CLI warnings
- L4 audit (4 rounds, 12 fixes). Physical retest 10/10 PASS
Phase R - Relay Time-Limited Grants
- Replaces binary
relay_data=truewith time-limited grant store on relay - Physical retest 9/9 PASS
Phase B - Token Delivery + Presentation
- B1: GrantPouch (holder-side), P2P delivery protocol (
/shurli/grant/1.0.0), offline queue - B2: Binary grant header on plugin streams (4-byte overhead). Physical retest 12/12 PASS
- B3: Multi-hop delegation with attenuation-only model. Physical retest 8/8 PASS
- B4: Auto-refresh protocol (background refresh at 10% remaining). 5 rounds L4 audit
Phase C - Notification Subsystem
-
NotificationSinkinterface with non-blocking router and event dedup - 8 event types, 3 built-in sinks (LogSink, DesktopSink, WebhookSink)
- Pre-expiry warnings,
shurli notify test/list
Phase D - Hardening
- Integrity-chained audit log (HMAC-SHA256 chain).
shurli auth audit [--verify] - Configurable cleanup interval, per-peer ops rate limiter (10/min)
- Protocol version on wire messages (downgrade protection)
- 3 rounds self-review, 8 bugs fixed. 25/25 PASS -race
- D1: Cancel propagation fix (physical test PASS) (2026-03-24)
- D3:
SanitizeForDisplay()applied to 8 display points,sanitizeComment/sanitizeAttrValuehardened (2026-03-24)
Post-D UX + AI Agent CLI
-
shurli auth pouch [--json](receiver-side grant visibility) -
--jsonon ALL grant + notify commands -
shurli reconnect <peer> [--json](AI agent control) - Grant-aware backoff reset (relay notifies client on grant create)
- Security: AppleScript injection defense, Router thread safety
Phase 9A: Core Interfaces & Library Consolidation
Goal: Define public API contracts for third-party extensibility. Design-first: get interfaces right before building implementations.
Core Interfaces (pkg/sdk/contracts.go):
-
PeerNetwork- interface for core network operations (expose, connect, resolve, close, events) -
Resolver- interface for name resolution with fallback chaining -
ServiceManager- interface for service registration and dialing, with middleware support -
Authorizer- interface for authorization decisions (pluggable auth) -
StreamMiddleware/StreamHandler- functional middleware chain for stream handlers -
EventType/Event/EventHandler- typed event system for network lifecycle - Logger: Go stdlib
*slog.Logger(no custom interface - deletion over addition)
Extension Points:
- Constructor injection -
Network.Configaccepts optionalResolver - Event hook system -
OnEvent(handler)with subscribe/unsubscribe, thread-safeEventBus - Stream middleware -
ServiceRegistry.Use(middleware)wraps inbound stream handlers - Protocol ID formatter -
ProtocolID()+MustValidateProtocolIDs()for init-time validation
Library Consolidation (completed in 9B):
-
BootstrapAndConnect()extracted topkg/sdk/bootstrap.go - Centralized orchestration -
cmd_ping.goandcmd_traceroute.goreduced by ~100 lines each - Package-level documentation in
pkg/sdk/doc.go
Phase 9B: File Transfer Plugin
Goal: Build file transfer as the first real plugin. Validates the ServiceManager and stream middleware interfaces from 9A. Also includes bootstrap extraction deferred from 9A.
Chunked P2P file transfer with content-defined chunking, integrity verification, compression, erasure coding, multi-source download, parallel streams, and AirDrop-style receive permissions. Hardened across FT-A through FT-H + audit-fix batches (1A, 1B, 2A-2C, 3A-3C, 4A, 4C).
Core Transfer
-
shurli send <file> <peer>- fire-and-forget by default,--followfor inline progress,--priorityfor queue priority -
shurli transfers- transfer inbox with--watchfor live updates,--historyfor completed - FastCDC content-defined chunking (own implementation, adaptive targets 128K-2M)
- BLAKE3 Merkle tree integrity (binary tree, odd-node promotion, root verification)
- zstd compression on by default with incompressible detection and bomb protection (10x ratio cap)
- SHFT v2 wire format (magic + version + flags + manifest + chunk data)
- Receive modes: off / contacts (default) / ask / open / timed
- Disk space checks before each chunk write
- Atomic writes (write to
.tmp, rename on completion) -
PluginPolicy- transport-aware access control (LAN + Direct only, relay blocked by default)
Download, Share & Browse
-
shurli download <file> <peer>- download from shared catalog (--multi-peerfor RaptorQ) -
shurli browse <peer>- browse peer’s shared files -
shurli share add/remove/list- manage shared files (--tofor selective sharing) -
ShareRegistrywith persistent storage (shares.json, survives daemon restarts) - Browse protocol (
/shurli/file-browse/1.0.0) and download protocol (/shurli/file-download/1.0.0)
Transfer Queue & Management
-
TransferQueuewith priority ordering and configurable concurrency (default: 3 active) -
shurli accept/reject <id>- manage pending transfers (--allfor batch) -
shurli cancel <id>- cancel outbound transfer
Advanced Features
- Reed-Solomon erasure coding (auto-enabled on Direct WAN, 50% max overhead)
- RaptorQ fountain codes for multi-source download (
/shurli/file-multi-peer/1.0.0) - Parallel QUIC streams (adaptive: 1 for LAN, up to 4 for WAN)
- Checkpoint-based resume (bitfield of received chunks,
.shurli-ckptfiles) - Recursive directory transfer with path sanitization
- Transfer event logging (JSON lines, rotation) and notifications (desktop/command)
- Per-peer rate limiting (10/min, fixed-window, silent rejection)
- Compression ratio display in transfer output
Audit-Fix Batches
- Macaroon
Verify()wired into pairing consume flow (1A) - Yubikey challenge-response wired into vault unseal (1B)
- Transfer event logging with file rotation (2A)
- Transfer notifications - desktop and command modes (2B)
- Timed receive mode - temporarily open, reverts after duration (2C)
- Batch accept/reject with
--allflag (3A) - Parallel receive streams (3C)
- AllowStandalone config wiring (4A)
- Erasure config gap fix (4C)
Security Hardening (FT-I, FT-J)
- Full integration audit: all exported functions wired, config fields validated, CLI flags consistent
- Command injection fix in notification command templates
- Multi-peer filename sanitization
- Transfer IDs changed from sequential to random hex (
xfer-<12hex>) - Rate limiter applied to multi-peer request path
-
TransferService.Close()cleanup on daemon shutdown
New P2P protocols (4): /shurli/file-transfer/2.0.0, /shurli/file-browse/1.0.0, /shurli/file-download/1.0.0, /shurli/file-multi-peer/1.0.0
New daemon API endpoints (15 new, 38 total)
Dependencies: zeebo/blake3 (CC0), klauspost/compress/zstd (BSD-3), klauspost/reedsolomon (MIT), xssnick/raptorq (MIT)
Test status: 1100 tests across 21 packages, race detector clean.
Post-9B: Chaos Testing and Network Hardening
Timeline: 4 days (2026-03-11 to 2026-03-14) Goal: Physical chaos testing of all network transitions. Verify the daemon handles real-world network switches without restarts.
16 test cases across 5 ISPs and 3 VPN providers. 11 root causes found and fixed. 8 post-chaos flags investigated (6 fixed, 2 informational).
Root Causes Fixed (FT-K through FT-P)
- Black hole detector blocks valid transports after network switch
- Probe targets relay server IP instead of peer IP
- ForceDirectDial tries all peerstore addresses, cascade failure
- mDNS relay cleanup fights remote PeerManager reconnect
- CloseStaleConnections misses private IPs
- Autorelay drops reservations on public networks
- mDNS upgrade poisoned by UDP black hole state
- CloseStaleConnections kills valid IPv6 during DAD window
- Autorelay 1-hour backoff prevents re-reservation
- ProbeUntil cooldown blocks reconnect after direct death
- Swarm reports closed connection as live for 57s
Post-Chaos Investigation (FT-R through FT-X)
- Flag #1: TOCTOU race in mDNS + idle relay cleanup
- Flag #5: Default gateway tracking for private IPv4-only switches
- Flag #7: Dial worker cache poisoning workaround (3-part fix)
- Flag #8: VPN tunnel interface detection
- Autorelay tuning for static relays (faster reconnection)
- ARCHITECTURE.md libp2p upstream overrides section (10 overrides documented)
- Engineering journal ADR-S01 through ADR-S07
libp2p upstream overrides (10): TCP source binding, black hole reset, autorelay tuning (backoff/minInterval/bootDelay/minCandidates), ForceReachabilityPrivate, global IPv6 address factory, custom mDNS, route socket expansion (macOS), VPN tunnel detection, default gateway tracking.
Post-9B: Plugin Architecture Shift
Timeline: 5 days (2026-03-16 to 2026-03-20) Goal: Extract file transfer from inline code into a proper plugin. Build the Plugin interface that all future features follow.
This was a foundational restructuring. Every future feature (service discovery, Wake-on-LAN, gateway, console) drops in as a plugin implementing the same interface. No more wiring into core.
Batch 1 - Plugin Framework (pkg/plugin/)
-
Plugininterface: Name, Version, Init, Start, Stop, Commands, Routes, Protocols, ConfigSection -
PluginContextwith capability grants (no raw Network/Host/credential access) - Registry: discovery, load, enable/disable
- Lifecycle state machine: LOADING -> READY -> ACTIVE -> DRAINING -> STOPPED
-
shurli plugin list/enable/disable/info/disable-allCLI commands - Hot reload: enable/disable without daemon restart
- Kill switch:
shurli plugin disable-all - Plugin directory 0700 permission check
Batch 2 - File Transfer Extraction (plugins/filetransfer/)
- 9 CLI commands moved to plugin (send, download, browse, share, transfers, accept, reject, cancel, clean)
- 14 daemon API endpoints moved to plugin
- 12 types moved to plugin
- 4 P2P protocols registered through plugin Start()
- Plugin owns config section, state files (queue.json, shares.json with HMAC)
- Core untouched: network, auth, identity, relay, ZKP all unchanged
Batch 2.5 - Fix Tracked Findings
- All 67 findings from 5 audit rounds resolved (none deferred)
Batch 3 - Tests + Supervisor + Checkpointer
- 81 test artifacts (unit + integration + fuzz)
- Supervisor auto-restart with circuit breaker (3 crashes = auto-disable)
- Transfer checkpoint/resume persistence
- 7 fuzz targets, 209M total executions, zero crashes
Batch 4a - Security Hardening
- 43-vector threat analysis (traditional, build-time, AI-era)
- 4-round audit with 12 additional fixes
- Credential isolation verified (daemon keys, vault never in PluginContext)
- Plugins cannot install other plugins (propagation chain break)
Batch 4b - Physical Retest
- 11/11 physical tests PASS (LAN send, relay send, browse, download, transfers, share, plugin list/enable/disable/disable-all, protocol unregister)
- Smoke tests PASS (auth, resolve, traceroute, ping, services, plugins, status)
- Performance baselines: LAN 3.3 MB/s, relay 682 KB/s, ping 21ms LAN / 186ms relay
Architecture after shift:
shurli binary
core (network, auth, identity, daemon, CLI framework)
pkg/plugin/ - Plugin interface + registry + supervisor
pkg/sdk/ - Protocol library code (unchanged)
plugins/filetransfer/ - First plugin (CLI, handlers, protocols)Three-layer evolution: Layer 1 (compiled-in Go, current), Layer 2 (WASM via wazero, next), Layer 3 (AI-driven plugin generation, future).
Test status: 24/24 packages PASS, zero races. 7 fuzz targets clean.
E14: Relay-First Onboarding
Timeline: 2026-03-23
Restructured onboarding so relay pairing is the primary path. Simplifies first-time setup.
- Relay-first onboarding flow (relay pairing before peer-to-peer)
- 12 commits on dev branch
- 5 ACL issues deferred to macaroon migration
Per-Peer Bandwidth Budgets
Timeline: 2026-03-24
Per-peer bandwidth_budget auth attribute overrides global default. LAN peers always exempt.
-
shurli auth set-attr <peer> bandwidth_budget <value>(local + relay admin API) - Pipeline: authorized_keys attr -> PeerAttrFunc -> PeerBudgetFunc -> bandwidthTracker override
- Values:
unlimited,500MB,1GB, etc. Config accepts human-readable strings - 3 audit rounds, 23 tests
- Docs: COMMANDS.md, managing-network.md updated
Phase 10: Distribution (partial)
Timeline: 2026-03-24 Status: Install script, release archives, relay-setup –prebuilt complete. GoReleaser, Homebrew, APT planned.
-
tools/install.sh- one-line installer (curl -sSL get.shurli.io | sh) - Colored ANSI output (terminal-aware),
--help,--yes/-y,--upgradeflags -
SHURLI_METHOD/SHURLI_ROLE/SHURLI_UPGRADE/SHURLI_YESenv vars -
get.shurli.ioDNS redirect toshurli.io/install - GitHub Actions release archives (tar.gz per platform)
-
relay-setup.sh --prebuilt(install from release archive instead of source build) -
~/.shurli/config path (migrated from~/.config/shurli/) - Website onboarding redesign (Homebrew-style install in hero, dual URLs)
- Auto-generated release notes from conventional commits