T09 · Insecure Skill Coding Practices
Warning
- Location
- examples/fetch-wrapper.md:27
- Finding
- Bearer Token Forwarding to a Caller-Controlled Network Destination<![CDATA[ ## Vulnerability Details **File Location**: `examples/fetch-wrapper.md`, lines 27 and 50–72 **Vulnerability Type**: Credential disclosure through an insufficiently constrained authenticated HTTP client **Risk Level**: Medium ### Vulnerable Code ```typescript const baseURL = options.baseURL || import.meta.env.VITE_API_BASE_URL ``` ```typescript // Add authentication token const token = localStorage.getItem('token') if (token) { headers.Authorization = `Bearer ${token}` } // Create an AbortController for timeout handling const controller = new AbortController() const timeoutId = setTimeout(() => { controller.abort() }, timeout) try { // Send request const response = await fetch(fullURL, { ...options, headers, signal: controller.signal, // HTTPS-specific configuration credentials: import.meta.env.PROD ? 'same-origin' : 'include', redirect: 'follow' }) ``` ### Technical Analysis The wrapper automatically attaches the browser's ambient bearer token to every request. At the same time, `options.baseURL` allows a caller to override the request destination. There is no same-origin check, HTTPS enforcement, or destination allowlist before the `Authorization` header is added. Consequently, code that can invoke this wrapper may cause the bearer token to be transmitted to an unintended origin. The `credentials` setting does not protect the bearer token because it only controls browser-managed credentials such as cookies. The explicitly assigned `Authorization` header remains attached to the request. Following redirects also increases the need for strict destination validation, although browser redirect behavior may limit forwarding of authorization headers in some cross-origin cases. This network behavior is related to the Skill's declared authenticated API-wrapper functionality, but unconstrained destination selection exceeds the minimum privilege required. An authenticated client should only release credentials to expl ...[truncated 1070 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Remove per-request `baseURL` overrides from authenticated clients. - Configure a fixed first-party API origin when constructing the client. - Validate the final URL with `new URL()` before adding credentials. - Require HTTPS outside explicitly isolated local-development environments. - Maintain an exact allowlist of trusted origins and reject all other destinations. - Add the `Authorization` header only after destination validation. - Use separate authenticated and unauthenticated request functions. - Avoid accepting absolute URLs in methods intended for first-party API paths. - Set `redirect: 'error'` or manually validate every redirect destination for sensitive requests. - Configure server-side token audience restrictions, short expirations, rotation, and revocation. - Add tests proving that tokens are never attached to cross-origin or non-HTTPS requests. Example hardening: ```typescript const API_ORIGIN = new URL(import.meta.env.VITE_API_BASE_URL) if (API_ORIGIN.protocol !== 'https:' && !import.meta.env.DEV) { throw new Error('The API origin must use HTTPS') } const target = new URL(url, API_ORIGIN) if (target.origin !== API_ORIGIN.origin) { throw new Error('Untrusted API destination') } const token = getAccessToken() if (token) { headers.Authorization = `Bearer ${token}` } const response = await fetch(target, { ...options, headers, signal: controller.signal, credentials: 'same-origin', redirect: 'error' }) ``` ]]>
