Observed
Every x-siws-auth payload carries a nonce in its signed message, but the server never checks it. authenticateRequest in server/api/middleware/auth.middleware.js verifies the signature, statement, and domain — but not nonce uniqueness.
Consequence: a captured x-siws-auth header can be replayed:
Proposed fix
Single-use nonces + a hard expiry on every chain:
- Nonce store — a
auth_nonces table (nonce, expires_at); the first time a nonce is seen it is recorded, a repeat is rejected (403, replay).
- Expiry — require
expirationTime on every sign-in message (add it to the Substrate SiwsMessage client-side; the Substrate verifier rejects expired/missing-expiry messages, matching the Ethereum/Solana verifiers).
- Expired nonce rows are cleaned up opportunistically.
Acceptance criteria
Notes
A full server-issued challenge/response (server hands out the nonce) would be stronger but is a larger flow change. Seen-nonce tracking + expiry is the proportionate fix and closes the practical replay window. Identified during a launch-readiness review.
Observed
Every
x-siws-authpayload carries anoncein its signed message, but the server never checks it.authenticateRequestinserver/api/middleware/auth.middleware.jsverifies the signature, statement, and domain — but not nonce uniqueness.Consequence: a captured
x-siws-authheader can be replayed:expirationTime(PR feat: multi-chain sign-in — Substrate + Ethereum + Solana #84 sets a 10-minute expiry on those messages — partial mitigation only).Proposed fix
Single-use nonces + a hard expiry on every chain:
auth_noncestable (nonce,expires_at); the first time a nonce is seen it is recorded, a repeat is rejected (403, replay).expirationTimeon every sign-in message (add it to the SubstrateSiwsMessageclient-side; the Substrate verifier rejects expired/missing-expiry messages, matching the Ethereum/Solana verifiers).Acceptance criteria
expirationTimeis rejected, on all three chains.auth_noncestable.Notes
A full server-issued challenge/response (server hands out the nonce) would be stronger but is a larger flow change. Seen-nonce tracking + expiry is the proportionate fix and closes the practical replay window. Identified during a launch-readiness review.