T09 · Insecure Skill Coding Practices
- Location
sniffer.py:29- Finding
Incomplete SSRF Protection Allows Access to Internal Network Resources
- Content
View full analysis
Vulnerability Details
File Location:
sniffer.py, lines 29–40, 45–50, 68–69, and 84–85
Vulnerability Type: Server-Side Request Forgery (SSRF) caused by incomplete host validation and unrestricted redirects
Risk Level: HighVulnerable Code:
python def is_internal_host(host: str) -> bool: """Check if host is an internal/reserved address.""" if host.lower() in BLOCKED_HOSTNAMES: return True for pattern in BLOCKED_IP_PATTERNS: if pattern.match(host): return True # Try reverse DNS to detect internal IPs try: resolved = socket.gethostbyname(host) for pattern in BLOCKED_IP_PATTERNS: if pattern.match(resolved): return True except (socket.gaierror, socket.herror): pass return False def sniff_llms_txt(url): parsed = urlparse(url) domain = f"{parsed.scheme}://{parsed.netloc}" host = parsed.netloc # SSRF check if is_internal_host(host): return {"target_url": url, "found_index": None, "type": None, "error": "Internal hosts blocked (SSRF protection)"}The validated URL is subsequently requested without any validation of redirect destinations:
python response = urlopen(probe_url, timeout=5)The same request pattern is used for sitemap probing:
python response = urlopen(sitemap_url, timeout=5)Technical Analysis
The implementation validates
parsed.netlocinstead ofparsed.hostname. Thenetlocvalue can include a port or user-information component. For example, parsinghttp://127.0.0.1:8080produces anetlocof127.0.0.1:8080. The anchored regular expression^127\.initially matches this particular value, but other hostname checks and DNS resolution are still incorrectly performed against an authority string rather than a canonical hostname. More importantly, equivalent or noncanonical IP representati ...[truncated 3135 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate canonical URL components
- Accept only
httpandhttps. - Use
parsed.hostname, notparsed.netloc, for host validation. - Reject embedded credentials, malformed ports, empty hostnames, fragments, and unexpected URL forms.
- Accept only
-
Use positive public-address validation
- Resolve hosts with
socket.getaddrinfoto cover both IPv4 and IPv6. - Parse every returned address with Python's
ipaddress.ip_address. - Allow a request only when every possible destination is explicitly classified as globally routable.
- Reject loopback, private, link-local, multicast, reserved, unspecified, documentation, and other non-global addresses.
- Fail closed when DNS resolution or address parsing fails.
- Resolve hosts with
-
Validate every redirect
- Disable automatic redirects and process each
Locationheader manually. - Canonicalize and validate the scheme, hostname, port, and all resolved addresses for every redirect target.
- Enforce a small redirect limit.
- Reject redirects to a different origin unless cross-origin redirects are strictly required.
- Disable automatic redirects and process each
-
Mitigate DNS rebinding
- Avoid independently resolving the hostname during validation and again during connection.
- Connect only to an address that was validated, while preserving the intended hostname for TLS verification and the HTTP
Hostheader. - Revalidate each new connection and redirect destination.
-
Reduce response exposure
- Enforce strict response-size and content-type limits.
- Avoid returning raw response previews unless required.
- Sanitize or summarize retrieved content before including it in output.
-
Apply network-level controls
- Run the skill in a sandbox with egress rules that deny loopback, private, link-local, metadata, and management networks.
- Use an outbound proxy that independently enforces destination policy.
-
