Files
tsunagi/docs/threat-model.md
T
tsunagiandClaude Opus 5 ea7aaa2b69 Implement the WireGuard data plane plugin
The first IP plugin, built on the data plane boundary the core already had.

Plugin:
- one X25519 key per network in the plugin's own wireguard.sqlite, separate
  from the iroh identity and from the network secret; a damaged store is an
  error, never a silently regenerated identity
- deterministic IPv6 ULA overlay: every member derives the same /64 from the
  network id and its own /128 from its WireGuard public key, so no
  coordinator allocates addresses
- AllowedIPs are derived locally, never taken from a peer's announcement, so
  a member cannot claim another member's overlay address; a mismatched claim
  is rejected
- bounded, versioned, validated announcement carried as the existing opaque
  capability payload, which the core still never parses
- each agent builds its own full-mesh configuration (N-1 peers) and
  reconciles on every change and on a timer, repairing drift
- WireguardBackend abstraction: RecordingBackend in memory, and WgToolBackend
  driving real wg/ip on Linux, split into a pure planner plus parsers and a
  thin executor so everything interesting is testable without root

Core, three generic additions the plugin needed:
- IpPlugin::on_network_activated, so per-network state is ready before peers
- PluginContext for re-announcements and error reports from plugin tasks,
  with errors counted by the owning network runtime
- IpPlugin::shutdown, awaited with a grace period, so system objects go away

94 tests pass offline with no privileges: 35 new WireGuard unit tests and 12
integration tests over real iroh connections. The real wg/ip backend needs
root and is behind --ignored in tests/wireguard_system.rs; it was not run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:07:31 +01:00

5.6 KiB

Threat model and known limits

Read this before relying on anything here. The protocol is in protocol.md.

What is protected

  • Network membership. A peer must prove knowledge of auth_key, derived from the network name and shared secret, to get an authenticated session. Knowing the public NetworkId, or an agent's address, is not enough.
  • Endpoint authenticity. iroh's QUIC/TLS handshake authenticates the remote endpoint id, which is its public key. Identities in the membership transcript are taken from the certificate, never from a peer's claim.
  • Connection binding. The membership proof includes TLS exporter output, so a proof captured on one connection does not verify on another.
  • Role separation. Initiator and responder proofs cover different transcripts, so a proof cannot be reflected back at its sender.
  • Network isolation. A session authenticated for network A cannot carry messages for network B, even over a shared physical connection.
  • Confidentiality and integrity in transit. Provided by QUIC/TLS. This crate adds no encryption of its own.
  • Overlay address ownership. A WireGuard peer's AllowedIPs are derived from its public key, not taken from its announcement, so a member cannot claim another member's overlay address and receive its traffic. See wireguard.md.
  • Resource bounds. Frame lengths are validated before allocation; strings, lists, queues, concurrent dials and in-flight handshakes are all bounded; handshakes, dials and writes have timeouts.

What is not protected

  • Anyone who knows the secret is a full participant. They can create arbitrarily many identities, flood the network with records and collide with other participants' names. This is why a majority is not a root of trust. Signatures protect authorship; they do not make a participant honest.
  • Weak secrets. This targets high-entropy secrets. There is no PAKE, so a short human passphrase can be guessed offline by anyone who can reach the handshake. Use NetworkSecret::generate().
  • Addresses and metadata are observable. Anyone able to watch the network sees addresses, timing and volume. Discovery backends see the discovery_key and the addresses published under it, which is enough to map a network's participants. This library does not make a network anonymous, and having iroh under it does not make it so.
  • A cloned state directory is a cloned identity. state.sqlite holds the device secret key and the network secrets. Copying it copies the participant. Restoring an old backup rolls the agent's state back, which — once signed records exist — can resurrect revoked information or replay stale versions.
  • A compromised host. The secret is on disk to survive restarts. File permissions are owner-only where the platform supports it, and the state directory takes an ownership lock, but neither defends against a user who can read the file or against malware running as that user.
  • User IP traffic. Carried by the WireGuard plugin, not by iroh, and encrypted by WireGuard itself. Filtering it is still the operating system's and the user's job: the plugin creates connectivity between members and does not police what flows over it.
  • Overlay address squatting. A member can mint many WireGuard keys and therefore occupy many overlay addresses. It cannot pick which ones, but it can consume them and appear as many participants.
  • Plugin keys on disk. The WireGuard private keys live in the plugin's own wireguard.sqlite, owner-only where the platform supports it. Copying that file copies this agent's overlay identity, exactly as copying state.sqlite copies its control plane identity.
  • What the data plane does not police. The plugin sets AllowedIPs per peer, which stops a member impersonating another member's overlay address. It does not stop a member sending whatever it likes from its own address.
  • Denial of service. Bounds and timeouts stop trivial resource exhaustion from a single peer. They do not make the agent resistant to a determined attacker who knows the secret, and no rate limiting per identity exists yet.
  • Global freshness. A signature proves authorship, not that you have the newest state. See sync-model.md.
  • Discovery is not trustworthy. It returns candidates. A hostile or stale discovery backend can waste dial attempts and learn addresses; it cannot forge membership.

Deliberate design consequences

  • No owner, no vote. Nobody can evict anybody. Removing a participant means changing the secret, which creates a different network space that the removed participant cannot enter.
  • Rotating the secret is not revocation of past access. Anyone who held the old secret keeps whatever they already saw.
  • Local deactivation is not revocation. Deactivating a network stops this agent participating. It says nothing about anyone else.
  • Failures are contained, not escalated. A bad proof, wrong secret, malformed frame or unknown version rejects one message or one session. It never stops another network and never stops the agent, and there is no irreversible global error flag.

Cryptographic choices

Standard primitives only, no home-made constructions: HKDF-SHA256 (RFC 5869) for key separation, HMAC-SHA256 for the membership proof, constant-time verification, iroh's Ed25519 endpoint keys and QUIC/TLS for the transport, and RFC 5705 TLS exporter output for channel binding. There is no custom encryption layer and no custom PAKE.