Back to skill

Security audit

x402 Private Search

Security checks for vulnerabilities and agentic risk

Overview

The skill appears purpose-built for x402 paid API requests, but it needs review because it can automatically authorize wallet payments and handles private keys in risky ways.

Install only if you are comfortable giving this skill spending authority for a dedicated low-balance test wallet. Prefer --key-file over X402_PRIVATE_KEY or --key, do not reuse a valuable wallet, inspect payment terms and endpoints manually, and be aware that npm dependencies are fetched at install time.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/x402-fetch.mjs:41
Finding
Automatic payment signing lacks destination and spending restrictions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/x402-fetch.mjs:41-91` **Vulnerability Type**: Unrestricted automatic payment authorization **Risk Level**: High ### Vulnerable Code ```js const url = args[0]; ``` ```js // Create signer and x402 client const signer = privateKeyToAccount(privateKey); if (!quiet) console.error(`Wallet: ${signer.address}`); const client = new x402Client(); registerExactEvmScheme(client, { signer }); const fetchWithPayment = wrapFetchWithPayment(fetch, client); // Make the request const fetchOpts = { method, headers }; if (body) fetchOpts.body = body; if (!quiet) console.error(`${method} ${url}`); try { const response = await fetchWithPayment(url, fetchOpts); ``` ### Technical Analysis The script accepts an arbitrary user-supplied URL and passes it to a payment-enabled fetch wrapper backed by a wallet signer. It does not locally enforce: - HTTPS-only transport - A trusted destination or payee allowlist - An expected blockchain network - An expected payment asset - A maximum payment amount - A cumulative spending limit - Interactive confirmation before signing Signing payment authorizations is necessary for the declared x402 functionality, but automatically granting this capability to any supplied endpoint exceeds safe minimum privilege. A malicious, compromised, or incorrectly configured server can return x402 payment requirements that the wrapper may process without explicit user review. The code does not transmit the raw private key directly. It does, however, provide the SDK with signing authority and sends the resulting signed payment authorization as part of the x402 exchange. ### Attack Path 1. An attacker persuades an operator or agent to invoke the script with an attacker-controlled URL, or compromises a previously trusted endpoint. 2. The endpoint responds with crafted x402 payment requirements. 3. `wrapFetchWithPayment` processes the response using the registered EVM signer. 4. Because the scrip ...[truncated 843 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject all non-HTTPS URLs before making a request. 2. Maintain an explicit allowlist of trusted hostnames or require a deliberate override for unknown hosts. 3. Decode and validate every payment requirement before signing, including: - Chain identifier - Payment asset and contract address - Recipient/payee address - Per-request amount - Expiration and replay-related fields 4. Add a secure default maximum payment amount and a cumulative session spending cap. 5. Display the normalized payment terms and require interactive confirmation unless the endpoint and limits were explicitly preapproved. 6. Fail closed when payment requirements contain unsupported or ambiguous fields. 7. Use a dedicated low-balance wallet for automated requests. 8. Document that endpoint health checks do not establish the identity or trustworthiness of the payment recipient. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wallet-gen.mjs:15
Finding
Private keys can be exposed through output, command arguments, and inherited environment variables<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wallet-gen.mjs:15-29`, `scripts/x402-fetch.mjs:25-31`, `scripts/x402-fetch.mjs:42-43` **Vulnerability Type**: Insecure secret handling **Risk Level**: Medium ### Vulnerable Code ```js console.log(`Address: ${account.address}`); console.log(`Private Key: ${key}`); console.log(`Network: Base Sepolia (eip155:84532)`); console.log(); console.log("Next steps:"); console.log("1. Get Base Sepolia ETH: https://www.alchemy.com/faucets/base-sepolia"); console.log("2. Get Base Sepolia USDC: https://faucet.circle.com/ (select Base Sepolia + USDC)"); console.log(`3. Send ETH and USDC to: ${account.address}`); if (outFile) { const { writeFileSync } = await import("fs"); writeFileSync(outFile, key + "\n", { mode: 0o600 }); console.log(`\nPrivate key saved to: ${outFile}`); } ``` ```js Options: --key <hex> EVM private key (hex, with or without 0x prefix) --key-file <path> File containing the private key --method <GET|POST> HTTP method (default: GET) --body <json> Request body (for POST) --header <k:v> Extra header (repeatable) --quiet Suppress stderr info messages Environment: X402_PRIVATE_KEY EVM private key (fallback if --key not provided) X402_KEY_FILE Key file path (fallback if --key-file not provided) ``` ```js const url = args[0]; let privateKey = process.env.X402_PRIVATE_KEY || ""; let keyFile = process.env.X402_KEY_FILE || ""; ``` The documentation additionally recommends: ```bash export X402_PRIVATE_KEY=$(cat ~/.x402-client/wallet.key) ``` ### Technical Analysis The wallet generator always writes the private key to standard output, including when the caller requests storage in a mode-`0600` key file. In agent, CI, terminal-recording, or managed execution environments, stdout may be captured and retained. The fetch utility also supports passing a private key directly in a command-line argument. Depending on the operating system and e ...[truncated 1562 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not print private keys by default. 2. When `--out` is used, print only the public address and the destination path. 3. If displaying a private key is retained for exceptional use, require an explicit option such as `--show-private-key` and emit a strong warning. 4. Remove the `--key` command-line option to prevent secrets from entering process listings and shell history. 5. Prefer a mode-`0600` key file, OS credential store, encrypted keystore, or hardware-backed signer. 6. Avoid recommending a globally exported private-key environment variable. 7. If environment-based input must remain supported, use it only for a single process and sanitize the environment before launching child processes. 8. Ensure CI and agent platforms redact wallet keys and do not retain sensitive stdout. 9. Rotate any wallet whose key may already have appeared in logs, histories, or transcripts. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/setup.sh:23
Finding
Wallet-sensitive dependencies are installed from mutable version ranges without a lockfile<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:23-35` **Vulnerability Type**: Non-reproducible and insufficiently constrained dependency installation **Risk Level**: Medium ### Vulnerable Code ```sh cat > package.json << 'EOF' { "name": "x402-client", "version": "1.0.0", "private": true, "type": "module", "dependencies": { "@x402/fetch": "^2.3", "@x402/evm": "^2.3", "viem": "^2.0" } } EOF npm install --quiet 2>&1 | tail -3 ``` ### Technical Analysis The setup script creates a package manifest containing caret version ranges and runs `npm install` without a committed lockfile. Consequently, installations performed at different times can resolve different package and transitive-dependency versions. The installed packages operate in a wallet-signing context. They are loaded into the same Node.js process that receives the private key and constructs payment authorizations. A compromised upstream release or transitive dependency could therefore access wallet material or modify payment behavior. Standard npm installation may also execute dependency lifecycle scripts unless explicitly disabled. There is no evidence in the reviewed project that the named packages are currently malicious. The confirmed weakness is that the installation process does not provide reproducible, reviewed dependency resolution or constrain install-time script execution. ### Attack Path 1. A package maintainer account, package release, or transitive dependency is compromised, or an unsafe version is published within one of the accepted ranges. 2. A user runs `scripts/setup.sh` after that publication. 3. `npm install` resolves the mutable range to the affected version because no audited lockfile fixes the dependency graph. 4. Malicious lifecycle code may run during installation, or malicious runtime code may load when `x402-fetch.mjs` executes. 5. Runtime code can access the signer context, alter payment parameters, or attempt to disclose ...[truncated 606 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin direct dependencies to exact, reviewed versions rather than caret ranges. 2. Generate, review, and distribute a `package-lock.json`. 3. Use `npm ci` so installation fails when the manifest and lockfile differ. 4. Review resolved transitive dependencies and lockfile integrity hashes. 5. Use `npm ci --ignore-scripts` if the selected packages do not require lifecycle scripts. 6. If lifecycle scripts are required, audit them and document why they are necessary. 7. Run dependency vulnerability and provenance checks in CI. 8. Perform installation and wallet operations under a dedicated, non-privileged account. 9. Keep only limited testnet funds in wallets exposed to this dependency stack. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill performs sensitive actions involving network access and environment-based secret handling, but it does not declare any explicit tool scope or permissions. This creates a governance gap: an agent may invoke capabilities broader than the user expects, making secret exposure or unintended outbound requests more likely.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill instructs users to place a blockchain private key directly into an environment variable, which increases the chance of leakage through shell history, process inspection, logs, crash reports, or downstream tools that read inherited environment variables. Because this key authorizes spending from the wallet, compromise can lead to unauthorized payments and loss of funds.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This shell script creates directories and files, runs `npm install`, and copies scripts into the target directory. While the script's purpose is setup and installation, it does not clearly disclose beforehand that it will write `package.json`, install dependencies from the network, and copy executable scripts into a user directory.

Static analysis

No suspicious patterns detected.