Back to skill

Security audit

XPR DeFi

Security checks for vulnerabilities and agentic risk

Overview

This DeFi skill is mostly coherent, but it can sign real XPR financial and multisig transactions with under-declared key access and one state-changing multisig cancel tool bypasses the skill’s own confirmation rule.

Install only if you intend to let this skill sign XPR Network DeFi and multisig transactions. Use a dedicated low-privilege account or restricted permission, set a trusted RPC endpoint, preview every transaction out of band, and avoid using the multisig tools for arbitrary or externally supplied proposals until confirmation and proposal-inspection gaps are fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
src/index.ts:956
Finding

Arbitrary User-Supplied Contracts Receive Wallet-Authorized Transfer Actions

Content
View full analysis

Vulnerability Details

File Location: src/index.ts:956-959, src/index.ts:1048-1058, src/index.ts:1176-1180, src/index.ts:1470-1473
Vulnerability Type: Insufficient contract validation and excessive signing authority
Risk Level: Medium

Vulnerable Code

ts
// src/index.ts:956-959
{
  account: fromSpec.contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'proton.swaps', quantity: fromQty, memo: '' },
},
ts
// src/index.ts:1048-1058
{
  account: params.token1_contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'proton.swaps', quantity: params.token1, memo: '' },
},
{
  account: params.token2_contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'proton.swaps', quantity: params.token2, memo: '' },
},
ts
// src/index.ts:1176-1180
const depositActions = params.from_tokens.map((t: any) => ({
  account: t.contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'token.escrow', quantity: t.quantity, memo: '' },
}));
ts
// src/index.ts:1470-1473
{
  account: lp_contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'yield.farms', quantity: lp_amount, memo: '' },
},

Technical Analysis

Several write tools accept token contract accounts directly from tool parameters and use those accounts as the target of wallet-authorized transfer actions. The supplied contracts are not consistently resolved from trusted market, pool, escrow, or farm metadata, nor are they checked against an allowlist of supported XPR token contracts.

The confirmed: true gate only confirms the overall operation. It does not establish that the contract account embedded in ...[truncated 1421 chars]

Remediation
View remediation

Remediation Suggestions

  • Resolve token contracts from trusted on-chain market, pool, escrow, or farm records rather than accepting arbitrary contract names.
  • Maintain an explicit allowlist of supported token contracts, including eosio.token, xmd.token, loan.token, xtokens, and verified LP-token contracts.
  • Validate all contract values as valid EOSIO account names.
  • Verify that the token symbol and precision match authoritative contract or pool metadata.
  • Display the exact contract, action, recipient, quantity, and authorization in a transaction preview before requesting confirmation.
  • Use a dedicated XPR permission restricted to the documented contracts and actions instead of the unrestricted active permission.
  • Add negative tests proving that unrecognized contract accounts are rejected before signing.

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:1237
Finding

Untrusted RPC Escrow Data Directly Controls Signed Deposit Actions

Content
View full analysis

Vulnerability Details

File Location: src/index.ts:1237-1252
Vulnerability Type: Unvalidated external data used to construct authorized blockchain actions
Risk Level: Medium

Vulnerable Code

ts
// Fetch the escrow to know what tokens to deposit
const rows = await getTableRows(rpcEndpoint, {
  code: 'token.escrow', scope: 'token.escrow', table: 'escrows',
  lower_bound: escrow_id, upper_bound: escrow_id, limit: 1,
});
if (rows.length === 0) return { error: `Escrow ${escrow_id} not found` };

const escrow = rows[0];
const toTokens: any[] = escrow.toTokens || [];

// Deposit the required toTokens
const depositActions = toTokens.map((t: any) => ({
  account: t.contract,
  name: 'transfer',
  authorization: [{ actor: account, permission }],
  data: { from: account, to: 'token.escrow', quantity: t.quantity, memo: '' },
}));

Technical Analysis

Fetching escrow information is necessary for the declared OTC-fill functionality and does not itself transmit the private key. However, the endpoint is configurable, and its response is treated as authoritative when constructing wallet-signed actions.

The handler does not verify that the returned row's identifier exactly equals escrow_id. It also does not validate the escrow status, expiry, intended counterparty, token contracts, quantities, or the assets the user will receive. Values in toTokens directly control the target contract and quantity of each signed action.

An RPC provider is not supposed to alter blockchain state, but a malicious, compromised, or incorrectly configured provider can return fabricated query results. Because the transaction is submitted after signing, contract-level checks may reject some inconsistencies, but the Skill should not rely exclusively on downstream contracts to protect wallet authorization.

Attack Path

  1. The Skill is configured with a malicious or compromised XPR_RPC_ENDPOINT.
  2. The opera ...[truncated 941 chars]
Remediation
View remediation

Remediation Suggestions

  • Restrict RPC configuration to trusted HTTPS endpoints or an administrator-controlled allowlist.
  • Query multiple independent RPC providers and compare security-sensitive escrow fields before signing.
  • Verify that the returned row ID exactly matches escrow_id.
  • Validate escrow status, expiry, intended recipient, creator, and fill eligibility.
  • Validate every token quantity as finite, positive, correctly formatted, and within a configured maximum.
  • Resolve or allowlist all token contracts before constructing actions.
  • Present the fetched you_sent and you_received assets to the operator and obtain confirmation only after the lookup.
  • Consider reading authoritative state and simulating the complete transaction before requesting wallet authorization.

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:1719
Finding

Multisig Cancellation Bypasses the Documented Confirmation Gate

Content
View full analysis

Vulnerability Details

File Location: src/index.ts:1719-1738
Vulnerability Type: Missing authorization safeguard on a state-changing operation
Risk Level: Medium

Vulnerable Code

ts
// ── 24. msig_cancel ──
api.registerTool({
  name: 'msig_cancel',
  description: 'Cancel a multisig proposal you created.',
  parameters: {
    type: 'object',
    required: ['proposal_name'],
    properties: {
      proposal_name: { type: 'string', description: 'Proposal name to cancel' },
    },
  },
  handler: async ({ proposal_name }: { proposal_name: string }) => {
    if (!isValidEosioName(proposal_name)) return { error: 'Invalid proposal_name' };
    try {
      const { api: eosApi, account, permission } = await getSession();
      const result = await eosApi.transact({
        actions: [{
          account: 'eosio.msig', name: 'cancel',
          authorization: [{ actor: account, permission }],
          data: { proposer: account, proposal_name, canceler: account },
        }],
      }, { blocksBehind: 3, expireSeconds: 30 });

Technical Analysis

SKILL.md states that all write operations require confirmed: true. Other state-changing handlers enforce this rule, but msig_cancel neither declares a confirmed parameter nor checks it before signing and broadcasting the cancellation.

Proposal-name validation prevents malformed EOSIO names but does not confirm operator intent. Consequently, any successful invocation containing a valid proposal name immediately performs a signed write operation.

Attack Path

  1. Untrusted content or an agent-planning error causes msig_cancel to be selected.
  2. A valid proposal name belonging to the configured account is supplied.
  3. No explicit transaction confirmation is requested or checked.
  4. The Skill loads the wallet signer and broadcasts eosio.msig::cancel.
  5. The active proposal is cancelled before its approval workflow ...[truncated 340 chars]
Remediation
View remediation

Remediation Suggestions

  • Add confirmed to the tool's required parameters and property schema.
  • Reject execution unless confirmed === true.
  • Return a preview containing the proposer account and proposal name when confirmation is absent.
  • Require confirmation immediately before loading the signing session.
  • Add msig_cancel to confirmation-gate tests; the existing test list does not validate this write operation.
  • Apply a centralized write-operation wrapper so future tools cannot omit the confirmation policy.

T09 · Insecure Skill Coding Practices

Note
Location
skill.json:40
Finding

Skill Manifest Omits Required Private-Key and Signing Environment Access

Content
View full analysis

Vulnerability Details

File Location: skill.json:40-42 and src/index.ts:175-190
Vulnerability Type: Undeclared sensitive credential access and excessive key exposure
Risk Level: Low

Vulnerable Code

json
"requires": {
  "env": []
}
ts
async function getSession(): Promise<{ api: any; account: string; permission: string }> {
  if (cachedSession) return cachedSession;

  const privateKey = process.env.XPR_PRIVATE_KEY;
  const account = process.env.XPR_ACCOUNT;
  const permission = process.env.XPR_PERMISSION || 'active';
  const rpcEndpoint = process.env.XPR_RPC_ENDPOINT;

  if (!privateKey) throw new Error('XPR_PRIVATE_KEY is required for write operations');
  if (!account) throw new Error('XPR_ACCOUNT is required for write operations');
  if (!rpcEndpoint) throw new Error('XPR_RPC_ENDPOINT is required for write operations');

  const { Api, JsonRpc, JsSignatureProvider } = await import('@proton/js');
  const rpc = new JsonRpc(rpcEndpoint);
  const signatureProvider = new JsSignatureProvider([privateKey]);
  const api = new Api({ rpc, signatureProvider });

  cachedSession = { api, account, permission };
  return cachedSession;
}

Technical Analysis

The manifest declares an empty environment requirement list, while write operations require direct access to XPR_PRIVATE_KEY, XPR_ACCOUNT, and XPR_RPC_ENDPOINT, with optional access to XPR_PERMISSION. The private key is loaded into an in-process signature provider, and the resulting signing session is cached.

This mismatch prevents operators and automated policy systems from accurately evaluating the Skill's credential requirements before installation. Using a raw private key in the same process as agent-controlled tooling also enlarges the consequences of a process compromise. The default permission is active, which may be broader than the documented DeFi actions require.

Attack Path

  1. An o ...[truncated 879 chars]
Remediation
View remediation

Remediation Suggestions

  • Declare XPR_PRIVATE_KEY, XPR_ACCOUNT, XPR_RPC_ENDPOINT, and XPR_PERMISSION in the manifest, distinguishing required write-operation settings from read-only settings.
  • Clearly document that write tools use a raw signing key and that the key remains available to the running process.
  • Prefer an external wallet, hardware signer, or isolated signing service that displays and approves transaction contents.
  • Create a dedicated XPR permission restricted to the exact contracts and actions required by this Skill.
  • Do not default silently to the broad active permission; require an explicitly configured restricted permission where possible.
  • Minimize signer lifetime and avoid caching credential-bearing sessions longer than necessary.
  • Ensure errors and logs never include the private key or signer internals.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The tool list exposes multiple irreversible financial actions such as order placement, withdrawals, swaps, liquidity removal, OTC fulfillment, staking changes, and multisig proposal/approval, yet the manifest provides no user-facing warning or acknowledgment of financial risk. In this context, missing warnings are dangerous because users and downstream agents may treat the skill like an informational utility despite its ability to move assets, lock funds, or authorize treasury actions.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The msig_propose tool accepts an arbitrary array of blockchain actions and serializes them for an eosio.msig proposal, without restricting contracts, action names, or tying them to the stated DeFi-only scope. In this skill context, that effectively exposes a general-purpose transaction construction primitive that can be used to propose transfers, permission changes, contract admin actions, or any other chain operation the account could encode.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
78% confidence
Finding

The skill exposes high-risk financial and multisig capabilities but does not declare an explicit tool scope such as allowed-tools or permissions. If the runtime infers or grants broader env/network access than intended, the agent may be able to reach external resources or use undeclared capabilities, weakening least-privilege controls around sensitive DeFi actions.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest describes a very broad DeFi skill surface spanning trading, swaps, OTC escrow, farming, liquidity, and multisig actions, but it does not define any boundaries for when these operations may be invoked or which ones require elevated confirmation. In an agent setting, ambiguous scope increases the chance of unintended high-risk tool invocation, especially where natural-language requests could be interpreted as authorization for asset-moving actions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The description claims the proposal is inert until humans approve and execute it, but the same skill also exposes msig_approve, allowing the agent to programmatically contribute an approval with the loaded key. This mismatch can mislead operators into underestimating risk and may encourage unsafe use of proposal creation as if it were harmless drafting rather than part of an executable signing workflow.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The msig_approve tool will approve any proposal identified by proposer and proposal_name using the local account key, without inspecting the underlying transaction contents or enforcing a DeFi-only policy. That means a user or upstream agent could cause approval of a malicious or unrelated multisig proposal, turning this skill into a signing surface for arbitrary blockchain actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The msig_cancel tool performs an on-chain state-changing action immediately without requiring a confirmed=true style execution gate, unlike most other write operations in the file. This inconsistency increases the chance of accidental or prompt-induced cancellation of legitimate proposals, which can disrupt governance or operational workflows.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/index.ts:178

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
src/index.ts:1633