T09 · Insecure Skill Coding Practices
- Location
scripts/generate_llms_txt.py:27- Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_llms_txt.py:27-39, with additional affected flows atscripts/generate_llms_txt.py:88-111,scripts/generate_llms_txt.py:169-179, andscripts/generate_llms_txt.py:196-201
Vulnerability Type: Server-Side Request Forgery (SSRF) through user-controlled and sitemap-controlled URLs
Risk Level: HighVulnerable Code
python def __init__(self, domain, timeout=10): self.domain = domain.replace('https://', '').replace('http://', '').rstrip('/') self.base_url = f"https://{self.domain}" self.timeout = timeout self.visited = set() self.pages = [] def fetch(self, path='', full_url=None): """Fetch a URL with error handling.""" url = full_url or urljoin(self.base_url, path) try: resp = requests.get(url, timeout=self.timeout, allow_redirects=True) if resp.status_code == 200: return resp except: pass return NoneSitemap URLs are filtered using an unsafe substring comparison and then fetched:
python def get_sitemap_urls(self): """Fetch URLs from sitemap.xml.""" urls = [] # Try common sitemap locations for path in ['/sitemap.xml', '/sitemap_index.xml', '/sitemap-index.xml']: resp = self.fetch(path) if resp and resp.status_code == 200: # Parse XML import xml.etree.ElementTree as ET try: root = ET.fromstring(resp.text.encode('utf-8')) # Handle both sitemap and urlset for elem in root.iter(): if elem.tag.endswith('loc'): urls.append(elem.text.strip()) except: pass if urls: break # Filter to same domain return [u for u in urls if self.domain in u][:50] # Limit to 50Interactive and file-based generat ...[truncated 4442 chars]
- Remediation
View remediation
Remediation Suggestions
Apply centralized URL validation before every network request:
- Parse URLs with
urllib.parse.urlsplitand permit only explicithttpandhttpsschemes. - Reject URLs containing username or password components.
- Normalize hostnames and compare the parsed hostname exactly against the approved hostname. If subdomains are needed, require either exact equality or a dot-boundary suffix such as
host.endswith("." + approved_host). - Resolve all destination hostnames before connecting. Reject every resolved IPv4 and IPv6 address that is loopback, private, link-local, multicast, reserved, unspecified, or otherwise non-global.
- Protect against DNS rebinding by ensuring the validated address is the address actually used for the connection.
- Disable automatic redirects or process redirects manually. Validate the scheme, hostname, port, and resolved addresses of every redirect target before following it.
- In sitemap mode, accept only URLs belonging to the exact original site or a narrowly defined allowlist. Replace the substring test with parsed-host comparison.
- In interactive and URL-list modes, reject off-domain absolute URLs unless the user explicitly enables a documented and appropriately isolated cross-domain mode.
- Restrict destination ports to
80and443unless other ports are explicitly required and approved. - Enforce maximum response sizes and streaming download limits to reduce denial-of-service exposure.
- Run the generator in a network-isolated environment that cannot reach cloud metadata services, loopback administration endpoints, or sensitive private networks.
- Return explicit validation errors rather than suppressing all exceptions with a bare
except, so rejected or suspicious destinations can be audited.
- Parse URLs with
