T09 · Insecure Skill Coding Practices
Error
- Location
- index.js:29
- Finding
- Emby API Token Disclosed to Unrelated TMDB Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `index.js:29-45`; additional affected callers appear at `index.js:49-56`, `index.js:87-101`, and `index.js:986-1027` **Vulnerability Type**: Cross-origin credential disclosure caused by a shared authenticated HTTP helper **Risk Level**: High ### Vulnerable Code ```js async function fetchJson(url, options = {}) { const headers = { 'Content-Type': 'application/json', 'X-Emby-Token': EMBY_API_KEY, 'X-Emby-Authorization': 'MediaBrowser Client="MovieButler",Device="PC",DeviceId="1",Version="1.0"', ...options.headers }; const res = await fetch(url, { headers, ...options }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } // ==================== TMDB 功能 ==================== async function searchMovie(query) { const url = `${TMDB_BASE_URL}/search/movie?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(query)}&language=zh-CN`; const data = await fetchJson(url); return data.results || []; } ``` Other TMDB requests use the same helper: ```js async function getMovieDetails(movieId) { const url = `${TMDB_BASE_URL}/movie/${movieId}?api_key=${TMDB_API_KEY}&language=zh-CN`; return await fetchJson(url); } async function getMovieCredits(movieId) { const url = `${TMDB_BASE_URL}/movie/${movieId}/credits?api_key=${TMDB_API_KEY}&language=zh-CN`; return await fetchJson(url); } ``` ### Technical Analysis `fetchJson()` unconditionally adds `X-Emby-Token` and `X-Emby-Authorization` to every request. The helper is used for both the private Emby server and the unrelated public TMDB service. Consequently, an ordinary TMDB movie search sends the configured Emby API token to `api.themoviedb.org`. TMDB does not require this token, so this behavior exceeds the minimum privileges needed for movie metadata retrieval and violates credential isolation between service origins. The risk applies when `EMBY_API_KEY` or the higher-pr ...[truncated 1461 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create separate request functions or clients for TMDB, OMDb, and Emby. 2. Attach `X-Emby-Token` only in the Emby-specific client. 3. Parse and validate the destination URL before adding credentials, requiring its origin to exactly match the configured Emby origin. 4. Do not rely on substring or suffix checks for host validation. 5. Ensure caller-provided headers cannot accidentally propagate credentials across origins. 6. Rotate the current Emby token because it may already have been disclosed through normal TMDB requests. 7. Add automated tests asserting that TMDB and OMDb requests never contain Emby headers. A safer design is: ```js async function fetchPublicJson(url, options = {}) { const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } async function fetchEmbyJson(resource, options = {}) { const base = new URL(EMBY_URL); const url = new URL(resource, base); if (url.origin !== base.origin) { throw new Error('Refusing to send Emby credentials to another origin'); } const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', 'X-Emby-Token': EMBY_API_KEY, 'X-Emby-Authorization': 'MediaBrowser Client="MovieButler",Device="PC",DeviceId="1",Version="1.0"', ...options.headers } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } ``` ]]>
