T09 · Insecure Skill Coding Practices
Error
- Location
- SKILL.md:158
- Finding
- Unvalidated Server-Controlled Payment Authorization## Vulnerability Details **File Location**: `SKILL.md:158-179, 190-199`; duplicated in `Reference.md:174-195, 203-212` and the ethers.js flow at `Reference.md:254-276, 285-294` **Vulnerability Type**: Unvalidated payment parameters from an external service **Risk Level**: High ### Vulnerable Code ```javascript // Step 2: Find preferred network in accepts array const networkInfo = paymentRequired.accepts.find(a => a.network === 'eip155:8453'); if (!networkInfo) throw new Error('Base network not available'); // Step 3: Sign EIP-712 TransferWithAuthorization const nonce = keccak256(toHex(`${Date.now()}-${Math.random()}`)); const validBefore = BigInt(Math.floor(Date.now() / 1000) + 3600); const signature = await account.signTypedData({ domain: { name: networkInfo.extra.name, version: networkInfo.extra.version, chainId: 8453, verifyingContract: networkInfo.asset, }, types: { TransferWithAuthorization: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' }, ], }, primaryType: 'TransferWithAuthorization', message: { from: account.address, to: networkInfo.payTo, value: BigInt(networkInfo.amount), validAfter: 0n, validBefore, nonce, }, }); ``` The resulting authorization repeats the same untrusted parameters: ```javascript const paymentPayload = { x402Version: 2, scheme: 'exact', network: 'eip155:8453', payload: { signature, authorization: { from: account.address, to: networkInfo.payTo, value: networkInfo.amount, validAfter: '0', validBefore: validBefore.toString(), nonce, }, }, }; ``` ### Technical Analysis The manual EVM pay ...[truncated 3349 chars]
- Remediation
- ## Remediation Suggestions Validate every payment term before invoking any signing operation: 1. Require the network to equal the intended CAIP-2 identifier, such as `eip155:8453`. 2. Require the chain ID to equal `8453` and reject inconsistent network or domain values. 3. Compare `networkInfo.asset` against an immutable allowlist of official payment-token contracts for each supported network. 4. Require `networkInfo.amount` to be exactly `30000` atomic units for the advertised scan price, or enforce a lower user-configured maximum. Parse it strictly as a decimal integer and reject negative, malformed, or oversized values. 5. Validate `networkInfo.payTo` against a recipient obtained through a trusted, independently authenticated configuration. If recipients are dynamic, display the recipient and amount and require explicit user approval. 6. Allowlist the expected EIP-712 domain name and version. 7. Retain a short authorization lifetime and use a cryptographically random nonce, such as `crypto.randomBytes(32)`, rather than combining the current time with `Math.random()`. 8. Use the documented payment-identifier extension so retries cannot unintentionally create multiple charges. 9. Keep the dedicated payment wallet recommendation and enforce a low balance or wallet-level spending limit. 10. Update the “read-only” wording to clarify that scanning does not modify the analyzed token, but the Skill signs a payment authorization that can transfer funds from the payment wallet. 11. Apply the same validation helper to the viem, ethers.js, `@x402/fetch`, and managed-wallet integrations wherever the integration permits pre-signing policy checks. Example hardening logic: ```javascript const EXPECTED_NETWORK = 'eip155:8453'; const EXPECTED_CHAIN_ID = 8453; const EXPECTED_ASSET = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const EXPECTED_AMOUNT = 30000n; const networkInfo = paymentRequired.accepts.find( item => item.network === EX ...[truncated 863 chars]
