Back to skill

Security audit

xpr-xmd

Security checks for vulnerabilities and agentic risk

Overview

This XMD skill is purpose-aligned but needs review because it can sign real financial transactions with an environment private key that is not declared in its metadata.

Review before installing. Use this only with a dedicated, narrowly scoped XPR permission and small limits, not a broad active key. Treat xmd_mint and xmd_redeem as real financial transactions and require an external transaction preview or manual wallet confirmation before allowing confirmed=true.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:486
Finding
Incomplete Preflight Validation for Financial Transactions<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts:486-523` and `src/index.ts:577-610` **Vulnerability Type**: Incomplete transaction validation and weak confirmation binding **Risk Level**: Medium ### Vulnerable Code Minting performs only limited state checks before signing and broadcasting the transfer: ```ts // Look up token config const tokens = await getTableRows(rpcEndpoint, { code: XMD_TREASURY, scope: XMD_TREASURY, table: 'tokens', limit: 50, }); const token = tokens.find((t: any) => { const parsed = parseExtSym(t.symbol); return parsed?.symbol === sym; }); if (!token) { const available = tokens.map((t: any) => parseExtSym(t.symbol)?.symbol).filter(Boolean); return { error: `Token "${sym}" not supported. Available: ${available.join(', ')}` }; } if (!token.isMintEnabled) { return { error: `Minting with ${sym} is currently disabled` }; } const parsed = parseExtSym(token.symbol); if (!parsed) return { error: 'Could not parse token symbol' }; // Check treasury is not paused const globals = await getTableRows(rpcEndpoint, { code: XMD_TREASURY, scope: XMD_TREASURY, table: 'xmdglobals', limit: 1, }); if (globals.length > 0 && globals[0].isPaused) { return { error: 'XMD treasury is currently paused' }; } const quantity = formatAsset(amount, parsed.precision, parsed.symbol); const { api: eosApi, account, permission } = await getXmdSession(); const result = await eosApi.transact({ actions: [{ account: parsed.contract, name: 'transfer', authorization: [{ actor: account, permission }], data: { from: account, to: XMD_TREASURY, quantity, memo: 'mint', }, }], }, { blocksBehind: 3, expireSeconds: 30 }); ``` The redeem path has the same weakness: ```ts // Validate the target collateral exists and redeem is enabled const tokens = await getTableRows(rpcEndpoint, { code: XMD_TREASURY, scope: XMD_TREASURY, table: 'tokens', limit: 50, }); const token = tokens.find((t: any) => { ...[truncated 3823 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Perform an atomic preflight immediately before signing: - Fetch `xmdglobals`. - Reject when the treasury is paused. - Fetch the selected collateral configuration. - Verify mint or redeem status. - Fetch the current aggregate oracle price. - Enforce `minOraclePrice`. - For minting, calculate and enforce the projected treasury percentage. - Fetch and verify the user's token balance. - For redemption, verify treasury liquidity for the requested collateral. 2. Generate a canonical transaction preview containing: - Source account and permission. - Token contract. - Asset and exact fixed-point quantity. - Destination. - Memo. - Current oracle price. - Fees. - Expected output. - Minimum acceptable output or slippage bound. - Expiration time. 3. Bind confirmation to that exact preview using a nonce or hash. Do not treat a generic Boolean supplied alongside transaction parameters as sufficient confirmation. 4. Re-fetch time-sensitive state after confirmation and abort if the price, fee, cap, enabled status, destination, memo, or quantity differs from the approved preview. 5. Parse amounts as decimal strings or integer smallest units rather than JavaScript floating-point numbers. Reject values with excessive precision, non-finite values, or values outside configured limits. 6. Add configurable per-transaction and cumulative amount limits. Require elevated confirmation for unusually large transactions. 7. Preserve contract-side validation as defense in depth rather than using it as the only enforcement mechanism. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
skill.json:19
Finding
Signing Credentials and Permission Requirements Are Undeclared in Skill Metadata<![CDATA[ ## Vulnerability Details **File Location**: `skill.json:19-21`; credential use occurs at `src/index.ts:81-90` **Vulnerability Type**: Undeclared sensitive environment access and unsafe permission default **Risk Level**: Low ### Vulnerable Code The Skill metadata declares no environment requirements: ```json "requires": { "env": [] } ``` The implementation nevertheless reads a private signing key and account from the environment and silently defaults to the `active` permission: ```ts const privateKey = process.env.XPR_PRIVATE_KEY; const account = process.env.XPR_ACCOUNT; const permission = process.env.XPR_PERMISSION || 'active'; if (!privateKey) throw new Error('XPR_PRIVATE_KEY is required for XMD write operations'); if (!account) throw new Error('XPR_ACCOUNT is required for XMD write operations'); const { Api, JsonRpc, JsSignatureProvider } = await import('@proton/js'); const rpc = new JsonRpc(MAINNET_RPC); const signatureProvider = new JsSignatureProvider([privateKey]); const api = new Api({ rpc, signatureProvider }); ``` ### Technical Analysis The declared metadata does not inform deployment tooling or users that write operations require access to a private blockchain signing key. This discrepancy prevents installers and policy engines from accurately assessing the Skill's credential requirements and enforcing separation between read-only and signing modes. The optional permission variable defaults to `active`, which may grant substantially broader authority than the mint and redeem operations require. A dedicated, contract-restricted permission would better satisfy least privilege. No evidence shows that the implementation directly transmits `XPR_PRIVATE_KEY` in an RPC body. The key is passed to `JsSignatureProvider` for local signing. Therefore, this finding concerns undeclared credential access and permission scope, not confirmed key exfiltration. ### Attack Path 1. A user or deployment system reviews `skill.json` and observes tha ...[truncated 1164 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare credential requirements accurately in `skill.json`, distinguishing: - Required write-mode variables: `XPR_PRIVATE_KEY` and `XPR_ACCOUNT`. - Optional variable: `XPR_PERMISSION`. - Read-only mode, which should require no signing credentials. 2. Clearly disclose that the private key is used locally to sign mainnet transactions and identify the RPC destination used for broadcast. 3. Do not silently default to `active`. Require an explicit permission value for write mode or use a narrowly scoped default only when its authority is verifiably restricted. 4. Recommend and document a dedicated XPR permission limited to: - Transfers from the intended account. - The approved token contracts. - The `xmd.treasury` destination. - Appropriate transaction limits where supported. 5. Separate read-only and write capabilities at runtime so read-only invocations do not initialize a signing provider or require private-key access. 6. Avoid logging, serializing, returning, or including the private key in exception messages. Clear cached signing state when the host lifecycle permits. 7. Pin and review the `@proton/js` dependency through a package manifest and lockfile because it receives sensitive key material. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill describes tool-based interactions, including write operations, but does not declare an explicit tool scope such as allowed-tools or permissions. When a skill with detected env/network capabilities lacks a restrictive scope, the runtime or agent may expose broader capabilities than intended, increasing the chance of unauthorized network access, environment data exposure, or misuse of available tools.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The manifest advertises mint and redeem capabilities but provides no user-facing warning that these are financial, state-changing actions. In DeFi/stablecoin workflows, missing safety language can lead an orchestrating agent to treat transactional tools like ordinary informational queries, increasing the risk of unintended asset movement or financially consequential actions.

Vague Triggers

Low
Confidence
89% confidence
Finding
The manifest description is broad enough to match general stablecoin-related requests without clearly constraining when the skill should be invoked. In a financial context, overly broad routing increases the chance that an agent invokes mint/redeem tooling in situations where the user only wanted information, creating unnecessary exposure to state-changing operations.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

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