T09 · Insecure Skill Coding Practices
Error
- Location
- lib/x402-client.mjs:75
- Finding
- Untrusted x402 Server Controls Signed Payment Authorization Terms<![CDATA[ ## Vulnerability Details **File Location**: `lib/x402-client.mjs:75-81`, `lib/crypto.mjs:40-82`, `wallet.mjs:65-81` **Vulnerability Type**: Insufficient validation of server-supplied payment parameters **Risk Level**: High ### Complete Vulnerable Code Snippets From `lib/x402-client.mjs:75-81`: ```js // Find a supported payment option (Base USDC) const option = requirements.accepts?.[0]; if (!option || option.scheme !== 'exact') { throw new Error('No supported payment option (exact scheme on Base)'); } // Sign the payment authorization const payment = await createEIP3009Signature(privateKey, option, requirements.x402Version || 1); ``` From `lib/crypto.mjs:40-82`: ```js // Parse network from EIP-155 namespace (e.g., "eip155:8453") const chainId = parseInt(requirement.network.split(':')[1], 10); // Generate cryptographically secure random nonce (32 bytes) const nonceBytes = new Uint8Array(32); crypto.getRandomValues(nonceBytes); const nonce = `0x${Buffer.from(nonceBytes).toString('hex')}`; const now = Math.floor(Date.now() / 1000); const validAfter = BigInt(now - 60); const validBefore = BigInt(now + (requirement.maxTimeoutSeconds || requirement.requiredDeadlineSeconds || 300)); // Parse amount: decimal string ("5.00") or base units const maxAmount = requirement.maxAmountRequired; let value; if (typeof maxAmount === 'string' && maxAmount.includes('.')) { value = BigInt(Math.floor(parseFloat(maxAmount) * 1e6)); } else if (x402Version >= 2 || String(maxAmount).length > 6) { value = BigInt(maxAmount); } else { value = BigInt(maxAmount) * BigInt(1e6); } const authorization = { from: account.address, to: requirement.payTo || requirement.payToAddress, value, validAfter, validBefore, nonce, }; const domain = { name: requirement.extra?.name || 'USD Coin', version: requirement.extra?.version || '2', chainId, verifyingContract: requirement.asset || requirement.usdcAddress, }; const types = { TransferWithAuthorization: [ ...[truncated 3225 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Define a strict local mapping of supported networks to canonical USDC contracts. 2. Require the server-supplied network to match the locally configured wallet network. 3. Require `requirement.asset` to exactly match the canonical USDC contract for that chain. 4. Validate all recipient addresses and optionally require an application-level recipient allowlist. 5. Introduce a mandatory local maximum payment amount that cannot be overridden by the server. 6. Reject missing, malformed, excessive, or negative authorization deadlines. 7. Retrieve and validate the payment requirements before asking for confirmation. 8. Display the exact chain, token contract, recipient, amount, and expiration to the user. 9. Require a second, informed confirmation over those resolved terms before signing. 10. Bind the authorization to the expected request origin where the protocol and application design permit it. ]]>
