T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/build_digest.py:100
- Finding
- Unrestricted Feed Fetching Enables Server-Side Request Forgery and Local File Access## Vulnerability Details **File Location**: `scripts/parse_opml.py:11-17`, `scripts/build_digest.py:10-18`, and `scripts/build_digest.py:100-103` **Vulnerability Type**: Server-Side Request Forgery (SSRF) and unsafe URL scheme handling **Risk Level**: High ### Vulnerable Code `scripts/parse_opml.py:11-17`: ```python for o in root.findall('.//outline'): url = o.attrib.get('xmlUrl') or o.attrib.get('url') if not url: continue title = o.attrib.get('text') or o.attrib.get('title') or url feeds.append((title.strip(), url.strip())) ``` `scripts/build_digest.py:10-18`: ```python def read_feeds(path): feeds = [] with open(path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or '\t' not in line: continue title, url = line.split('\t', 1) feeds.append((title, url)) return feeds ``` `scripts/build_digest.py:100-103`: ```python req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) with urllib.request.urlopen(req, timeout=12) as r: xml_text = r.read().decode('utf-8', errors='ignore') feed_ok += 1 ``` ### Technical Analysis Feed URLs originate from user-supplied OPML or a feed-list file and are passed directly to `urllib.request.urlopen`. The implementation does not validate the URL scheme, hostname, resolved IP address, port, or redirect destination. Consequently, an attacker can supply URLs targeting loopback interfaces, private networks, link-local services, or cloud metadata endpoints. Because `urllib.request` supports URL schemes beyond HTTP and HTTPS, a `file://` URL may also read a locally accessible XML file. Automatic redirects can bypass a superficial hostname check unless every redirect target is independently validated. The 12-second timeout and `--max-feeds` limit constrain request duration and count but do not prevent access to prohibited destinations. Any retrieved content that is valid RSS, A ...[truncated 1817 chars]
- Remediation
- ## Remediation Suggestions 1. **Restrict URL schemes** - Parse every URL with `urllib.parse.urlsplit`. - Permit only `https`, or `http` where explicitly required. - Reject `file`, `ftp`, `data`, and all other schemes. - Reject URLs containing embedded usernames or passwords. 2. **Block prohibited destinations** - Resolve the hostname before connecting. - Use Python's `ipaddress` module to reject loopback, private, link-local, multicast, reserved, and unspecified addresses. - Validate every returned DNS address rather than accepting the first safe-looking result. - Explicitly block known cloud metadata destinations. 3. **Control redirects** - Disable automatic redirects or use a custom redirect handler. - Reapply complete scheme, hostname, port, DNS, and IP validation to every redirect target. - Set a low maximum redirect count. 4. **Reduce DNS rebinding exposure** - Ensure the address validated is the address used for the connection. - Where the deployment permits it, enforce outbound network policy at the container, firewall, or proxy layer. 5. **Constrain response processing** - Enforce an allowlist of expected XML content types where practical. - Stream responses and stop after a configured maximum byte count rather than calling unbounded `read()`. - Apply strict connection and read timeouts. 6. **Prefer an explicit trust model** - Require user confirmation for newly imported domains. - Consider a domain allowlist for approved public feed providers. - Treat OPML and feed-list files as untrusted input. 7. **Add security tests** - Verify rejection of `file://`, loopback, private, link-local, IPv6-local, encoded-IP, credential-bearing, and nonstandard-port URLs. - Test public-to-private redirects and hostnames resolving to mixed public/private addresses.
