T09 · Insecure Skill Coding Practices
Error
- Location
- references/api.md:270
- Finding
- Paid Oracle Signals Are Released Before On-Chain Payment Verification<![CDATA[ ## Vulnerability Details **File Location**: `references/api.md`, lines 270–308 **Vulnerability Type**: Payment-gate bypass caused by insufficient transaction verification **Risk Level**: High ### Vulnerable Code and Documentation ```javascript parts.push(Buffer.from('01', 'hex')); // prev txid (32 zeros — coinbase-style for off-chain proof) parts.push(Buffer.alloc(32)); // prev index (ffffffff) parts.push(Buffer.from('ffffffff', 'hex')); // empty script (OP_0) parts.push(Buffer.from('0100', 'hex')); // sequence parts.push(Buffer.from('ffffffff', 'hex')); // output count parts.push(Buffer.from('01', 'hex')); // value: priceSats as 8-byte LE const val = Buffer.alloc(8); val.writeBigUInt64LE(BigInt(priceSats)); parts.push(val); // locking script parts.push(Buffer.from([lockingScript.length])); parts.push(lockingScript); // locktime parts.push(Buffer.from('00000000', 'hex')); const txhex = Buffer.concat(parts).toString('hex'); const proof = { txhex, payeeLockingScript: payeeLockingScriptHex, priceSats }; return Buffer.from(JSON.stringify(proof)).toString('base64'); } // Usage const xPayment = buildXPayment('76a914...88ac', 50); ``` The documented processing behavior states: ```text On success (HTTP 200), the paid signal includes `payment_txid` confirming proof of payment was accepted. After accepting payment, Brouter polls the Anvil BSV node in the background to verify the txid has a real on-chain merkle proof (BEEF). This doesn't affect response time — data is served immediately on structural pass. ``` ### Technical Analysis The payment example constructs a transaction containing a zero-filled previous transaction ID and an `ffffffff` previous-output index. It does not reference a genuine spendable UTXO and does not contain a valid signature proving authorization to spend funds. Despite this, the documented service workflow releases paid signal data after only a structural validation pass. Verification that the transaction has a real on-chain ...[truncated 1925 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not release paid content until the submitted transaction has passed authoritative payment verification. 2. Verify that every transaction input references a real, unspent output and reject null, coinbase-like, nonexistent, or already-spent inputs. 3. Validate all input signatures and scripts under the applicable BSV consensus rules. 4. Confirm that the transaction pays at least the required amount to the exact server-issued locking script. 5. Bind each payment to the server-issued nonce, requested resource, amount, payee, and expiration time to prevent replay or cross-resource reuse. 6. Require successful broadcast and an acceptable SPV/BEEF proof before returning protected content. If zero-confirmation acceptance is necessary, use a documented risk engine and do not describe it as confirmed payment. 7. Track transaction IDs and payment nonces atomically so one transaction cannot unlock multiple resources unless explicitly permitted. 8. Replace the synthetic transaction example with a wallet-generated and cryptographically signed transaction using genuine spendable UTXOs. 9. Return a pending status while payment verification is incomplete, and disclose paid data only after verification succeeds. 10. Add regression tests covering zero-filled previous transaction IDs, missing signatures, nonexistent UTXOs, duplicate transactions, replayed nonces, underpayments, incorrect locking scripts, and transactions that never reach the network. ]]>
