T09 · Insecure Skill Coding Practices
- Location
scripts/qweather_query.py:100- Finding
QWeather API key can be disclosed to an unrestricted destination host
- Content
View full analysis
Vulnerability Details
File Location:
scripts/qweather_query.py:100-115,scripts/qweather_query.py:151-160
Vulnerability Type: Unvalidated network destination receiving a sensitive credential
Risk Level: MediumVulnerable Code
python def get_api_host(explicit_host: str | None) -> str: host = explicit_host or environ.get("QWEATHER_API_HOST") if not host: fail("Missing API host. Set --api-host or QWEATHER_API_HOST.") return host def request_json(url: str, api_key: str, timeout_s: float) -> Dict[str, Any]: req = urllib.request.Request( url, headers={ "X-QW-Api-Key": api_key, "Accept": "application/json", "Accept-Encoding": "gzip, deflate", "User-Agent": "qweather-city-weather-skill/1.0", }, )python def search_city(query: str, api_key: str, api_host: str, timeout_s: float, number: int) -> List[Dict[str, Any]]: encoded = urllib.parse.quote(query) url = f"https://{api_host}/geo/v2/city/lookup?location={encoded}&number={number}" data = request_json(url, api_key, timeout_s) locations = data.get("location") or data.get("city") or [] if not isinstance(locations, list): return [] return locations def get_weather(location: str, api_key: str, api_host: str, timeout_s: float) -> Dict[str, Any]: encoded = urllib.parse.quote(location) url = f"https://{api_host}/v7/weather/now?location={encoded}" data = request_json(url, api_key, timeout_s)The unrestricted host behavior is also declared in
SKILL.md:20-22andreferences/qweather-http-contract.md:7-10.Technical Analysis
The API host is accepted directly from the
--api-hostargument orQWEATHER_API_HOSTenvironment variable. It is interpolated into an HTTPS URL without parsing the value or verifying that the resulting hostname belongs to QWeather. Ever ...[truncated 1860 chars]- Remediation
View remediation
Remediation Suggestions
- Parse the configured value as a hostname rather than interpolating arbitrary text into a URL.
- Reject user information, paths, query strings, fragments, malformed hostnames, IP literals, and ports not explicitly required by policy.
- Allow only documented QWeather-owned domains or a narrowly defined administrator-controlled allowlist.
- Resolve and validate the final request origin before attaching
X-QW-Api-Key. - Disable automatic redirects for credential-bearing requests, or follow redirects only when the scheme remains HTTPS and the destination remains on the validated QWeather origin.
- Prefer an administrator-controlled full base URL over an unrestricted host supplied on every invocation.
- Add tests proving that attacker-controlled domains, user-info URL forms, malformed hosts, and cross-origin redirects are rejected before any request containing the API key is transmitted.
