T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/meeting_assistant.py:30
- Finding
- Unrestricted API Base URL Can Expose Credentials and Confidential Meeting Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/meeting_assistant.py:30, 43-46, 75-81, 127-138, 178-189` **Vulnerability Type**: Unvalidated external service configuration and sensitive-data transmission **Risk Level**: High ### Vulnerable Code ```python DEFAULT_API_BASE = os.environ.get("ASTRONCLAW_API_BASE", "https://api.astronclaw.com") API_KEY = os.environ.get("ASTRONCLAW_API_KEY", "") ``` ```python def get_headers() -> dict: return { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } ``` ```python url = f"{DEFAULT_API_BASE}/v1/audio/transcriptions" files = {"file": (audio_path.name, audio_data)} data = {"model": "whisper-1", "language": language} headers = {"Authorization": f"Bearer {API_KEY}"} if API_KEY else {} response = requests.post(url, files=files, data=data, headers=headers) ``` ```python url = f"{DEFAULT_API_BASE}/v1/chat/completions" payload = { "model": "gpt-4o", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_message}, ], "temperature": 0.3, } response = requests.post(url, json=payload, headers=get_headers()) ``` ```python url = f"{DEFAULT_API_BASE}/v1/chat/completions" payload = { "model": "gpt-4o", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Please extract todos from the following meeting content:\n\n{transcript}"}, ], "temperature": 0.2, "response_format": {"type": "json_object"}, } response = requests.post(url, json=payload, headers=get_headers()) ``` ### Technical Analysis The destination used for every API request is read directly from the `ASTRONCLAW_API_BASE` environment variable. The value is not validated before the application appends an API route and submits a request. The implementation does not enforce: - The HTTPS scheme. - The documented AstronClaw hostname. - An allowlist of trusted API hosts. - ...[truncated 2262 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse the configured URL with `urllib.parse.urlparse` before using it. 2. Require `https` and reject plaintext HTTP. 3. Allowlist the documented API hostname, or maintain a narrowly scoped list of explicitly approved enterprise endpoints. 4. Reject URLs containing embedded credentials, fragments, unexpected ports, or malformed hostnames. 5. Disable redirects or validate every redirect target before forwarding credentials or request bodies. 6. Never forward the bearer token to a different origin. 7. If custom endpoints are a required feature, require an explicit opt-in and display the exact destination before transmitting meeting data. 8. Add a request timeout and fail closed on TLS or destination-validation errors. 9. Document clearly that recordings and transcripts are transmitted to an external service. A hardened validation pattern could resemble: ```python from urllib.parse import urlparse ALLOWED_API_HOSTS = {"api.astronclaw.com"} def validate_api_base(value: str) -> str: parsed = urlparse(value) if parsed.scheme != "https": raise ValueError("The API base URL must use HTTPS") if parsed.hostname not in ALLOWED_API_HOSTS: raise ValueError("The API hostname is not approved") if parsed.username or parsed.password or parsed.fragment: raise ValueError("The API base URL contains prohibited components") return value.rstrip("/") ``` Requests should also use a finite timeout and either disable redirects or validate them explicitly: ```python response = requests.post( url, json=payload, headers=get_headers(), timeout=(5, 120), allow_redirects=False, ) ``` ]]>
