T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/swap.mjs:194
- Finding
- Unvalidated Remote Transaction Is Signed After User Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/swap.mjs`, lines 194–243, 260–275, and 295–336 **Vulnerability Type**: Signing of an untrusted, remotely constructed transaction without semantic validation **Risk Level**: High ### Vulnerable Code ```js // Jupiter swap (build tx) let swapTxBase64; try { const swapBody = { quoteResponse, userPublicKey: owner.toBase58(), dynamicComputeUnitLimit: true, prioritizationFeeLamports: { priorityLevelWithMaxLamports: { maxLamports, priorityLevel: 'high', }, }, }; if (destination) swapBody.destinationTokenAccount = destination.toBase58(); const res = await fetch(`${JUPITER_BASE}/swap`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(swapBody), }); if (!res.ok) { const body = await res.text(); if (res.status >= 500) fail('BACKEND_UNAVAILABLE', `Jupiter swap build error: ${res.status}`, true); fail('BACKEND_QUOTE_FAILED', `Jupiter swap build failed: ${res.status} ${body}`); } const swapData = await res.json(); swapTxBase64 = swapData.swapTransaction; if (!swapTxBase64) fail('BACKEND_QUOTE_FAILED', 'Jupiter returned no swap transaction'); } catch (e) { if (e.code) throw e; fail('BACKEND_UNAVAILABLE', `Jupiter swap build unreachable: ${e.message}`, true); } ``` ```js const prepared = { prepareId, txBase64: swapTxBase64, fromMint, toMint, amountIn, slippage, owner: owner.toBase58(), destination: destination ? destination.toBase58() : owner.toBase58(), expiresAt, executed: false, expectedOut: quoteResponse.outAmount || null, minOut: quoteResponse.otherAmountThreshold || null, priceImpact: quoteResponse.priceImpactPct || null, }; writeFileSync(prepareFilePath(prepareId), JSON.stringify(prepared)); ``` ```js // Sign const keypair = loadKeypair(); let signedTx; try { const txBuf = Buffer.from(prepared.txBase64, 'base64'); const tx = VersionedTransac ...[truncated 3950 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Decode and validate the transaction before signing** - Resolve versioned-message address lookup tables. - Inspect every instruction, account, signer, and writable account. - Reject malformed, unsupported, or unexpected instructions. 2. **Allowlist permitted programs and instruction types** - Permit only the expected Jupiter routing, Solana system, SPL Token, associated-token-account, and compute-budget programs. - Reject authority changes, delegate approvals, unrelated transfers, and account closures unless explicitly expected and confirmed. 3. **Bind execution to the confirmed swap** - Verify the wallet signer and source account. - Verify the input and output mints. - Enforce the confirmed maximum input and minimum output. - Verify the destination account against the confirmed destination. - Validate priority-fee and compute-budget limits. - Calculate a digest of the fully validated transaction and bind that digest to the confirmation record. - Recalculate and compare the digest immediately before signing. 4. **Protect prepared state** - Store prepared swaps in a dedicated owner-only directory. - Create directories with mode `0700` and files with mode `0600`. - Use atomic file creation and reject symlinks where supported. - Add authenticated integrity protection to detect modification between preparation and execution. - Remove expired or completed prepared-state files securely. 5. **Reduce wallet exposure** - Recommend a dedicated low-balance trading wallet rather than a primary wallet. - Document that Jupiter and the configured RPC endpoint are security-critical trust dependencies. - Consider locally constructing the transaction from validated route data where practical. 6. **Add adversarial tests** - Test transactions with altered destinations, excessive inputs, unexpected SOL transfers, delegate approvals, account closures, unknown programs, and excess ...[truncated 88 chars]
