T09 · Insecure Skill Coding Practices
- Location
scripts/compound.ts:166- Finding
External Odos API response is signed without destination or calldata validation
- Content
View full analysis
{ const response = await rateLimitedFetch('https://api.odos.xyz/sor/assemble', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userAddr: userAddress, pathId, simulate: false, }), }); if (!response.ok) return null; const data = await response.json() as OdosAssembleResponse; if (!data.transaction?.to || !data.transaction?.data) return null; return data; } ``` ```ts console.log(` Executing swap...`); const gasEstimate = BigInt(assembled.transaction.gas); const gasWithBuffer = gasEstimate + (gasEstimate * 50n / 100n); const nonce = await getFreshNonce(publicClient, account.address); try { const swapHash = await walletClient.sendTransaction({ to: assembled.transaction.to as Address, data: assembled.transaction.data as Hex, value: BigInt(assembled.transaction.value), gas: gasWithBuffer, nonce, }); const receipt = await waitForTransaction(publicClient, swapHash); if (receipt.status === 'success') { console.log(` ✅ Swapped! Tx: ${swapHash}\n`); } else { console.log(` ❌ Swap reverted\n`); } } catch (err) { console.log(` ❌ Swap failed\n`); } ``` ### Technical Analysis The Odos assemble endpoint controls the transaction destination, calldata, native token value, and gas estimate. The response validation only confirms that `transaction.to` and `transaction.data` are present. The implementation does not: - Require the destination to equal the hard-coded `ODOS_ROUTER`. - Decode and validate the calldata. - Verify the input token, output token, amount, recipient, or minimum output. - Restrict the native ETH value. ...[truncated 2111 chars]- Remediation
View remediation
