T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/x402-sign.mjs:76
- Finding
- Untrusted Payment Headers Can Authorize Arbitrary USDC Transfers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/x402-sign.mjs`, lines 76-157 **Vulnerability Type**: Insufficient validation of payment authorization data **Risk Level**: High ### Vulnerable Code ```js // Decode the Payment-Required header (x402 v2) const headerJson = Buffer.from(base64Header, "base64").toString("utf-8"); const paymentRequired = JSON.parse(headerJson); const accept = paymentRequired.accepts?.[0]; if (!accept) { log("Error: No payment options in Payment-Required header."); process.exit(1); } const amount = BigInt(accept.amount); const amountUsdc = Number(amount) / 10 ** USDC_DECIMALS; log(`Signing payment of $${amountUsdc.toFixed(2)} USDC`); log(` From: ${account.address}`); log(` To: ${accept.payTo}`); // Check balance const publicClient = createPublicClient({ chain: base, transport: http(rpcUrl), }); const balance = await publicClient.readContract({ address: USDC_ADDRESS, abi: BALANCE_OF_ABI, functionName: "balanceOf", args: [account.address], }); if (balance < amount) { const balanceUsdc = Number(balance) / 10 ** USDC_DECIMALS; log( `Error: Insufficient USDC balance. Have $${balanceUsdc.toFixed(2)}, need $${amountUsdc.toFixed(2)}` ); process.exit(1); } // Generate nonce and validity window const nonce = keccak256( concat([ pad(toHex(Date.now())), encodePacked(["address"], [account.address]), ]) ); const validAfter = 0n; const validBefore = BigInt(Math.floor(Date.now() / 1000) + 3600); // Sign EIP-3009 TransferWithAuthorization const walletClient = createWalletClient({ account, chain: base, transport: http(rpcUrl), }); const signature = await walletClient.signTypedData({ domain: USDC_DOMAIN, types: TRANSFER_WITH_AUTHORIZATION_TYPES, primaryType: "TransferWithAuthorization", message: { from: account.address, to: accept.payTo, value: amount, validAfter, validBefore, nonce, }, }); ``` ### Technical Analysis The signing command decodes a ...[truncated 2042 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Validate `x402Version`, payment scheme, network, and asset against strict constants before signing. 2. Require the resource URL to use HTTPS and match an explicit Ordiscan hostname and expected API path. 3. Validate `payTo` against a trusted recipient obtained through authenticated configuration or documented service metadata. 4. Require an explicit maximum amount supplied independently of the payment header, and reject payments above it. 5. Bind the payment response to the original method, URL, and request body so a header cannot be reused for a different operation. 6. Present the verified recipient, amount, asset, network, resource, and expiration to the user and require confirmation before signing. 7. Reject malformed, duplicated, unsupported, or ambiguous payment options rather than automatically selecting the first entry. 8. Prefer a maintained x402 validation library where available, while retaining local policy checks around origin and spending limits. ]]>
