Back to skill

Security audit

SelfPaid - Zero-Cost DeFi Payments

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real DeFi payment tool, but it relies on a hosted API to build wallet transactions and its local validator does not cap the collateral amount a user agreed to lock.

Review this before installing for autonomous use. It can generate Base mainnet transactions that pay a recipient, pay a developer fee, approve USDC, and deposit collateral into Aave or Morpho. Only use it with a signer or policy layer that decodes the actual calldata, confirms the developer fee, and enforces a maximum collateral amount you approved locally.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
validate.js:116
Finding

Remote API Can Supply an Unbounded Collateral Amount Through Incomplete Local Validation

Content
View full analysis

Vulnerability Details

File Location: validate.js, lines 116–169
Vulnerability Type: Insufficient validation of remotely constructed financial transactions
Risk Level: Medium

Vulnerable Code

javascript
const call3Spender = '0x' + call3.data.slice(34, 74).toLowerCase();
const call3Amount = BigInt('0x' + call3.data.slice(74, 138));
if (call3Spender !== ALLOWLIST.aavePool && call3Spender !== ALLOWLIST.morphoVault) {
  throw new Error(`Validation failed: Call 3 spender (${call3Spender}) is not an approved yield pool`);
}
// Reject infinite / unlimited approvals
const maxUint = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
if (call3Amount === maxUint) {
  throw new Error(`Validation failed: Unlimited approval is strictly rejected`);
}

// --- Call 4: Supply Collateral to Yield Pool ---
const call4 = calls[3];
const poolTarget = norm(call4.to);
if (poolTarget !== ALLOWLIST.aavePool && poolTarget !== ALLOWLIST.morphoVault) {
  throw new Error(`Validation failed: Call 4 target (${poolTarget}) is not an approved yield pool`);
}
// Enforce Call 3 approved spender strictly equals Call 4 destination
if (call3Spender !== poolTarget) {
  throw new Error(`Validation failed: Call 3 spender (${call3Spender}) does not match Call 4 target (${poolTarget})`);
}

// Validate Call 4 calldata based on protocol
if (poolTarget === ALLOWLIST.aavePool) {
  if (!call4.data.startsWith(ALLOWLIST.selectors.aaveSupply)) {
    throw new Error(`Validation failed: Call 4 does not use Aave V3 supply selector (${ALLOWLIST.selectors.aaveSupply})`);
  }
  const asset = '0x' + call4.data.slice(34, 74).toLowerCase();
  const supplyAmount = BigInt('0x' + call4.data.slice(74, 138));
  const onBehalfOf = '0x' + call4.data.slice(162, 202).toLowerCase();

  if (asset !== ALLOWLIST.usdc) {
    throw new Error(`Validation failed: Aave supply asset is not USDC (${asset})`);
  }
  if
...[truncated 4188 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require an explicit, locally approved collateral amount as an input to validatePaymentBatch.
  2. Compare that amount exactly against both call3Amount and the decoded Aave supplyAmount or Morpho depositAmount.
  3. If collateral is derived from a multiplier and buffer, calculate it locally from user-approved values rather than trusting fields returned by the hosted API.
  4. Enforce a locally configured absolute maximum collateral amount and reject zero, negative, malformed, non-finite, or out-of-range payment parameters before conversion.
  5. Generate the confirmation summary exclusively from decoded, validated calldata. Do not use buildResponse.summary as the authoritative source for transaction amounts.
  6. Present the decoded payment, fee, approval, collateral deposit, beneficiary, and target protocol to the user or policy layer before signing.
  7. Add tests in which the remote summary reports a normal collateral amount while calldata contains a larger amount, and verify that validation rejects the batch.
  8. Consider requiring a cryptographically bound user authorization object containing the payment amount, maximum collateral, recipient, sender, protocol, and chain ID.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill markets 'zero-cost' and '100% principal recovery' prominently, but the operational model requires users to lock substantially more USDC than the payment amount for a long period, often hundreds of days. That omission can mislead agents or users into authorizing transactions without understanding the true capital commitment, liquidity loss, and protocol exposure.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

Get current APYs and see which protocol is currently offering higher yields:

bash
curl -s https://selfpaid.cryptofolio.it/api/v1/payment/rates

Response:

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The header says the layer enforces strict local validation on all remotely constructed transactions before forwarding to a signer, which implies a general transaction-validation layer. In practice, the function only validates a very specific buildPayment response format with exactly four calls in a fixed order and fixed protocol targets, so the documentation overstates the code’s scope.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The validator hard-requires a USDC transfer to a fixed developer wallet before allowing the payment flow, which means any agent using this validation layer must approve a hidden third-party payment. In an M2M payment skill, embedding and enforcing a non-optional fee transfer at validation time is dangerous because it can cause unauthorized or insufficiently disclosed value diversion from users or autonomous agents.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file computes and enforces a developer fee transfer locally without any evidence of user-facing disclosure or confirmation in the validation logic. In a skill intended for AI agents and smart accounts, this creates a high risk of silent fee extraction because downstream signers may trust validation as proof that the batch is aligned with the requested payment intent.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.