Back to skill

Security audit

Cli

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent with its payment-dispute purpose, but it handles wallet credentials and on-chain evidence in ways that can put funds or disputes at risk.

Review this carefully before installing. Use only a low-value dedicated test wallet, avoid putting private keys in shell commands, check or restrict ~/.x402r permissions, configure and verify real evidence pinning before filing disputes, and assume arbiter/court/Pinata endpoints may receive payment or dispute data. Do not use this for valuable payments until credential storage and fail-closed evidence handling are fixed.

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

Error
Location
src/config.ts:65
Finding
Wallet Private Key and Pinata JWT Are Persisted Insecurely<![CDATA[ ## Vulnerability Details **File Location**: `src/commands/config.ts:8-18`, `src/config.ts:65-79` **Vulnerability Type**: Plaintext credential storage and insecure command-line secret handling **Risk Level**: High ### Vulnerable Code `src/commands/config.ts:8-18` accepts sensitive credentials directly as command-line arguments: ```ts export function registerConfigCommand(program: Command): void { program .command("config") .description("Save or view CLI configuration") .option("-k, --key <privateKey>", "Set private key") .option("-o, --operator <address>", "Set operator address") .option("-a, --arbiter-url <url>", "Set arbiter server URL") .option("-c, --court-url <url>", "Set court UI URL (for independent verification)") .option("-n, --network <networkId>", "Set network ID (e.g., eip155:84532)") .option("-r, --rpc <url>", "Set RPC URL") .option("--pinata-jwt <jwt>", "Set Pinata JWT token") ``` `src/config.ts:65-79` merges those credentials into the configuration and writes them to disk as plaintext without explicitly restricting permissions: ```ts export function saveConfigFile(config: CliConfigFile): void { if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } // Merge with existing config const existing = loadConfigFile(); const merged = { ...existing, ...config }; // Remove undefined/null values for (const key of Object.keys(merged)) { if (merged[key as keyof CliConfigFile] === undefined || merged[key as keyof CliConfigFile] === null) { delete merged[key as keyof CliConfigFile]; } } fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2)); } ``` The documented setup command also encourages passing the private key on the command line: ```bash npx --yes @x402r/cli config --key <private-key> --arbiter-url https://www.moltarbiter.com/arbiter ``` ### Technical Analysis The configuration model includes both `privateKey` and `pinataJwt`. T ...[truncated 2560 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not accept wallet private keys through ordinary command-line arguments. - Use an interactive hidden prompt when manual entry is unavoidable. - Support input through protected standard input rather than process arguments. - Prefer hardware wallets, external signers, wallet-agent integrations, or operating-system keychains. 2. Avoid long-term private-key persistence where possible. - Retain only a signer reference or keychain identifier. - If environment-variable support remains, document that it is preferable to command-line arguments but may still leak through misconfigured process environments or logs. 3. Store secrets separately from non-sensitive configuration. - Create `~/.x402r` with mode `0700`. - Create secret files with mode `0600`. - Explicitly correct permissions on existing files rather than relying on the process umask. 4. Use secure file replacement: - Write to an owner-only temporary file in the same directory. - Flush and atomically rename it into place. - Prevent symlink-following where supported. 5. Add startup permission checks. - Refuse to load a private key from a group-readable or world-readable file. - Display a clear remediation command without printing any secret. 6. Update `SKILL.md` so examples do not place private keys directly in shell history. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/ipfs.ts:12
Finding
Failed Evidence Uploads Are Replaced with a Fixed Placeholder CID and Submitted On-Chain<![CDATA[ ## Vulnerability Details **File Location**: `src/ipfs.ts:12-40`, called from `src/commands/dispute.ts:93-100` **Vulnerability Type**: Fail-open evidence handling and integrity failure **Risk Level**: High ### Vulnerable Code The complete IPFS upload function returns a fixed CID whenever no Pinata JWT is configured or an upload fails: ```ts export async function pinToIpfs(data: Record<string, unknown>): Promise<string> { const config = getConfig(); if (config.pinataJwt) { console.log(" Pinning to IPFS via Pinata..."); try { const response = await fetch("https://api.pinata.cloud/pinning/pinJSONToIPFS", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.pinataJwt}`, }, body: JSON.stringify({ pinataContent: data, pinataMetadata: { name: `x402r-evidence-${Date.now()}` }, }), }); if (response.ok) { const result = (await response.json()) as { IpfsHash: string }; console.log(` Pinned: ${result.IpfsHash}`); return result.IpfsHash; } console.warn(` Pinata failed (${response.status})`); } catch (err) { console.warn(` Pinata error:`, err instanceof Error ? err.message : err); } } // Placeholder fallback console.log(" (Using placeholder CID — set pinataJwt in config for production)"); return "QmXyxi3LYRb33bThaHLtotFxcG4FXnDowC2d5EjwYqE4iR"; } ``` The dispute command treats the returned placeholder exactly like a successfully uploaded evidence CID: ```ts let cid: string; try { cid = await pinToIpfs(evidenceData); } catch (error) { console.error(" Failed to pin evidence:", error instanceof Error ? error.message : error); process.exit(1); } ``` It later submits that value on-chain: ```ts const { txHash } = await client.submitEvidence(paymentInfo, nonce, cid); evidenceTxHash = txHash; console.log(" Evidence submitted:", txHash); ``` ## ...[truncated 2640 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed when evidence publication fails. - Throw an error if no production pinning provider is configured. - Throw an error for non-success HTTP responses, malformed responses, timeouts, and network failures. - Do not call `submitEvidence` unless a verified CID was obtained. 2. Remove the fixed placeholder from production code. - If needed for automated tests, place it behind an explicit test-only flag. - Ensure test mode cannot submit evidence to a live network. 3. Validate the upload result. - Confirm that `IpfsHash` is present and syntactically valid. - Compute the expected content identifier locally when feasible and compare it with the service response. - Retrieve the uploaded object and verify its content before broadcasting the on-chain transaction. 4. Separate the workflow into explicit stages. - Build evidence. - Show the user what will become public. - Upload and verify it. - Display the verified CID. - Request confirmation before committing the CID on-chain. 5. Improve recovery behavior. - If the protocol permits evidence replacement or supplementation, expose a command for correcting failed evidence. - Never treat the presence of an earlier placeholder submission as a reason to skip valid evidence. 6. Clearly document that dispute evidence is uploaded to Pinata/IPFS and may become publicly accessible and permanently referenced on-chain. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (92)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose centers on payments and disputes, but the provided code chunk exposes only a TypeScript declaration for registering a configuration command. Its documented behavior is save/load CLI configuration, which is materially different from merchant payment or dispute functionality. This is a clear description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This code does not implement merchant payments or payment dispute filing. Its primary purpose is configuration management for a CLI, including saving and printing settings. That is materially different from the declared payment/dispute functionality. While configuration may support a larger payments tool, this chunk itself only manages config and sensitive connection/authentication parameters, which is an undeclared capability relative to the stated description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared description mentions two primary capabilities: paying merchants and filing payment disputes. The supplied code chunk instead exposes a list command for viewing disputes. Listing disputes is a distinct user-facing capability not covered by the declared description, and the code shown does not indicate merchant payment or dispute filing behavior. Therefore the code's behavior is materially different from the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared purpose focuses on two capabilities: merchant payments and filing payment disputes. This code does neither. Its primary function is to list existing pending disputes by calling an arbiter server API and displaying the results. That is a distinct dispute-management capability not represented in the description. While dispute listing is related to the payment dispute domain, it is still a materially different operation from filing disputes or making payments, so this should be flagged as a description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code chunk accurately matches the payment portion of the description: it initiates an x402r payment, signs the escrow payload, retries the request with payment headers, stores payment state for future use, and outputs the response. However, this specific chunk does not implement dispute filing; it only saves state and prints guidance to run a separate dispute command later. Additionally, it performs an extra external POST to an arbiter dashboard endpoint to cache payment info, which is not mentioned in the declared description. Because the declared purpose says the skill pays merchants and files disputes, while the provided code only handles payment plus ancillary state caching, this chunk is a description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The declared description says the skill is for paying merchants and filing payment disputes. The supplied code does neither: it does not initiate payments or file a dispute. Instead, it initializes a read-only client, retrieves evidence count and evidence entries for an existing dispute, and prints them. This is a materially different primary function within the dispute workflow—inspection/showing evidence rather than payment or dispute filing—so the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared description focuses on paying merchants and filing payment disputes. The actual code chunk exposes a command specifically for checking dispute status, which is a distinct capability not mentioned in the declared purpose. While related to disputes, status checking is materially different from initiating payments or filing disputes, so this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
The declared purpose says the skill pays merchants and files payment disputes. The supplied code does neither: it does not initiate payments or submit/file a dispute request. Its primary function is to retrieve and display the status of an existing dispute, using either an arbiter API (`/api/dispute/{compositeKey}`) or on-chain read methods (`hasRefundRequest`, `getRefundStatus`). That is a materially different capability from the declared behavior. The network access to an arbiter server is also not reflected in the declared permissions, though the main mismatch is the different primary purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose focuses on payment operations and dispute filing in the x402r refundable payments protocol. The actual code chunk instead exposes a CLI verify command for replaying arbiter evaluation via an independent verifier. That is a materially different primary purpose from making payments or filing disputes, and the code shown does not support the declared payment/dispute capabilities.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This code chunk does not implement paying merchants or filing payment disputes. Its primary purpose is dispute verification: it replays an arbiter evaluation through an external court UI service and reports commitment/hash comparison results and parsed AI decision content. That is materially different from the declared purpose. Although dispute verification may be related to the overall x402r workflow, it is a distinct capability not represented in the description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose focuses on payment and dispute operations in the x402r protocol, but the supplied code chunk does not implement merchant payments or dispute filing. Instead, it handles configuration management and service discovery for a CLI. While config handling could support a payments tool, this chunk’s actual behavior is materially different from the declared primary purpose, so this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose focuses on payment and dispute actions in the x402r protocol, but this code chunk does not implement payment processing or dispute filing. Instead, it handles configuration persistence, secret loading, remote contract discovery, and console printing of config. These are supporting/administrative functions, but as supplied, the chunk’s actual behavior is materially different from the declared functional purpose and includes resource access (filesystem, env vars, network endpoint) not reflected in the description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose is centered on payment processing and dispute filing for the x402r protocol, but the supplied code chunk exposes functionality for pinning JSON to IPFS. That is a different capability and accesses a different type of external service/resource than the description suggests. While this could theoretically support a broader application, nothing in the declared purpose mentions IPFS, Pinata, or content-addressed storage, so this code represents an undeclared and materially different behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose centers on payment processing and dispute filing for the x402r protocol, but the supplied code chunk implements IPFS evidence/data pinning. This is a materially different capability and resource usage: it interacts with Pinata/IPFS rather than merchant payment or dispute endpoints. While IPFS pinning could theoretically support a dispute workflow as a supporting detail, this isolated code chunk does not show payment or dispute operations and instead performs an undeclared external storage/network function, so the description does not accurately represent the code's behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared description says the skill pays merchants and files payment disputes on x402r. The supplied code chunk does not implement payment submission or dispute filing. Instead, it exposes setup utilities and type definitions for initializing CLI context, including viem clients, account handling, and config/arbiter-derived network metadata. These are supporting capabilities, but as the actual code shown, its primary behavior is environment/client initialization rather than merchant payment or dispute operations. Therefore the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The supplied code does not itself pay merchants or file payment disputes. Its actual role is infrastructure/setup: reading configuration, deriving an account from a private key, resolving chain/network addresses, and creating public and wallet clients. While this may support a payment/dispute tool, the chunk’s primary behavior is environment initialization rather than the declared end-user payment/dispute functionality. That is a material description-behavior mismatch for this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is focused on payment and dispute actions in the x402r protocol, but the supplied code does not perform payments, merchant interactions, dispute filing, or protocol operations. Instead, it implements a configuration command for setting and printing CLI config data, including sensitive settings like a private key and JWT. This is a materially different primary purpose from the description, so it is a clear mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose focuses on two capabilities: paying merchants and filing payment disputes. The actual code does neither. It implements a 'list' command that fetches and displays existing disputes from an arbiter server. While dispute-related functionality is adjacent to the declared domain, listing disputes is a distinct capability from filing disputes, and no payment action is present. Therefore the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code substantially matches the payment portion of the description: it requests a resource, handles a 402 Payment Required response, signs an escrow payment, retries with payment headers, extracts payment info, and saves state for later dispute. However, there are two notable mismatches. First, this chunk does not file disputes; it only stores state and prints guidance for a later dispute command, so the declared combined purpose is broader than this code’s actual behavior. Second, the code performs an undeclared external network action by POSTing payment info to a configured arbiter URL for dashboard caching, which is not implied by the description. Local state saving is related to dispute support and may be considered supportive, but the external caching behavior is an extra capability beyond the declared purpose. Therefore this code chunk is a mismatch overall.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description focuses on two capabilities: paying merchants and filing payment disputes. The actual code chunk does neither. Instead, it initializes a read-only client, retrieves the evidence count and all evidence for a dispute, and prints that evidence. This is a materially different primary capability within the dispute domain: viewing dispute evidence rather than paying or filing disputes. Although related to disputes, this functionality is not accurately represented by the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared purpose focuses on two primary actions: paying merchants and filing payment disputes. This code does neither. Its actual function is read-only status retrieval for an existing dispute/refund request, using either an arbiter server endpoint or on-chain SDK calls. That is a materially different primary purpose from initiating payments or filing disputes, so the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description says the skill's purpose is to pay merchants and file payment disputes. This code does neither: it does not initiate payments, submit disputes, or interact with merchant payment flows. Instead, it performs dispute verification by replaying an arbiter evaluation through an external court UI service and displaying verification results. That is a materially different primary purpose and includes an undeclared external network interaction for verification.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose focuses on payment execution and dispute filing, but this code chunk does not implement either of those user-facing financial actions. Instead, it provides configuration loading, persistence, environment-variable resolution, remote contract metadata discovery, and config display. These are supporting CLI setup capabilities, but in this isolated chunk the actual behavior is materially different from the declared purpose and includes undeclared access to local files, env vars, and a remote API endpoint. Therefore this chunk does not accurately match the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The supplied code does not implement merchant payments or payment dispute filing logic. Its actual purpose is IPFS evidence/data storage by calling Pinata's pinning service, which is a distinct capability and external resource not reflected in the declared description. While IPFS pinning could potentially support a broader dispute workflow, this code chunk itself is specifically for storage/pinning and not for executing x402r payments or disputes, so the description does not accurately represent this behavior.

Credential Access

High
Category
Privilege Escalation
Content
// Load .env from cli/ directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenvConfig({ path: join(__dirname, "..", ".env") });
const program = new Command();
program
    .name("x402r")
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
e2e-test.ts:83

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
dist/src/config.js:68

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/config.ts:88