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; } ``` ]]>
