T09 · Insecure Skill Coding Practices
- Location
src/cli/commands/serve-mcp.ts:76- Finding
Agent-Controlled Absolute URL Can Exfiltrate Stored API Credentials
- Content
View full analysis
Vulnerability Details
File Location:
src/cli/commands/serve-mcp.ts:76-115; policy handling occurs atsrc/core/mcp-server.ts:219-263
Vulnerability Type: Origin validation failure and credential disclosure
Risk Level: HighVulnerable Code
ts // Build target URL (properly join base + path) let baseUrl = serviceConfig.baseUrl; if (!baseUrl.endsWith('/')) baseUrl += '/'; let reqPath = request.path; if (reqPath.startsWith('/')) reqPath = reqPath.slice(1); const targetUrl = new URL(reqPath, baseUrl); // Build headers const headers: Record<string, string> = { ...request.headers }; // Inject auth if (serviceConfig.auth.type === 'bearer' && serviceConfig.auth.key) { headers['Authorization'] = `Bearer ${serviceConfig.auth.key}`; } else if (serviceConfig.auth.type === 'headers' && serviceConfig.auth.headers) { Object.assign(headers, serviceConfig.auth.headers); } else if (serviceConfig.auth.type === 'hmac' && serviceConfig.auth.apiKey && serviceConfig.auth.apiSecret) { // HMAC signature (MEXC-style) const timestamp = Date.now().toString(); targetUrl.searchParams.set('timestamp', timestamp); // Create signature from query string const queryString = targetUrl.searchParams.toString(); const signature = createHmac('sha256', serviceConfig.auth.apiSecret) .update(queryString) .digest('hex'); targetUrl.searchParams.set('signature', signature); headers['X-MEXC-APIKEY'] = serviceConfig.auth.apiKey; } // Set Content-Type for requests with body if (request.body && !headers['Content-Type'] && !headers['content-type']) { headers['Content-Type'] = 'application/json'; } // Make API request const response = await makeAPIRequest(targetUrl, { ...request, headers });Technical Analysis
The MCP caller controls
request.path. The implementation removes one leading slash and passes ...[truncated 1937 chars]- Remediation
View remediation
Remediation Suggestions
- Require request paths to be relative API paths beginning with exactly one
/. - Reject absolute URLs, protocol-relative values such as
//attacker.example, embedded credentials, backslashes, and control characters. - Resolve the path before adding credentials, then compare the resolved URL against the configured base URL:
- Protocol must match.
- Hostname must match.
- Effective port must match.
- Username and password must be empty.
- Inject credentials only after successful origin validation.
- Prefer constructing the outbound URL by assigning a validated pathname and query to a copy of the configured base URL instead of resolving arbitrary input.
- Keep redirects disabled, or independently validate every redirect destination before forwarding credentials.
- Add regression tests for absolute URLs, protocol-relative URLs, HTTP downgrade attempts, encoded separators, backslashes, embedded credentials, and cross-origin redirects.
- Consider making policy matching operate on a canonicalized pathname and query only.
- Require request paths to be relative API paths beginning with exactly one
