T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/batch-test.js:43
- Finding
- Batch Test Signs and Broadcasts Real Blockchain Transactions Contrary to Documentation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/batch-test.js:43-80`; contradictory documentation at `SKILL.md:69-71` **Vulnerability Type**: Unsafe transaction broadcasting and misleading safety documentation **Risk Level**: High ### Vulnerable Code The Skill documentation states: ```markdown ## Batch Testing Use `scripts/batch-test.js` to test wallet signing speed and RPC connectivity without sending real transactions. ``` However, the test signs and broadcasts transactions: ```javascript // Pre-sign all const signStart = process.hrtime.bigint(); const signed = await Promise.all(signers.map((s, i) => { const tx = { to: cfg.contract.address, data: calldata, value: 0n, chainId: cfg.chainId, type: 2, maxFeePerGas: ethers.parseUnits(cfg.gas.maxFeePerGas, "gwei"), maxPriorityFeePerGas: ethers.parseUnits(cfg.gas.maxPriorityFeePerGas, "gwei"), gasLimit: cfg.gas.gasLimit, nonce: nonces[i], }; return s.signTransaction(tx); })); const signMs = Number(process.hrtime.bigint() - signStart) / 1e6; log("⚡ All " + signed.length + " txs signed in " + signMs.toFixed(0) + "ms"); // Batched broadcast const fireStart = process.hrtime.bigint(); log("🔥 FIRING in batches of " + BATCH_SIZE + "..."); let success = 0, fail = 0; const rpcUrls = cfg.rpcUrls || [cfg.rpcUrl]; for (let b = 0; b < signed.length; b += BATCH_SIZE) { const batchNum = Math.floor(b / BATCH_SIZE) + 1; const totalBatches = Math.ceil(signed.length / BATCH_SIZE); const batch = signed.slice(b, b + BATCH_SIZE); const labels = cfg.wallets.slice(b, b + BATCH_SIZE).map(w => w.label); const results = await Promise.allSettled(batch.map((raw, i) => { // Send to first RPC only to avoid rate limits return fetch(rpcUrls[0], { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_sendRawTransaction", params: [raw] }), }).then(r => r.json()).then(j => { ...[truncated 2292 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove all `eth_sendRawTransaction` calls from the default test path. 2. Test RPC connectivity with read-only methods such as `eth_chainId` and `eth_blockNumber`. 3. Test transaction execution with `eth_call` or `eth_estimateGas`, clearly documenting that these methods do not broadcast. 4. Perform signing benchmarks using offline dummy transactions on a designated test chain. 5. If broadcasting is retained as an optional feature: - Require an explicit `--broadcast` command-line flag. - Display the chain ID, target contract, function selector, wallet count, maximum gas cost, and RPC host. - Require explicit interactive confirmation. - Refuse mainnet by default unless a second override flag is supplied. - Provide a dry-run mode that is enabled by default. 6. Rename the script if it remains capable of real broadcasting and correct `SKILL.md` so the behavior is not represented as non-transactional. ]]>
