fix(federation): add nonce length validation and fix verification flow docs (FED-008)

This commit is contained in:
Jannis Braun
2026-03-31 18:18:50 +02:00
parent 9961d9012b
commit d0ed43cf58
2 changed files with 6 additions and 2 deletions
+3 -2
View File
@@ -135,10 +135,11 @@ Two layers of replay protection:
### Inbound Verification Flow (`POST /api/federation/relay`)
1. `parseFederationHeaders()` extracts origin, timestamp, signature from headers
1. `parseFederationHeaders()` extracts origin, timestamp, signature, and nonce from headers
2. Look up peer by `origin` in `federation_peers` -- must exist and be `status = 'active'`
3. Re-serialize request body to JSON: `JSON.stringify(request.body)`
4. `verifySignature(bodyString, signature, peer.hmacSecret, timestamp)` -- reject if false
4. `verifySignature(bodyString, signature, peer.hmacSecret, timestamp, nonce)` -- reject if false
5. Nonce enforcement: duplicate nonce → 409, missing nonce from ratcheted peer → 401, legacy peer → warn
**Important:** The body is re-serialized server-side. This means Fastify's JSON parsing and re-stringification must produce identical output to the sender's `JSON.stringify`. In practice this works because both sides use standard `JSON.stringify` with no custom replacers.
@@ -132,6 +132,9 @@ export function parseFederationHeaders(
const rawNonce = getHeader('x-federation-nonce') ?? getHeader('X-Federation-Nonce');
const nonce = rawNonce && typeof rawNonce === 'string' && rawNonce.trim() ? rawNonce.trim() : null;
// Reject nonces that are unreasonably long (UUID v4 is 36 chars)
if (nonce && nonce.length > 64) return null;
return { origin, timestamp: timestampMs, signature, nonce };
}