T09 · Insecure Skill Coding Practices
Error
- Location
- lib/patterns.py:222
- Finding
- Attacker-Controlled Domains Can Bypass the Safe-Domain Check<![CDATA[ ## Vulnerability Details **File Location**: `lib/patterns.py:222-229` **Vulnerability Type**: Improper hostname allowlist validation **Risk Level**: High ### Vulnerable Code ```python def is_safe_domain(url: str) -> bool: """Check if URL domain is in whitelist""" from urllib.parse import urlparse try: domain = urlparse(url).netloc return any(safe in domain for safe in SAFE_DOMAINS) except: return False ``` ### Technical Analysis The scanner determines whether a network endpoint is trusted by checking whether an allowlisted string appears anywhere in the parsed `netloc`. Substring matching does not establish that the endpoint is the allowlisted host. For example, the following attacker-controlled hostnames would be incorrectly classified as safe: - `api.openai.com.attacker.example` - `notapi.github.com` - `clawhub.com.evil.example` The use of `netloc` also includes optional user-information and port components. Host validation should instead use the normalized `urlparse(url).hostname` value and enforce exact hostname or deliberate subdomain-boundary matching. This flaw directly undermines the scanner's declared endpoint-safety feature. Although it does not itself send sensitive data, it can falsely assure users that an attacker-controlled exfiltration endpoint is known to be safe. ### Attack Path 1. An attacker publishes a skill containing a request to an attacker-controlled URL such as `https://api.openai.com.attacker.example/collect`. 2. A user scans the skill with ClawGuard. 3. `extract_urls()` extracts the malicious URL. 4. `is_safe_domain()` parses its network location. 5. The expression `"api.openai.com" in "api.openai.com.attacker.example"` evaluates to true. 6. The report labels the malicious endpoint as `known safe`. 7. The user may install the skill based on this misleading trust indication. 8. When invoked, the malicious skill can send any data available to it to the attacker's server. ### ...[truncated 538 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Use the parsed hostname rather than `netloc`, normalize it, and require a hostname-boundary-safe comparison: ```python from urllib.parse import urlparse def is_safe_domain(url: str) -> bool: try: hostname = urlparse(url).hostname if not hostname: return False hostname = hostname.rstrip(".").lower() for safe_domain in SAFE_DOMAINS: safe_domain = safe_domain.rstrip(".").lower() if hostname == safe_domain: return True # Enable this only where subdomains are explicitly trusted. if hostname.endswith("." + safe_domain): return True return False except (TypeError, ValueError): return False ``` Additional hardening should include: 1. Decide separately for each allowlisted domain whether subdomains are trusted. 2. Convert internationalized domain names to a canonical IDNA representation before comparison. 3. Reject malformed URLs and URLs without a hostname. 4. Add regression tests for suffix spoofing, prefix spoofing, user-information, ports, trailing dots, mixed case, and internationalized hostnames. 5. Treat allowlist status only as contextual information, not as evidence that a request is harmless. ]]>
