T09 · Insecure Skill Coding Practices
- Location
scripts/cache_doc.py:94- Finding
Redirect destinations bypass the official Ubuntu domain allowlist in cache_doc.py
- Content
View full analysis
Vulnerability Details
File Location:
scripts/cache_doc.py, lines 94-117 and 136-139
Vulnerability Type: Unvalidated HTTP redirect destination
Risk Level: MediumVulnerable Code
python def validate_url(url: str) -> urllib.parse.ParseResult: parsed = urllib.parse.urlparse(url) if parsed.scheme not in ALLOWED_SCHEMES: raise ValueError(f'Only {sorted(ALLOWED_SCHEMES)} URLs are supported') if parsed.username or parsed.password: raise ValueError('Userinfo in URLs is not supported') if parsed.hostname is None: raise ValueError('URL must include a hostname') try: ipaddress.ip_address(parsed.hostname) except ValueError: pass else: raise ValueError('Direct IP URLs are not supported') if parsed.hostname not in ALLOWED_NETLOCS: raise ValueError(f'Only {sorted(ALLOWED_NETLOCS)} URLs are supported') if parsed.port is not None: raise ValueError('Explicit ports are not supported') if BLOCK_QUERY and (parsed.params or parsed.query or parsed.fragment): raise ValueError('Query strings, params, and fragments are not supported') normalized_relative_path(parsed) return parsedpython def fetch_html(url: str) -> str: req = urllib.request.Request(url, headers={'User-Agent': USER_AGENT}) with urllib.request.urlopen(req, timeout=20) as resp: charset = resp.headers.get_content_charset() or 'utf-8' return resp.read().decode(charset, errors='replace')Technical Analysis
The script validates only the URL supplied on the command line. Python's default
urllib.request.urlopen()behavior automatically follows HTTP redirects, but neither each redirect target nor the final value ofresp.geturl()is passed throughvalidate_url().Consequently, a URL initially hosted on
documentation.ubuntu.com,help.ubuntu.com, orubuntu.comca ...[truncated 1543 chars]- Remediation
View remediation
Remediation Suggestions
- Disable automatic redirects and implement a custom redirect handler.
- Apply
validate_url()to every redirect target before following it. - Set a small maximum redirect count to prevent redirect loops.
- Reject HTTPS-to-HTTP transitions and continue enforcing the hostname, port, userinfo, query, and direct-IP restrictions.
- After opening the response, validate
resp.geturl()again before reading or caching its body. - Record the validated final URL in cache metadata so the provenance of cached content is explicit.
- Consider resolving approved hostnames and rejecting addresses in loopback, private, link-local, reserved, and cloud-metadata ranges if server-side request forgery protections must remain effective after DNS resolution.
