T09 · Insecure Skill Coding Practices
- Location
kokonna.py:45- Finding
Unrestricted API endpoint can disclose bearer credentials and uploaded images
- Content
View full analysis
str: """获取 API Base URL""" config = load_config() return config.get("api_base_url", DEFAULT_API_BASE_URL) ``` The configured value is subsequently used in authenticated requests: ```python def get_device_info(self) -> Optional[Dict]: """获取设备信息""" url = f"{self.api_base_url}/device" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } try: response = requests.post(url, headers=headers, json={}, timeout=10) response.raise_for_status() return response.json() ``` Image uploads transmit both the credential and selected image: ```python image_data = self._resize_image(image_source) base64_data = base64.b64encode(image_data).decode('utf-8') url = f"{self.api_base_url}/upload" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } payload = { "base64": base64_data, "name": filename, } response = requests.post(url, headers=headers, json=payload, timeout=30) response.raise_for_status() ``` ### Technical Analysis The `api_base_url` configuration value is accepted without validating its scheme or destination hostname. The Skill then attaches the configured device API key as a bearer token to requests sent to that endpoint. A malicious or mistakenly configured URL can therefore direct authenticated requests to an arbitrary server. If an `http://` endpoint is used, credentials, device operations, filenames, and image contents can also be intercepted or modified in transit. The Base64 operation itself is not covert exfiltration: it serializes the user-selected image for the documented JSON up ...[truncated 1639 chars]- Remediation
View remediation
str: parsed = urlparse(value) if parsed.scheme != "https": raise ValueError("The API endpoint must use HTTPS") if parsed.hostname not in ALLOWED_API_HOSTS: raise ValueError("The API endpoint hostname is not trusted") if parsed.username or parsed.password or parsed.fragment: raise ValueError("Invalid API endpoint") return value.rstrip("/") ``` ]]>
