T09 · Insecure Skill Coding Practices
- Location
- grazer/podcast_grazer.py:122
- Finding
- Unrestricted Podcast Feed URL Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `grazer/podcast_grazer.py:122-139` **Vulnerability Type**: Server-Side Request Forgery through an unrestricted user-supplied URL **Risk Level**: High ### Vulnerable Code ```python def episodes( self, feed_url: str, limit: int = 10, ) -> List[Dict]: """Fetch recent episodes from a podcast RSS feed. Args: feed_url: The podcast's RSS feed URL limit: Maximum episodes to return Returns: List of episode dicts with title, description, audio_url, etc. """ resp = self.session.get(feed_url, timeout=self.timeout) resp.raise_for_status() eps = _parse_podcast_rss(resp.text) return eps[:limit] ``` The method is exposed through the main client: ```python def podcast_episodes(self, feed_url: str, limit: int = 10) -> List[Dict]: """Fetch recent episodes from a podcast RSS feed URL.""" return self._podcast.episodes(feed_url, limit=limit) ``` ### Technical Analysis `feed_url` is passed directly to `requests.Session.get()` without validating its scheme, destination hostname, resolved IP address, port, or redirect chain. The request library follows HTTP redirects by default. Consequently, an untrusted caller can direct the process to arbitrary HTTP services reachable from its execution environment. This includes loopback services, private network hosts, link-local addresses, container-management endpoints, and cloud instance metadata services. A timeout does not prevent SSRF. There is also no response-size limit, so a hostile endpoint may return an excessively large body before the code accesses `resp.text`. ### Attack Path 1. An attacker gains control over the `feed_url` argument, directly or through an agent workflow that treats externally supplied podcast URLs as trusted. 2. The attacker supplies a URL such as: - `http://127.0.0.1:8080/internal` - `http://169.254.169.254/latest/meta-data/` - A public HTTPS URL that redirects to a ...[truncated 912 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit only explicitly supported schemes, normally `https`. 2. Parse URLs with `urllib.parse.urlsplit()` and reject embedded credentials, malformed hosts, and unexpected ports. 3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 4. Disable automatic redirects or validate every redirect destination using the same policy. 5. Protect against DNS rebinding by ensuring that the validated address is the address used for the connection. 6. Set strict connection and read timeouts. 7. Stream the response and enforce a conservative maximum size before parsing. 8. Consider allowing only feed URLs returned by a trusted podcast registry, while still validating redirect targets. 9. In environments where private feeds are required, use an explicit opt-in allowlist rather than accepting arbitrary destinations. ]]>
