Description
POST /api/confirm/:id only checks if the confirmation ID exists in the pending map. It never checks whether the authenticated wallet is the one that initiated the fund-moving operation.
ULIDs are time-based and partially predictable. An attacker who knows or guesses a confirmation ID can confirm operations on behalf of another user.
File
packages/agent/src/routes/confirm.ts:19-34
The pending map stores { resolve, timer } but never stores the originating wallet. The requestConfirmation() function at line 41 does not accept a wallet parameter.
Fix
- Add
wallet field to pending entry: { resolve, timer, wallet }
- Accept wallet in
requestConfirmation(id, wallet, timeoutMs)
- Validate
req.wallet === entry.wallet before resolving
const pending = new Map<string, {
resolve: (confirmed: boolean) => void
timer: NodeJS.Timeout
wallet: string // <-- add this
}>()
// In handler:
if (entry.wallet !== (req as any).wallet) {
res.status(403).json({ error: 'confirmation belongs to a different wallet' })
return
}
Priority
CRITICAL — fund-moving operations confirmable by unauthorized wallet
Description
POST /api/confirm/:idonly checks if the confirmation ID exists in the pending map. It never checks whether the authenticated wallet is the one that initiated the fund-moving operation.ULIDs are time-based and partially predictable. An attacker who knows or guesses a confirmation ID can confirm operations on behalf of another user.
File
packages/agent/src/routes/confirm.ts:19-34The
pendingmap stores{ resolve, timer }but never stores the originating wallet. TherequestConfirmation()function at line 41 does not accept a wallet parameter.Fix
walletfield to pending entry:{ resolve, timer, wallet }requestConfirmation(id, wallet, timeoutMs)req.wallet === entry.walletbefore resolvingPriority
CRITICAL — fund-moving operations confirmable by unauthorized wallet