Local endpoint security
Trust boundary, secret lifetimes, and threat model for the node's LAN listener.
Scope
Security properties of the Storage Node's local HTTP listener and the first-time pairing handshake. This listener is not the confidentiality boundary for file data — bulk data flows over WebRTC (DTLS). It is the trust boundary for identity and pairing on the LAN.
Secrets and their lifetimes
Challenge nonces (device ↔ node, local)
- Generated by
POST /nodus/challengeon the node. - 32 cryptographically random bytes.
- TTL: 30 seconds — expired nonces are rejected.
- Single-use: consuming a nonce on
/nodus/authremoves it. A second use of the same nonce fails. Poisons (unknown nonces) are rejected without side effects. - Stored in node memory (not disk), keyed by hex nonce.
Pairing tokens (Relay-issued)
- Issued by
POST /pairing/sessions(authenticated) on the Relay. - TTL: 15 minutes (
expires_aton the row). - Single-use: the first consumer atomically sets
consumed_at(UPDATE ... WHERE consumed_at IS NULL). Any later use is rejected. - Device-bound: the token is bound to the requesting device's Ed25519 public key at issuance. The node verifies the pairing signature/auth identity corresponds to the device the token names; a stolen token cannot be redeemed by a different device unless that device also has the token and the correct identity.
- Verified twice: once by the node (local
pairing_sessions) and once by the Relay (/pairing/sessions/verify), whichever path is hit. The Relay row is the source of truth for single-use across re-syncs.
Node bootstrap pairing codes (Relay, Phase 7b)
Separate from the device↔node tokens above: first-time Storage Node association
uses a one-time NODUS-XXXX-XXXX pairing code redeemed over the public
origin via POST /pairing/codes/redeem. The code is hashed at rest,
single-use, TTL-bounded, IP rate-limited, and never substitutes for the node's
Ed25519 WS challenge-response. See
bootstrap-pairing.md for the endpoints, failure
reasons, and threat model.
Threat model
| Threat | Mitigation |
|---|---|
| Rogue LAN device impersonates a node | Node proves identity via Ed25519 challenge-response; a rogue device cannot sign nonces with the node's private key |
| Spoofed mDNS advertisement | Discovery is not authentication. Pairing binds to the account (Relay-issued token), not to whatever answers on the LAN. The client verifies the node's public key against the node_id in the pairing URL |
| Replayed pairing token | Single-use token, redeemed atomically at the Relay; server-side consumed_at |
| Sniffed token used by a different device | Token is bound to a device pubkey at issuance; redemption requires that same device identity |
| Replayed challenge-response | Nonces are single-use and TTL-bounded |
| On-path attacker swaps WebRTC SDP/ICE on the plaintext LAN listener | Each signaling request signs "{device_id}:{session_id}:{timestamp_ms}:{blake3(payload)}", so a body swap invalidates the signature |
| Browser origin probing the node | Permissive CORS on the LAN listener, accepted for the home-LAN v1 trust model |
Known accepted risks (v1)
- Plaintext HTTP on the local link: handshake bytes could be observed on the LAN. Pairing-intent replay is blocked by single-use + device binding; node impersonation is blocked by the signature protocol. Not transport-encrypted for v1 — revisit TLS if untrusted-LAN scenarios become in-scope.
- A compromised device that was paired retains its local trust until revoked (same accepted tradeoff as ADR-0001).
Offline pairing limitation
First-time pairing while the node is fully offline (no local token record and no connectivity to the Relay for verification) is not supported. The client sees a "node unreachable" error. Pairing requires the node to have either received the Relay's WS push or be able to reach the Relay's verify endpoint.