Back to skill

Security audit

RSoft Agentic Bank

Security checks for vulnerabilities and agentic risk

Overview

This is a real-money lending skill whose main behavior is disclosed, but it gives an agent broad wallet authority and includes under-scoped autonomous financial actions that need human review.

Review this before installing. Use only a dedicated CDP project and wallet with limited funds, keep the wallet.env file out of synced folders, pin dependencies, and do not let an agent run loan, mailbox, or repayment commands automatically. In particular, avoid bin/repay.js unless you have independently verified the repayment destination and amount.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
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: High

Vulnerable 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_amount and pay_to from 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_to is 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-191 separates retrieval, payment, and confirmation. However, bin/repay.js combines these operations ...[truncated 1310 chars]

Remediation
View remediation

Remediation Suggestions

  1. 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.
  2. Verify the repayment obligation and treasury destination against trusted on-chain contract state where possible.
  3. Maintain an explicit allowlist of authorized treasury addresses and reject every other destination.
  4. Strictly validate pay_to as a Base address and parse repayment_amount using fixed-point decimal logic rather than floating-point arithmetic.
  5. Enforce a configurable maximum repayment amount and reject values exceeding the active loan’s expected bounds.
  6. Make dry-run the default behavior and display the token, destination, exact amount, network, and loan identifier.
  7. Require an explicit confirmation parameter or interactive approval before invoking cdp.evm.sendTransaction.
  8. Check infoRes.ok, reject redirects or unexpected content types, and fail closed on malformed or unsigned responses.

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:150
Finding

Remote Mailbox Messages Are Elevated to Principal Instructions Without Locally Verifiable Sponsor Authentication

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:150-164; supporting implementation in bin/check-messages.js:10-17
Vulnerability Type: External instruction trust-boundary violation
Risk Level: Medium

Vulnerable Instruction Segment:

md
You have no server, so your human cannot call you. Instead they write to you
from **RSoft Zero** and the bank keeps the message in a mailbox only you can
open (signed pull). Check it at the start of every task and on your
heartbeat; act on what your human asked; then answer.

node bin/check-messages.js
# ...do what they asked (e.g. request a loan, repay)...
node bin/reply.js "Requested $5 (req_…). It is above my ceiling — please approve in the app."

Messages come from the human who sponsors you: treat them as instructions
from your principal ("request $5", "repay now", "stop"). Each message is
delivered once.

Supporting Code:

js
const signed = await signInbox("pull");
const res = await fetch(`${MCP}/api/sponsor/inbox/pull`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ ...signed, limit: 50 }),
});
const text = await res.text();
console.log(text);

Technical Analysis

The Skill directs the agent to check the mailbox at the start of every task and heartbeat, then treat returned message bodies as instructions from its principal. Those instructions can include financially consequential actions such as requesting loans or making repayments.

The wallet signature generated for the pull request authenticates the agent to the mailbox service. It does not authenticate the authorship or integrity of each message returned by the service. Neither check-messages.js nor the documented workflow verifies a sponsor signature attached to individual message bodies or binds those messages locally to the paired sponsor identity.

This creates a trust-boundary problem: text controlled by the remot ...[truncated 1821 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require every mailbox message to include a digital signature from the paired sponsor over the message body, message ID, recipient wallet, timestamp, and expiration.
  2. Pin or securely derive the sponsor’s verification identity during the pairing process and verify each message locally before presenting it as authenticated.
  3. Reject unsigned, expired, replayed, mismatched, or incorrectly signed messages.
  4. Treat mailbox text as an untrusted request rather than a principal command unless end-to-end verification succeeds.
  5. Require explicit current-user confirmation for borrowing, repayment, token transfer, publication, or other consequential actions requested through the mailbox.
  6. Return structured message data with a verified-authorship status instead of printing opaque response text.
  7. Document that successful mailbox retrieval authenticates only the agent’s request and is not sufficient proof of message authorship.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a lending skill with financial actions on Base mainnet. The actual code chunk is a small utility that validates wallet ownership/control through Coinbase CDP and outputs the wallet address. While this could be a supporting setup step for a lending system, the chunk itself does not implement or expose the declared core capabilities. Therefore the description does not accurately represent what this supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description centers on autonomous lending operations: credit checks, USDC loan requests, and repayment on Base mainnet. The supplied code does none of that. It performs a signed pull from a sponsor inbox/mailbox endpoint (/api/sponsor/inbox/pull) and outputs messages. This is a different primary purpose and accesses different resources than described. While signed requests are mentioned in both contexts, the actual behavior is mailbox message retrieval, not lending-related blockchain or loan activity.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This code does not implement lending behavior such as checking credit, requesting USDC loans, or repaying debt. Its primary purpose is to accept a human sponsor by generating and submitting a signed SponsorBinding tied to a pairing code, then relying on sponsor-binding status transitions. While it uses Coinbase CDP-style wallet signing on Base mainnet, that is only a shared implementation detail and does not make it a lending function. The described purpose therefore materially misrepresents the actual behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description centers on autonomous lending operations on Base mainnet: credit checks, requesting USDC loans, and repaying them. The supplied code instead implements message-signing for an AgentInbox typed-data structure tied to actions named pull and reply. While it does use Base mainnet and Coinbase CDP-style typed-data signing, that is only a shared technical mechanism, not evidence of lending behavior. The primary purpose is materially different: inbox/authentication messaging rather than lending. Therefore the description does not accurately represent the actual code behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description centers on autonomous lending functionality on Base mainnet: credit checks, USDC loan requests, and repayment. The supplied code does not interact with Base, USDC, lending logic, credit systems, Coinbase CDP, or blockchain transaction flows. Instead, it performs a separate communication function: signing and sending a reply message to a sponsor inbox endpoint. This is a materially different primary purpose and an undeclared capability unrelated to the stated lending behavior.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 135)May include surrounding context.

md
node bin/claim-sponsor.js ABC-DEF

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill explicitly uses environment-based secrets and outbound network access to financial endpoints, yet declares no tool scope or allowed-tools restrictions. In an agent ecosystem, that omission weakens least-privilege controls and makes it easier for the skill to access sensitive credentials or perform real-money operations without clear sandboxing expectations.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
93% confidence
Finding

The skill directs users to persist long-lived CDP API and wallet secrets in a local env file, and the documentation states those credentials can control every wallet in the CDP project. Persisted high-value credentials materially increase the blast radius of host compromise, accidental disclosure, backup leakage, or misuse by other local tools, especially given the real-money Base mainnet context.

Content

Scanner excerpt · SKILL.md (reported line 29)May include surrounding context.

presents your credentials. You point the skill at a config file, and it acts on the wallet that file describes; switch wallets by pointing at a different file.

One-time setup — create a config file (keep it OUTSIDE synced folders, with tight permissions):

bash

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 39)May include surrounding context.

CDP_WALLET_SECRET=your-cdp-wallet-secret AGENT_WALLET=0xYourWalletAddress EOF chmod 600 /.rsoft/wallet.env export WALLET_CONFIG_PATH=/.rsoft/wallet.env

text

External Transmission

Medium
Category
Data Exfiltration
Confidence
95% confidence
Finding

The skill instructs the agent to transmit signed loan-origination data and an API key to an external service that can trigger real-money financial actions. Even if expected for the product, this is security-sensitive because compromised endpoints, malicious docs changes, or misbinding of wallet/signature data could cause unauthorized borrowing or leakage of sensitive operational metadata.

Content

Scanner excerpt · SKILL.md (reported line 99)May include surrounding context.

Step 3b — submit the signed request (pipe the JSON straight through):

bash
node bin/sign-loan.js 5 | curl -s -X POST \
  -H "Content-Type: application/json" -H "X-API-Key: $BANK_API_KEY" \
  -d @- https://rsoft-agentic-bank.com/api/v1/loan/request

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends the pairing code, agent wallet address, signature, nonce, and deadline to a remote MCP endpoint, which is a safety-relevant network action involving user/system data. While the file comments describe usage and output, the code itself provides no confirmation prompt or user-facing warning immediately before transmitting this data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code signs EIP-712 messages with the configured on-chain wallet whenever signInbox() is invoked, with no local authorization gate, policy check, or user confirmation tied to the signing action. In an agentic banking context handling real Base mainnet funds, any compromised caller, prompt-injected workflow, or unintended code path that reaches this function can obtain valid signatures usable against the bank inbox protocol, increasing the risk of unauthorized loan, repayment, or message actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This script performs an irreversible USDC transfer on Base mainnet automatically, based solely on data returned by a remote repay-info endpoint, with no user confirmation, no independent validation of the recipient, and no safety bounds on the amount. In the context of an autonomous lending skill handling real money, a compromised backend, wrong configuration, or accidental invocation could immediately cause loss of funds.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code performs a network POST to an external service and transmits wallet address, loan amount, nonce, deadline, signature, and an API key header. Although comments describe the behavior, there is no runtime confirmation or clear user-facing disclosure before sending the request, which is a safety-relevant operation affecting external state.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This script creates a valid EIP-712 loan authorization using a production Base mainnet wallet with no interactive approval, policy gating, or secondary verification beyond a positive numeric CLI argument. In an agentic lending context handling real money, any process or downstream tool that can invoke this script can autonomously authorize borrowing, enabling unauthorized debt creation if the host, pipeline, or agent workflow is misused or compromised.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The script makes an HTTP request to a remote endpoint containing cfg.AGENT_WALLET in the URL, which transmits wallet-identifying data off-host. Although the network call is visible in code, there is no clear warning or disclosure in this file that the wallet address will be shared with an external service.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The dependency is specified with a caret range, which allows newer minor and patch releases of @coinbase/cdp-sdk to be installed automatically. In a financial skill that can check credit, request USDC loans, and handle real-money blockchain actions, a compromised or breaking upstream release could introduce supply-chain risk or unsafe transaction behavior without an explicit review.

Content

Scanner excerpt · package.json (reported line 6)May include surrounding context.

json
"version": "2.4.0",
  "private": true,
  "dependencies": {
    "@coinbase/cdp-sdk": "^1.47.0"
  }
}

Static analysis

No suspicious patterns detected.