T09 · Insecure Skill Coding Practices
Error
- Location
- src/adapters/circle.ts:162
- Finding
- Circle API Failures Are Converted into Fabricated Successful Transfers<![CDATA[ ## Vulnerability Details **File Location**: `src/adapters/circle.ts:162-168`, `src/adapters/circle.ts:213-219` **Related Call Sites**: `src/cli/commands/borrow.ts:91-98`, `src/cli/commands/repay.ts:68-86` **Vulnerability Type**: Fail-open financial transaction handling **Risk Level**: High ### Vulnerable Code ```ts // src/adapters/circle.ts:162-168 } catch (error) { // If Circle API fails, return mock success for demo return { success: true, transactionId: `mock-tx-${Date.now()}`, status: 'COMPLETE', }; } ``` ```ts // src/adapters/circle.ts:213-219 } catch (error: any) { // If Circle API fails, return mock success for demo return { success: true, transactionId: `mock-repay-${Date.now()}`, status: 'COMPLETE', }; } ``` The fabricated results are trusted by the borrowing and repayment commands: ```ts // src/cli/commands/borrow.ts:91-98 const result = await disburseLoan(agent.walletAddress, amount, poolWallet.id); if (result.success || result.status === 'INITIATED') { // Update agent ledger agentRegistry.updateOutstandingLoan(agent.id, amount); console.log(`✅ Loan created successfully!`); ``` ```ts // src/cli/commands/repay.ts:68-86 const transferResult = await receiveRepayment( agent.walletId, agent.walletAddress, poolWallet.address, amount ); if (!transferResult.success) { console.error(`\n❌ Transfer failed: ${transferResult.error || 'Unknown error'}\n`); return; } // Update ledger only after successful transfer const newOutstanding = outstanding - amount; agentRegistry.updateOutstandingLoan(agent.id, newOutstanding); ``` ### Technical Analysis The adapter enters mock mode correctly when no Circle configuration exists. However, after a real Circle client has been initialized, operational errors are still converted into mock transactions with `success: true` and `status: 'COMPLETE'`. This violates the fail-closed requirement for financial operations. There is no distinction between an ...[truncated 1649 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove all mock-success responses from exception handlers used after real Circle configuration has been selected. 2. Return a genuine failure object: ```ts catch (error) { return { success: false, error: sanitizeCircleError(error), }; } ``` 3. Require an explicit configuration value such as `MOCK_MODE=true` before any mock transfer is permitted. 4. Refuse to combine real credentials with mock transaction fallback. 5. Keep loans and repayments in a pending state until the transaction is independently confirmed through `getTransactionStatus()`. 6. Store the real Circle transaction ID and reconcile it before changing outstanding balances. 7. Add idempotency keys and transactional ledger updates to prevent duplicate processing. 8. Add tests proving that timeouts, HTTP errors, rejected transactions, and invalid credentials do not alter balances. ]]>
