T09 · Insecure Skill Coding Practices
- Location
scripts/qweather_api.py:39- Finding
Authentication credentials are transmitted to an unrestricted configurable host
- Content
View full analysis
Vulnerability Details
File Location:
scripts/qweather_api.py:39-92
Vulnerability Type: Unvalidated credential destination
Risk Level: MediumVulnerable Code
python self._api_host = os.environ.get("HEFENG_API_HOST") api_key = os.environ.get("HEFENG_API_KEY") project_id = os.environ.get("HEFENG_PROJECT_ID") key_id = os.environ.get("HEFENG_KEY_ID") private_key_path = os.environ.get("HEFENG_PRIVATE_KEY_PATH") private_key_str = os.environ.get("HEFENG_PRIVATE_KEY") if not self._api_host: raise ValueError("HEFENG_API_HOST environment variable is not set") if api_key: self._auth_header = { "X-QW-Api-Key": api_key, "Content-Type": "application/json" } else: if not project_id or not key_id or ( not private_key_path and not private_key_str ): raise ValueError("Incomplete authentication configuration") if private_key_path: with open(private_key_path, "rb") as f: private_key = f.read() else: private_key = private_key_str.replace( "\\r\\n", "\n" ).replace("\\n", "\n").encode() payload = { "iat": int(time.time()), "exp": int(time.time()) + 900, "sub": project_id, } headers = {"kid": key_id} encoded_jwt = jwt.encode( payload, private_key, algorithm="EdDSA", headers=headers ) self._auth_header = { "Authorization": f"Bearer {encoded_jwt}" } url = f"https://{self._api_host}/geo/v2/city/lookup" response = httpx.get( url, headers=self._auth_header, params={"location": city} )Equivalent unrestricted host construction and credential transmission also occur in
skill.py:155-199and in the generic request method atscripts/qweather_api.py:126-129.Technical Analysis
HEFENG_API_HOSTis used directly to construct the destin ...[truncated 2404 chars]- Remediation
View remediation
Remediation Suggestions
- Parse the configured value as a hostname rather than interpolating an unrestricted string.
- Reject values containing a URL scheme, user information, path, query, fragment, unexpected port, control characters, or an IP literal.
- Maintain an administrator-controlled allowlist of authorized QWeather hosts and explicitly enrolled custom account domains.
- Require a separate confirmation or trusted configuration mechanism before adding a custom domain to the allowlist.
- Use a preconfigured
httpx.Clientwithfollow_redirects=False, explicit TLS verification, and bounded connection/read timeouts. - If redirects must be supported, verify every redirect destination before forwarding authentication headers.
- Apply the same validation in
skill.py,scripts/qweather_api.py, andscripts/configure.pyso no entry point can bypass it. - Document that changing the host changes the party receiving authentication credentials.
