T09 · Insecure Skill Coding Practices
- Location
- index.js:23
- Finding
- API Credentials Can Be Transmitted to an Arbitrary or Insecurely Configured Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `index.js:23-40` **Vulnerability Type**: Unrestricted credential destination and missing transport validation **Risk Level**: Medium ### Vulnerable Code ```js const SAYBA_BASE_URL = process.env.SAYBA_BASE_URL || "https://ai.sayba.com"; const SAYBA_API_KEY = process.env.SAYBA_API_KEY || ""; const API_BASE = `${SAYBA_BASE_URL}/api/v1`; // ─── Helper ────────────────────────────────────────────────────── async function saybaApi(path, options = {}) { const url = path.startsWith("http") ? path : `${API_BASE}${path}`; const headers = { "Content-Type": "application/json" }; if (SAYBA_API_KEY) headers["x-api-key"] = SAYBA_API_KEY; if (options.token) headers["Authorization"] = `Bearer ${options.token}`; if (options.headers) Object.assign(headers, options.headers); const res = await fetch(url, { method: options.method || "GET", headers, body: options.body ? JSON.stringify(options.body) : undefined, }); ``` ### Technical Analysis The server obtains the API endpoint from the unrestricted `SAYBA_BASE_URL` environment variable. It then attaches `SAYBA_API_KEY` to authenticated requests without validating the destination's protocol or hostname. Although sending the API key to the Sayba service is necessary for the declared functionality, the implementation does not ensure that credentials are sent only to the intended service. A configuration value such as `http://attacker.example` would cause authenticated tool calls to transmit the key to that endpoint. An HTTP endpoint would additionally expose the key to interception or modification in transit. The helper also accepts absolute paths through `path.startsWith("http")`. No current tool passes a user-controlled absolute URL into this function, so that branch does not presently create a separately exploitable tool-input SSRF path. Nevertheless, it unnecessarily weakens the helper's destination guarantees. The API key authorizes high-impact ...[truncated 1686 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse the configured endpoint with the standard `URL` class and reject malformed values. 2. Require `https:` for all endpoints. Permit plaintext HTTP only through an explicitly named development-only override that is disabled by default. 3. Default-deny hosts other than `ai.sayba.com`. If custom instances are required, use an explicit allowlist or require a separate credential associated with the custom origin. 4. Remove support for absolute request paths from `saybaApi()` unless it is demonstrably required. 5. Prevent authorization headers from being sent when the final request origin differs from the validated API origin. 6. Disable automatic redirects or validate every redirect destination before following it. 7. Keep different credentials for production and custom instances to limit the effect of configuration mistakes. 8. Clearly warn users that changing `SAYBA_BASE_URL` changes the destination receiving their secret API key. 9. Add automated tests that verify rejection of HTTP URLs, embedded credentials, unexpected hosts, malformed URLs, and cross-origin redirects. A hardened configuration pattern would resemble: ```js const configuredBase = process.env.SAYBA_BASE_URL || "https://ai.sayba.com"; const baseUrl = new URL(configuredBase); if (baseUrl.protocol !== "https:") { throw new Error("SAYBA_BASE_URL must use HTTPS"); } const allowedHosts = new Set(["ai.sayba.com"]); if (!allowedHosts.has(baseUrl.hostname)) { throw new Error("SAYBA_BASE_URL host is not allowed"); } const API_BASE = new URL("/api/v1/", baseUrl); async function saybaApi(path, options = {}) { if (/^https?:\/\//i.test(path)) { throw new Error("Absolute API paths are not allowed"); } const url = new URL(path.replace(/^\//, ""), API_BASE); if (url.origin !== baseUrl.origin) { throw new Error("Cross-origin API request rejected"); } // Construct and send the authenticated request. } ``` ]]>
