T09 · Insecure Skill Coding Practices
- Location
api/src/middleware/walletAuth.ts:43- Finding
Wallet authentication signatures are replayable across endpoints and actions
- Content
View full analysis
{ const address = req.header("X-WALLET-ADDRESS"); const signatureEncoded = req.header("X-WALLET-SIGNATURE"); const timestampHeader = req.header("X-WALLET-TIMESTAMP"); if (!address || !signatureEncoded || !timestampHeader) { res.status(401).json({ error: "Missing wallet authentication headers" }); return; } const timestamp = Number(timestampHeader); if (!Number.isFinite(timestamp)) { res.status(401).json({ error: "Invalid wallet timestamp" }); return; } const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > MAX_CLOCK_DRIFT_SECONDS) { res.status(401).json({ error: "Wallet timestamp outside accepted window" }); return; } try { if (!StrKey.isValidEd25519PublicKey(address)) { res.status(401).json({ error: "Invalid Stellar public key" }); return; } const pubkeyBytes = StrKey.decodeEd25519PublicKey(address); const signature = decodeSignature(signatureEncoded); const message = new TextEncoder().encode(`asgcard-auth:${timestamp}`); const verified = nacl.sign.detached.verify(message, signature, pubkeyBytes); if (!verified) { res.status(401).json({ error: "Invalid wallet signature" }); return; } req.walletContext = { address, timestamp }; trackApiActivity(address).catch(() => {}); next(); } catch { res.status(401).json({ error: "Invalid wallet authentication payload" }); } }; ``` The client generates the same request-independent signature: ```ts private signAuth(): { address: string; signature: string; timesta ...[truncated 3199 chars]- Remediation
View remediation
method: path: body-sha256: timestamp: nonce: ``` Required hardening measures: 1. Include the HTTP method, canonical path, query string, and body digest in every signature. 2. Include the agent nonce in the signed message for card-details requests. 3. Prefer a server-issued, single-use challenge stored with a short expiration. 4. Atomically consume each challenge or nonce and reject reuse across all endpoints. 5. Reduce the accepted clock window after deploying nonce enforcement. 6. Include an audience or origin identifier to prevent signatures from being replayed against another deployment. 7. Apply the same signing implementation to the CLI, MCP server, SDK examples, and browser portal. 8. Add automated tests proving that: - A signature for `GET /cards/` fails on `POST /cards/{id}/freeze`. - A signature for one card ID fails for another card ID. - A changed body or query string invalidates the signature. - A reused nonce is rejected atomically. ]]>
