T09 · Insecure Skill Coding Practices
- Location
bin/repay.js:23- Finding
Unverified Remote Repayment Data Controls an Irreversible USDC Transfer
- Content
View full analysis
Vulnerability Details
File Location:
bin/repay.js:23-36
Vulnerability Type: Unverified remote data used to construct a financial transaction
Risk Level: HighVulnerable Code:
js const infoRes = await fetch(`${READS}/api/repay-info/${cfg.AGENT_WALLET}`); const info = await infoRes.json(); if (!info.request_id || !info.repayment_amount || !info.pay_to) { throw new Error("No active loan to repay (or repay-info unavailable): " + JSON.stringify(info)); } console.log(`Owe ${info.repayment_amount} USDC on ${info.request_id} → ${info.pay_to}`); // 2) Pay the EXACT amount via CDP const cdp = await cdpClient(cfg); const amountUnits = Math.round(info.repayment_amount * 1e6); const { transactionHash } = await cdp.evm.sendTransaction({ address: cfg.AGENT_WALLET, network: "base", transaction: { to: USDC, data: transferCalldata(info.pay_to, amountUnits), value: 0n }, });Technical Analysis
The repayment executable retrieves
repayment_amountandpay_tofrom a remote API and uses both fields directly to construct and broadcast an ERC-20 transfer. The only validation is that the response fields are truthy. The script does not:- Verify that
pay_tois an approved treasury address. - Verify the repayment obligation against trusted on-chain state.
- Require a bank signature over the repayment details.
- Apply a maximum payment limit.
- Require explicit confirmation of the resolved destination and amount.
- Default to a non-transactional dry-run mode.
HTTPS authenticates the configured endpoint at the transport layer, but it does not cryptographically bind the response to a specific on-chain debt or authorized treasury address. Consequently, remote application data crosses directly into the wallet-signing boundary.
The documented manual repayment workflow in
SKILL.md:171-191separates retrieval, payment, and confirmation. However,bin/repay.jscombines these operations ...[truncated 1310 chars]- Verify that
- Remediation
View remediation
Remediation Suggestions
- Require repayment responses to carry a cryptographic signature from a pinned bank key, covering the wallet, request ID, amount, destination, chain ID, token contract, and expiration time.
- Verify the repayment obligation and treasury destination against trusted on-chain contract state where possible.
- Maintain an explicit allowlist of authorized treasury addresses and reject every other destination.
- Strictly validate
pay_toas a Base address and parserepayment_amountusing fixed-point decimal logic rather than floating-point arithmetic. - Enforce a configurable maximum repayment amount and reject values exceeding the active loan’s expected bounds.
- Make dry-run the default behavior and display the token, destination, exact amount, network, and loan identifier.
- Require an explicit confirmation parameter or interactive approval before invoking
cdp.evm.sendTransaction. - Check
infoRes.ok, reject redirects or unexpected content types, and fail closed on malformed or unsigned responses.
