T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/approve-tx.ts:27
- Finding
- Blind Approval of an Unverified Safe Transaction Hash<![CDATA[ ## Vulnerability Details **File Location**: `scripts/approve-tx.ts:27-57` **Vulnerability Type**: Blind signing of unverified transaction data **Risk Level**: High ### Vulnerable Code ```ts try { if (opts.safe) validateAddress(opts.safe, 'safe'); if (opts.safeTxHash) validateTxHash(opts.safeTxHash, 'safe-tx-hash'); validateApiKey(opts); const txServiceUrl = resolveTxServiceUrl(opts); const chainId = resolveChainId(opts); const pk = requirePrivateKey(); // FIX SM-005 / SH-05: Use resolveRpcUrl instead of hardcoded Base fallback const provider = resolveRpcUrl(opts); const safeSdk = await Safe.init({ provider, signer: pk, safeAddress: opts.safe! }); const senderAddress = await safeSdk.getSafeProvider().getSignerAddress(); // Sign the tx hash const sig = await safeSdk.signHash(opts.safeTxHash!); const apiKitConfig: { chainId: bigint; txServiceUrl: string; apiKey?: string } = { chainId, txServiceUrl }; if (opts.apiKey) apiKitConfig.apiKey = opts.apiKey; const apiKit = new SafeApiKit(apiKitConfig); await apiKit.confirmTransaction(opts.safeTxHash!, sig.data); ``` ### Technical Analysis The approval command validates only that `--safe-tx-hash` is a syntactically valid 32-byte hexadecimal value. It does not retrieve the associated transaction from the Safe Transaction Service before signing it. Consequently, the script does not verify: - That the hash corresponds to an existing Safe transaction. - That the transaction belongs to the Safe supplied through `--safe`. - The transaction destination, value, calldata, operation, nonce, or gas-refund fields. - That a fetched transaction recomputes to the supplied Safe transaction hash. - That the signer has reviewed or explicitly authorized the underlying action. The command then signs the opaque caller-controlled hash and submits the resulting owner confirmation. This is a blind-signing pattern. It is particularly dangerous for Safe wallets because ...[truncated 1938 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Before producing an approval signature: 1. Retrieve the transaction associated with `--safe-tx-hash` from the configured transaction service. 2. Verify that the returned transaction’s Safe address exactly matches `--safe`. 3. Reconstruct the complete Safe transaction, including: - Destination address - Value - Calldata - Operation type - Nonce - Safe transaction gas - Base gas - Gas price - Gas token - Refund receiver 4. Recompute the Safe transaction hash locally using the SDK and reject the operation if it differs from the supplied hash. 5. Confirm that the connected RPC network’s chain ID matches the requested chain. 6. Present the complete transaction summary before signing, including decoded calldata where possible. 7. Require explicit interactive authorization for high-impact actions, unless a deliberate non-interactive flag is supplied. 8. Consider adding policy controls that reject delegate calls, owner changes, module activation, unlimited token approvals, or transfers above configured limits. 9. Add tests proving that approval is rejected when: - The transaction belongs to another Safe. - Any returned field is modified. - The recomputed hash differs. - The chain ID differs. - The transaction cannot be retrieved. ]]>
