T09 · Insecure Skill Coding Practices
- Location
scripts/scrape_and_summarize.py:20- Finding
Unrestricted URL Fetching Enables SSRF and Local File Disclosure
- Content
View full analysis
Vulnerability Details
File Location:
scripts/scrape_and_summarize.py, lines 20–29 and 109–114
Vulnerability Type: Server-Side Request Forgery (SSRF) and unrestricted URI scheme access
Risk Level: HighVulnerable Code
python def fetch_page(url): """Fetch web page content using curl.""" try: result = subprocess.run( ['curl', '-s', '-L', '-A', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', '--max-time', '30', url], capture_output=True, text=True, timeout=35, encoding='utf-8', errors='replace' ) return result.stdout except Exception as e: return NoneThe untrusted URL originates from the command line:
python url = sys.argv[1] style = sys.argv[2] if len(sys.argv) > 2 else 'brief' print(f"Fetching: {url}", file=sys.stderr) html = fetch_page(url)Technical Analysis
The script passes a user-controlled URL directly to
curlwithout validating its scheme, destination host, resolved IP address, port, or redirect targets. Using an argument array prevents shell metacharacters in the URL from becoming shell commands, but it does not constrain the network and filesystem resources thatcurlcan access.In particular,
curlcan support non-web schemes such asfile://, while HTTP requests can target loopback interfaces, private networks, link-local services, and cloud instance metadata endpoints. The-Loption follows redirects automatically, so an apparently public URL can redirect to an otherwise prohibited internal destination.The fetched resource is subsequently cleaned and supplied to the summarizer. If Ollama fails, lines 140–145 print up to 2,000 characters of the fetched content, creating a direct disclosure channel:
python else: print("\nError: Failed to generate summary with ...[truncated 1579 chars]- Remediation
View remediation
Remediation Suggestions
- Parse the URL before making any request and allow only
httpandhttps. - Reject URLs containing embedded credentials, ambiguous host syntax, unsupported ports, or malformed addresses.
- Resolve the hostname and reject every resolved loopback, private, link-local, multicast, reserved, unspecified, and documentation-range address for both IPv4 and IPv6.
- Explicitly block cloud metadata destinations, including link-local metadata addresses and environment-specific metadata hostnames.
- Disable automatic redirects or inspect and validate every redirect destination before following it.
- Protect against DNS rebinding by connecting only to the validated resolved address and ensuring subsequent resolutions cannot silently change the destination.
- Prefer a constrained HTTP client library configured with explicit protocol, redirect, response-size, and destination policies rather than invoking unrestricted
curl. - Apply a maximum response size while downloading instead of truncating only after the complete response has been received.
- Remove the raw-content fallback for potentially sensitive resources, or require explicit trusted-user approval before displaying fetched content.
- Run the scraper in a sandbox with no access to local secrets, metadata endpoints, or internal networks not required by its declared function.
- Parse the URL before making any request and allow only
