T09 · Insecure Skill Coding Practices
- Location
scripts/fetch_feeds.py:21- Finding
Remote-Controlled Feed and Article URLs Permit Unrestricted Network Requests
- Content
View full analysis
list[dict]: """Fetch OPML from URL and return list of {text, xmlUrl, htmlUrl}.""" async with session.get(_OPML_URL, timeout=aiohttp.ClientTimeout(total=30)) as resp: resp.raise_for_status() body = await resp.text() root = ET.fromstring(body) feeds = [] for outline in root.iter("outline"): url = outline.get("xmlUrl") if url: feeds.append({ "text": outline.get("text", ""), "xmlUrl": url, "htmlUrl": outline.get("htmlUrl", ""), }) return feeds async def fetch_feed(session: aiohttp.ClientSession, feed: dict, sem: asyncio.Semaphore, cutoff: datetime) -> list[dict]: """Fetch and parse a single RSS/Atom feed, returning articles newer than cutoff.""" url = feed["xmlUrl"] try: async with sem: async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as resp: body = await resp.text() ``` The subsequent agent workflow also directs the agent to retrieve feed-supplied article links: ```markdown ### Step 3: Get the full article text For every selected article, use WebFetch to read the full text. The editorial observation must be based on the full text and not only on the RSS summary. ``` ### Technical Analysis The feed-fetching script trusts a mutable OPML document hosted on an external domain. Every `xmlUrl` attribute in that document is passed directly to `aiohttp.ClientSession.get()` without validating: - The URL scheme - The destination hostname - The resolved IP address - L ...[truncated 2739 chars]- Remediation
View remediation
