Back to skill

Security audit

XPR NFT (AtomicAssets)

Security checks for vulnerabilities and agentic risk

Overview

This NFT skill is mostly purpose-aligned, but it handles wallet signing and blockchain transactions with under-disclosed credentials and one write action that bypasses its stated confirmation rule.

Review before installing. Use a dedicated low-value XPR account, a restricted permission rather than active, and a trusted HTTPS RPC endpoint. Treat every write tool, including auction claiming, as a real blockchain transaction that may move assets, spend tokens, consume resources, or be irreversible.

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

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:29
Finding
Privileged Wallet Signer Trusts an Unrestricted RPC Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts`, lines 29-41 **Vulnerability Type**: Unrestricted external trust boundary around a privileged transaction signer **Risk Level**: Medium ### Complete Code Snippet ```typescript 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 NFT write operations'); if (!account) throw new Error('XPR_ACCOUNT is required for NFT write operations'); if (!rpcEndpoint) throw new Error('XPR_RPC_ENDPOINT is required for NFT 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 }); ``` ### Technical Analysis The Skill accepts `XPR_RPC_ENDPOINT` directly from the environment without enforcing HTTPS, verifying the endpoint hostname, pinning the expected chain ID, or otherwise establishing that the endpoint belongs to the intended XPR network. The RPC client is then connected to a signing API holding the wallet's private key. The code does not explicitly transmit the raw private key through `fetch`. The key is provided locally to `JsSignatureProvider`, while signed transactions and public transaction details are sent to the configured RPC endpoint. Nevertheless, the RPC endpoint supplies chain state and ABI information used during transaction construction and serialization. A malicious or compromised endpoint could return deceptive chain information, malicious ABI data, or incorrect transaction context. The default permission is `active`, which is commonly broader than the NFT-specific authority required by this Skill. Consequently, the signer may possess more authority than the declared AtomicAssets and AtomicMarket ...[truncated 1461 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require RPC endpoints to use HTTPS and reject plaintext HTTP URLs. 2. Maintain an allowlist of trusted XPR mainnet and testnet RPC hostnames. Require explicit administrative approval for custom endpoints. 3. Query and verify the chain ID against a pinned expected value before enabling any signing operation. 4. Verify that configured contract accounts and network selection match the intended XPR deployment. 5. Use a dedicated wallet key with a custom permission restricted to the necessary `atomicassets`, `atomicmarket`, and approved token-contract actions. 6. Do not default to `active`; require an explicitly configured restricted permission for production writes. 7. Separate read-only RPC configuration from the transaction-submission endpoint so an untrusted read provider cannot automatically become part of the signing workflow. 8. Document the RPC trust boundary and warn operators that the endpoint participates in transaction construction and submission. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
src/index.ts:1438
Finding
Auction Claim Broadcasts a Transaction Without Required Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts`, lines 1438-1485 **Vulnerability Type**: Missing confirmation control for a state-changing blockchain operation **Risk Level**: Low ### Complete Code Snippet ```typescript api.registerTool({ name: 'nft_claim_auction', description: 'Claim won assets (buyer) or sale proceeds (seller) from a completed auction. No risk — just claims what is rightfully yours.', parameters: { type: 'object', required: ['auction_id'], properties: { auction_id: { type: 'string', description: 'Auction ID to claim' }, }, }, handler: async ({ auction_id }: { auction_id: string }) => { if (!auction_id) return { error: 'auction_id is required' }; try { const session = await getNftSession(); const actions = [ { account: 'atomicmarket', name: 'auctclaimbuy', authorization: [{ actor: session.account, permission: session.permission }], data: { auction_id: Number(auction_id) }, }, { account: 'atomicmarket', name: 'auctclaimsell', authorization: [{ actor: session.account, permission: session.permission }], data: { auction_id: Number(auction_id) }, }, ]; try { const result = await session.api.transact( { actions: [actions[0]] }, { blocksBehind: 3, expireSeconds: 30 } ); return { transaction_id: result.transaction_id || result.processed?.id, auction_id, claim_type: 'buyer' }; } catch { // Not the buyer — try seller claim } try { const result = await session.api.transact( { actions: [actions[1]] }, { blocksBehind: 3, expireSeconds: 30 } ); return { transaction_id: result.transaction_id || result.processed?.id, auction_id, claim_type: 'seller' }; } catch { ...[truncated 1811 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add `confirmed` to the tool's required parameters: ```typescript required: ['auction_id', 'confirmed'] ``` 2. Define the parameter as a boolean and require it to be exactly `true`. 3. Reject the request before loading the wallet session when confirmation is absent: ```typescript if (confirmed !== true) { return { error: 'Confirmation required. Set confirmed=true to claim this auction.' }; } ``` 4. Change the handler type to include `confirmed`. 5. Replace the phrase “No risk” with a clear statement that the operation signs and broadcasts a blockchain transaction. 6. Present the auction ID, expected claim role, network, account, and any resource implications before confirmation. 7. Where possible, query the auction first and determine whether the account is the buyer or seller rather than attempting two separate transactions. ]]>

other

Note
Location
skill.json:34
Finding
Required Wallet Credentials Are Missing From Skill Metadata<![CDATA[ ## Vulnerability Details **File Location**: `skill.json`, lines 34-36 **Vulnerability Type**: Undeclared sensitive environment and configuration requirements **Risk Level**: Low ### Complete Code Snippet The metadata declares no required environment variables: ```json "requires": { "env": [] } ``` The implementation nevertheless requires sensitive wallet and endpoint configuration: ```typescript 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 NFT write operations'); if (!account) throw new Error('XPR_ACCOUNT is required for NFT write operations'); if (!rpcEndpoint) throw new Error('XPR_RPC_ENDPOINT is required for NFT write operations'); ``` ### Technical Analysis The manifest reports an empty environment requirement list even though write operations depend on `XPR_PRIVATE_KEY`, `XPR_ACCOUNT`, and `XPR_RPC_ENDPOINT`, with optional use of `XPR_PERMISSION` and `XPR_NETWORK`. This discrepancy prevents deployment tooling and users from accurately understanding the Skill's credential requirements before installation. In particular, the manifest fails to communicate that the Skill handles a high-value private signing key and can perform irreversible or financially consequential blockchain operations. The issue does not independently expose the key, but it weakens secure deployment, secret provisioning, permission review, and least-privilege assessment. ### Attack Path 1. A user or deployment system inspects `skill.json` and concludes that the Skill requires no environment credentials. 2. The Skill is installed without a dedicated secret-management or restricted-wallet plan. 3. To make write operations work, an operator supplies credentials through an ad hoc mechanism or reuses a broadly privileged account key. 4. The key may conseq ...[truncated 707 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare the required environment variables in `skill.json`, including: - `XPR_PRIVATE_KEY` - `XPR_ACCOUNT` - `XPR_RPC_ENDPOINT` 2. Declare `XPR_PERMISSION` and `XPR_NETWORK` as optional configuration values with secure documented defaults. 3. Mark `XPR_PRIVATE_KEY` as sensitive if the manifest schema supports secret annotations. 4. Document that read-only tools can operate without the private key while write tools require a signer. 5. Recommend a dedicated NFT account and a restricted custom permission rather than a general-purpose `active` key. 6. Require secret-manager injection instead of plaintext configuration files or command-line arguments. 7. Ensure logs and error reports never print private-key values. ]]>
Vulnerability Patterns
  • 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
  • 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
87% confidence
Finding
The skill exposes operational guidance for NFT creation, minting, transfer, and market actions, but it does not declare an explicit tool scope such as allowed-tools or permissions. In a skill that can trigger blockchain transactions and use network-capable or environment-capable tooling, missing scope boundaries increases the risk that an agent may invoke unintended tools or broader capabilities than the skill actually requires.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The description advertises full NFT lifecycle support including minting, selling, transferring, burning, and auction flows, but it does not warn users that some operations are destructive, irreversible, or financially significant. In an agent context, this increases the risk of accidental burns, unintended listings or purchases, and irreversible asset transfers because users may treat the skill as informational rather than transactional.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
This is a true issue because the tool description explicitly claims auction claiming has 'No risk' even though the handler performs authenticated on-chain write transactions using the configured private key. Mislabeling a signed financial/blockchain action as risk-free can mislead an agent or user into approving state-changing operations without appropriate caution, especially in a skill whose primary purpose is to manage NFT assets and market actions.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

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