Back to skill

Security audit

x402janus-acp

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it can spend marketplace funds and send an ACP API key to a configurable endpoint without strong safeguards.

Review before installing. Use this only with an ACP key scoped for buying scans, set JANUS_OFFERING_NAME explicitly, verify price and provider identity before running scans, avoid custom ACP_BASE_URL unless you fully trust it, and fix or isolate the missing dependencies before running the scan command.

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/list-offerings.ts:55
Finding
ACP API Credential Can Be Redirected to an Arbitrary Network Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/list-offerings.ts:55-64, 186-193`; `scripts/scan-wallet-acp.ts:131-137, 390-403, 564-565` **Vulnerability Type**: Unrestricted credential forwarding to a configurable endpoint **Risk Level**: High ### Vulnerable Code From `scripts/list-offerings.ts`: ```ts async function fetchAgents( apiKey: string, baseUrl: string, query: string ): Promise<AgentProfile[]> { const resp = await fetch(`${baseUrl}/acp/agents?query=${encodeURIComponent(query)}`, { headers: { "x-api-key": apiKey, "Content-Type": "application/json", }, }); ``` ```ts const apiKey = process.env.ACP_API_KEY; if (!apiKey) { console.error("Error: ACP_API_KEY environment variable is not set."); console.error("Get your key from the Virtuals ACP dashboard."); process.exit(1); } const baseUrl = (process.env.ACP_BASE_URL ?? "https://claw-api.virtuals.io").replace(/\/$/, ""); ``` From `scripts/scan-wallet-acp.ts`: ```ts function buildClient(apiKey: string, baseUrl: string): AxiosInstance { return axios.create({ baseURL: baseUrl, headers: { "x-api-key": apiKey }, timeout: 15_000, }); } ``` ```ts const apiKey = options.apiKey ?? process.env.ACP_API_KEY; if (!apiKey) { throw new Error( "ACP_API_KEY environment variable is not set. " + "Get your key at https://claw-api.virtuals.io" ); } const baseUrl = options.baseUrl ?? process.env.ACP_BASE_URL ?? DEFAULT_BASE_URL; const agentWallet = options.agentWallet ?? process.env.ACP_AGENT_WALLET; const offeringName = options.offeringName ?? process.env.JANUS_OFFERING_NAME ?? null; const client = buildClient(apiKey, baseUrl); ``` ### Technical Analysis Both scripts attach the sensitive `ACP_API_KEY` to requests sent through a caller-controlled base URL. Neither implementation parses the URL nor enforces HTTPS, an approved hostname, an allowed port, or a trusted endpoint list. Support for a custom ACP deployment may be legitimate, but a ...[truncated 2213 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the base URL with the standard `URL` class before constructing the client. 2. Require `https:` for all credential-bearing production requests. 3. Allow `claw-api.virtuals.io` by default and reject other hostnames unless the user explicitly enables a custom-endpoint mode. 4. Maintain a narrowly scoped allowlist if multiple official ACP hosts are supported. 5. Reject URLs containing embedded credentials, unexpected ports, fragments, or non-HTTP protocols. 6. Require a separate API key for custom endpoints rather than automatically forwarding the production `ACP_API_KEY`. 7. Display the destination hostname and request explicit confirmation before sending a credential to a nonstandard endpoint. 8. Use server-side scoped credentials with only the permissions needed to list offerings, create jobs, and read the caller's own jobs. 9. Document that wallet addresses are transmitted to the ACP service. Example validation: ```ts function validateBaseUrl(raw: string): string { const url = new URL(raw); if (url.protocol !== "https:") { throw new Error("ACP_BASE_URL must use HTTPS."); } if (url.hostname !== "claw-api.virtuals.io") { throw new Error("Untrusted ACP API hostname."); } if (url.username || url.password || url.hash) { throw new Error("ACP_BASE_URL contains unsupported URL components."); } return url.origin; } ``` ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/scan-wallet-acp.ts:28
Finding
Runtime Dependencies Are Undeclared and Absent from the Lockfile<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scan-wallet-acp.ts:28-31`; `package.json:9-14` **Vulnerability Type**: Undeclared runtime dependencies and ambient package resolution **Risk Level**: Medium ### Vulnerable Code From `scripts/scan-wallet-acp.ts`: ```ts import axios, { AxiosInstance } from "axios"; import dotenv from "dotenv"; dotenv.config(); ``` The complete dependency declaration in `package.json` is: ```json "devDependencies": { "@types/node": "^22.0.0", "tsx": "^4.19.2", "typescript": "^5.7.2" } ``` Neither `axios` nor `dotenv` is declared under `dependencies` or `devDependencies`, and neither package is present in `package-lock.json`. ### Technical Analysis The scan script requires `axios` and `dotenv` at module initialization, but a clean installation based on the supplied package manifest does not install either package. The documented `npm install` workflow therefore does not produce a self-contained runtime for `scan-wallet-acp.ts`. In a clean environment, the expected result is a module-resolution failure. In a shared or nested Node.js workspace, however, Node may resolve packages from a parent `node_modules` directory. That means the effective implementation can depend on packages that are not declared, reviewed, or integrity-pinned by this project. Because both imports execute code during module loading, a malicious ambient package with one of these names can execute with the privileges of the user running the Skill. The risk requires an attacker or compromised build system to control a package location searched by Node, but the missing declarations make that condition materially easier to overlook. The existing lockfile otherwise uses npm registry URLs and integrity hashes; no evidence of a malicious declared package was found. ### Attack Path 1. The Skill is installed inside a monorepo, shared agent workspace, or directory nested below another `node_modules` tree. 2. An attacker, compromised workspac ...[truncated 1198 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add `axios` and `dotenv` as runtime dependencies under `dependencies`. 2. Regenerate and commit `package-lock.json` so exact resolved packages and integrity hashes are recorded. 3. Use `npm ci` in deployment and automation to enforce the committed lockfile. 4. Run the Skill in an isolated project directory rather than relying on parent or global modules. 5. Consider removing these dependencies entirely: - Use Node's built-in `fetch` consistently instead of `axios`. - Require environment variables to be supplied by the host rather than loading `.env`, or use Node's supported environment-file mechanism where appropriate. 6. Add a clean-install CI test that installs from the lockfile and runs TypeScript compilation plus CLI smoke tests. 7. Run dependency provenance and vulnerability checks after regenerating the lockfile. Example manifest correction: ```json "dependencies": { "axios": "<reviewed-version>", "dotenv": "<reviewed-version>" }, "devDependencies": { "@types/node": "^22.0.0", "tsx": "^4.19.2", "typescript": "^5.7.2" } ``` Pin reviewed versions according to the project's dependency-update policy. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scan-wallet-acp.ts:215
Finding
Paid Offering Is Automatically Selected from Mutable Marketplace Ordering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scan-wallet-acp.ts:215-236` **Vulnerability Type**: Unconstrained automatic selection of a paid remote offering **Risk Level**: Medium ### Vulnerable Code ```ts let offering: JobOffering | undefined; if (offeringName) { offering = offerings.find( (o) => o.name.toLowerCase() === offeringName.toLowerCase() ); if (!offering) { const names = offerings.map((o) => `"${o.name}"`).join(", "); throw new Error( `Offering "${offeringName}" not found. Available: ${names}` ); } } else { // Default: use the first available offering offering = offerings[0]; } return { agent: janusAgent, offering }; ``` The selected offering is then used to create a job: ```ts const jobId = await createScanJob( client, agent.walletAddress, offering.name, walletAddress, agentWallet ); ``` ### Technical Analysis When neither `options.offeringName` nor `JANUS_OFFERING_NAME` is supplied, the code purchases `offerings[0]`. Marketplace ordering is remote and mutable. The code does not enforce an expected offering name, supported currency, maximum price, or explicit confirmation before creating the job. The implementation requires an exact case-insensitive agent name, which reduces simple search-result spoofing, but a name is not necessarily a stable cryptographic identity. The code does not pin a documented provider ID or wallet address. It also trusts the remote `price` and `priceType` for display without using either value as a policy constraint. Consequently, a marketplace account change, API compromise, custom malicious ACP endpoint, or benign reordering can cause the script to purchase a different or more expensive service than intended. ### Attack Path 1. The user invokes the scan command without `--offering` and has not set `JANUS_OFFERING_NAME`. 2. The ACP endpoint returns an exact-name `x402janus` profile whose first offering is expensive, unintended, or attacker-controlled. ...[truncated 1137 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require an explicit offering name instead of silently selecting the first result. 2. If a default is necessary, use a hardcoded documented offering identifier rather than remote array ordering. 3. Pin the expected provider using a stable marketplace agent ID and expected wallet address, not only the display name. 4. Validate the offering currency against an allowlist. 5. Add a configurable maximum price and reject any offering above it. 6. Require interactive confirmation before creating a paid job unless the caller explicitly enables noninteractive mode. 7. In noninteractive automation, require the caller to provide the exact provider identity, offering, currency, and spending limit. 8. Log the selected provider wallet, offering, price, and currency before job creation. 9. Treat price fields as untrusted data and validate that they are finite, nonnegative numbers. Example policy checks: ```ts if (!offeringName) { throw new Error("An explicit offering name is required."); } if (agent.id !== EXPECTED_AGENT_ID || agent.walletAddress.toLowerCase() !== EXPECTED_PROVIDER_WALLET) { throw new Error("Unexpected x402janus provider identity."); } if (offering.priceType !== EXPECTED_CURRENCY) { throw new Error(`Unsupported offering currency: ${offering.priceType}`); } if (!Number.isFinite(offering.price) || offering.price > maxPrice) { throw new Error(`Offering price exceeds the configured limit.`); } ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (39)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description promises an ACP buyer workflow: purchase a wallet security scan from x402janus, create the job, pay for it, wait for completion, and return results. The supplied code does none of that. It only reads marketplace data by calling the ACP agents search endpoint, selecting the x402janus profile, and printing available offerings, pricing, SLA, descriptions, and requirements. This is a materially different primary purpose: listing offerings rather than buying or running scans. While both involve the ACP marketplace and x402janus, the implemented behavior lacks the core declared capabilities of job creation, payment, execution tracking, and result retrieval.

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/list-offerings.ts --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/scan-wallet-acp.ts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/scan-wallet-acp.ts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/scan-wallet-acp.ts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/scan-wallet-acp.ts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
ACP_API_KEY=$KEY npx tsx scripts/scan-wallet-acp.ts 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares required binaries and environment variables and clearly instructs users to run networked scripts, but it does not declare an explicit tool scope such as permissions or allowed-tools. That weakens sandboxing and review because an agent may grant broader env and network access than users expect, increasing the chance of credential exposure or unintended outbound calls.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill description mentions paying with $VIRTUAL tokens, but it does not prominently warn that invoking the purchase workflow may create paid marketplace jobs and spend user funds. In an agent setting, insufficient spend disclosure can cause unintended financial actions, especially if another system auto-selects skills based on terse descriptions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding
Using 'npx tsx' without pinning a version allows execution of whatever package version resolves at runtime, which can change over time or be replaced by a malicious upstream release. In a skill that handles API keys and makes marketplace/network calls, this creates a supply-chain execution path with access to sensitive credentials and the ability to trigger paid actions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/list-offerings.ts:186

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/scan-wallet-acp.ts:390