T09 · Insecure Skill Coding Practices
- Location
SKILL.md:189- Finding
Unverified Remote Transactions Are Forwarded for Signing and Broadcast
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 189–207
Vulnerability Type: Missing transaction validation before privileged wallet operation
Risk Level: HighVulnerable 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-transactionendpoint. 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
View remediation
Remediation Suggestions
Replace the verification comment with mandatory, fail-closed validation before invoking the wallet API:
- Bind every response to the original request, including the expected chain, operation, token, recipient, and maximum amount.
- Reject missing, malformed, unexpected, or additional transactions.
- Maintain chain-specific allowlists of verified router, bridge, and protocol contracts.
- Decode calldata rather than trusting the response's human-readable description.
- Allow only function selectors required for the requested operation.
- For approvals, decode both spender and amount. Reject unknown spenders and unlimited approvals; approve only the exact required amount where possible.
- Enforce strict native-value and token-amount limits derived from explicit user authorization.
- Simulate each transaction and evaluate asset changes before signing.
- Require explicit human confirmation for transfers, approvals, leveraged positions, and transactions above a conservative threshold.
- Use a dedicated wallet with minimal funds and narrowly scoped wallet-provider policies.
- Stop the entire sequence on any validation, simulation, signing, or confirmation failure.
- 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]
