T09 · Insecure Skill Coding Practices
- Location
scripts/mcp-client.js:440- Finding
API Credential Disclosure and Server-Side Request Forgery Through an Unvalidated Stream URL
- Content
View full analysis
{ const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(streamUrl, { method: "GET", headers: { "X-API-Key": MCP_API_KEY, }, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { const errorBody = await response.text(); throw new Error( `MCP stream HTTP ${response.status}: ${errorBody.slice(0, 200)}`, ); } const contentType = response.headers.get("content-type") || ""; const text = await response.text(); if (raw) { return { contentType, raw: text }; } if (contentType.includes("application/x-ndjson")) { return { contentType, rows: parseNdjson(text), }; } return { contentType, raw: text, }; } catch (error) { clearTimeout(timeoutId); if (error.name === "AbortError") { throw new Error(`MCP stream timeout (${timeout}ms)`); } throw error; } }); } ``` The affected method is reached through this flow: ```js const opened = await openQueryStream(sql, { format, previewRows, timeoutMs, timeout, metadata, }); const consumed = await consumeQueryStream(opened.streamUrl, { timeout, raw, }); ``` ### Technical Analysis `openQueryStream()` obtains `streamUrl` from the remote MCP service. `queryStream()` passes that value directly to `consumeQueryStream()`, which performs an outbound request and unconditionally attaches `MCP_API_KEY` as the `X-API-Ke ...[truncated 2590 chars]- Remediation
View remediation
