T09 · Insecure Skill Coding Practices
Error
- Location
- SKILL.md:189
- Finding
- Unverified Remote Transactions Are Forwarded for Signing and Broadcast## Vulnerability Details **File Location**: `SKILL.md`, lines 189–207 **Vulnerability Type**: Missing transaction validation before privileged wallet operation **Risk Level**: High ### Vulnerable Code ```javascript if (result.type === 'transaction') { for (const tx of result.transactions) { // VERIFY before signing — check to, value, chainId, description const broadcast = await fetch( 'https://frames.ag/api/wallets/{username}/actions/send-transaction', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.AGENTWALLET_API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ chainId: tx.chainId, to: tx.to, data: tx.data, value: tx.value }) } ); console.log(`TX sent: ${(await broadcast.json()).hash}`); } } ``` ### Technical Analysis The example forwards every transaction returned by the remote Tator API directly to AgentWallet's privileged `send-transaction` endpoint. Although a comment instructs the caller to verify the transaction, no executable validation is performed. The implementation does not enforce: - The expected chain ID from the original user request. - An allowlist of authorized destination contracts or recipient addresses. - A maximum native-token value. - Decoded calldata and permitted function selectors. - Approval spender and allowance restrictions. - Consistency between the transaction and the user's original amount, token, recipient, and operation. - Transaction simulation or explicit user confirmation. The AgentWallet API token grants access to a wallet operation capable of signing and broadcasting transactions. Consequently, values controlled by the remote response cross a trust boundary and reach a financially privileged operation without effective validation. The documentation elsewhere recommends verification, but those textual ...[truncated 1831 chars]
- Remediation
- ## Remediation Suggestions Replace the verification comment with mandatory, fail-closed validation before invoking the wallet API: 1. Bind every response to the original request, including the expected chain, operation, token, recipient, and maximum amount. 2. Reject missing, malformed, unexpected, or additional transactions. 3. Maintain chain-specific allowlists of verified router, bridge, and protocol contracts. 4. Decode calldata rather than trusting the response's human-readable description. 5. Allow only function selectors required for the requested operation. 6. For approvals, decode both spender and amount. Reject unknown spenders and unlimited approvals; approve only the exact required amount where possible. 7. Enforce strict native-value and token-amount limits derived from explicit user authorization. 8. Simulate each transaction and evaluate asset changes before signing. 9. Require explicit human confirmation for transfers, approvals, leveraged positions, and transactions above a conservative threshold. 10. Use a dedicated wallet with minimal funds and narrowly scoped wallet-provider policies. 11. Stop the entire sequence on any validation, simulation, signing, or confirmation failure. 12. Record the normalized user request, decoded transaction, validation result, and transaction hash in an audit log without recording bearer tokens. A hardened flow should resemble: ```javascript for (const tx of result.transactions) { const verification = await verifyTransaction({ tx, expectedChainId, originalRequest, allowedContracts, maximumValue }); if (!verification.safe) { throw new Error( `Transaction rejected: ${verification.warnings.join('; ')}` ); } const simulation = await simulateTransaction(tx); if (!simulation.success || simulation.hasUnexpectedAssetChanges) { throw new Error('Transaction simulation failed safety checks'); } await req ...[truncated 91 chars]
