T09 · Insecure Skill Coding Practices
Warning
- Location
- mcp-server.mjs:14
- Finding
- Unrestricted API Base URL Can Redirect Bearer Credentials<![CDATA[ ## Vulnerability Details **File Location**: `mcp-server.mjs:14-22, 34-36, 54-69, 247-260, 283-295, 352-361, 403-415, 442-454` **Vulnerability Type**: Unrestricted credential destination and insecure transport configuration **Risk Level**: Medium ### Vulnerable Code ```js function env(name, fallback = null) { const v = process.env[name]; return typeof v === "string" && v.trim() ? v.trim() : fallback; } const baseUrl = (env("NOBOT_BASE_URL", "https://nobot.life") || "https://nobot.life").replace( /\/+$/, "", ); async function apiFetch(path, init) { const url = `${baseUrl}${path.startsWith("/") ? "" : "/"}${path}`; const res = await fetch(url, init); let parsed = null; try { parsed = await res.json(); } catch { // ignore } if (!res.ok) { const msg = (parsed && (parsed.message || parsed.error || parsed.code)) || `HTTP ${res.status}`; const err = new Error(String(msg)); err.status = res.status; err.payload = parsed; throw err; } return parsed; } function requireBotKey(args) { const fromArgs = args && typeof args.apiKey === "string" && args.apiKey.trim() ? args.apiKey.trim() : null; const key = fromArgs || env("NOBOT_API_KEY"); if (!key) { const err = new Error( "Missing bot API key. Provide { apiKey: \"nbk_...\" } or set NOBOT_API_KEY. Self-register first via register_bot.", ); err.status = 401; throw err; } return key.trim(); } ``` A representative authenticated request is implemented as follows: ```js handler: async (args) => { const apiKey = requireBotKey(args); const payload = { question: args?.question, description: args?.description, options: args?.options, ...(args?.closesAt ? { closesAt: args.closesAt } : {}), }; return apiFetch("/api/polls", { method: "POST", headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json", }, body: JSON.stringify(payload), ...[truncated 2913 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse the configured endpoint with `new URL()` before accepting it. 2. Require the `https:` protocol for every request carrying credentials. 3. In production, permit only the exact declared origin, `https://nobot.life`. 4. If custom development endpoints are required, place them behind an explicit development-only option and prevent production credentials from being sent to those endpoints. 5. Reject URLs containing embedded usernames or passwords, fragments, or unexpected ports. 6. Consider constructing authenticated URLs from a fixed origin rather than a general environment variable. 7. Prefer obtaining the key exclusively from a protected environment or secret store instead of accepting it in MCP tool arguments. 8. Ensure authorization headers and tool arguments containing credentials are redacted from telemetry, errors, traces, and diagnostics. Example hardening: ```js function getValidatedBaseUrl() { const configured = env("NOBOT_BASE_URL", "https://nobot.life"); const url = new URL(configured); if (url.protocol !== "https:") { throw new Error("NOBOT_BASE_URL must use HTTPS"); } if (url.origin !== "https://nobot.life") { throw new Error("NOBOT_BASE_URL must use the approved nobot.life origin"); } if (url.username || url.password || url.hash) { throw new Error("NOBOT_BASE_URL contains unsupported URL components"); } return url.origin; } const baseUrl = getValidatedBaseUrl(); ``` If non-production endpoints must be supported, use a separate explicit flag and separate test credentials rather than weakening validation for production keys. ]]>
