T09 · Insecure Skill Coding Practices
- Location
src/rss/fetch.ts:30- Finding
Server-Side Request Forgery Through Unvalidated Feed and Article URLs
- Content
View full analysis
]*>/gi)].map((m) => m[0]); return tags .map((tag): OpmlFeed => ({ name: pickAttr(tag, ["text", "title"]) || "unknown", xmlUrl: pickAttr(tag, ["xmlUrl"]), htmlUrl: pickAttr(tag, ["htmlUrl"]), })) .filter((feed) => feed.xmlUrl.length > 0); } ``` `src/rss/fetch.ts:30-70`: ```ts export async function fetchFeed( feed: FeedSource, options?: { timeoutMs?: number; retries?: number; fetcher?: typeof fetch; seed?: number; }, ): Promise { const fetcher = options?.fetcher ?? fetch; const timeoutMs = options?.timeoutMs ?? 15_000; const retries = options?.retries ?? 2; const seed = options?.seed ?? 0; for (let attempt = 0; attempt <= retries; attempt += 1) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetcher(feed.xmlUrl, { signal: controller.signal, headers: buildFetchHeaders(seed + attempt), }); clearTimeout(timeout); if (!response.ok) { if ((response.status === 403 || response.status === 429) && attempt < retries) { await sleep(250 + attempt * 300); continue; } throw new Error(`HTTP ${response.status}`); } const xml = await response.text(); return parseFeedItems(xml, feed.name, feed.htmlUrl); } catch (error) { clearTimeout(timeout); if (attempt >= retries) { console.warn(`[rss] failed ${feed.name}: ${error instanceof Error ? error.message : String(error)}`); return []; } ...[truncated 4169 chars]- Remediation
View remediation
