T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/brickset_cli.py:30
- Finding
- Arbitrary API Base URL Allows Credential Exfiltration## Vulnerability Details **File Location**: `scripts/brickset_cli.py:30-56, 109, 181-182, 235-239` **Vulnerability Type**: Unrestricted credential-bearing network destination **Risk Level**: High The CLI permits the API base URL to be supplied through `--base-url` or `BRICKSET_BASE_URL` without validating the scheme, hostname, port, query, or fragment. It then automatically transmits Brickset credentials to that destination. Relevant code: ```python class BricksetClient: def __init__(self, api_key: str, user_hash: str = "", base_url: str = DEFAULT_BASE_URL, timeout: int = READ_TIMEOUT_SECONDS): self.api_key = api_key self.user_hash = user_hash self.base_url = base_url.rstrip("/") self.timeout = timeout def post_form(self, path: str, fields: dict[str, Any], *, user_hash: str | None = None) -> Any: body_fields = { "apiKey": self.api_key, "userHash": self.user_hash if user_hash is None else user_hash, **clean_params(fields), } body = urllib.parse.urlencode(body_fields, doseq=True).encode() url = f"{self.base_url}{path}" headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"} return self._request("POST", url, body, headers) def login(self, username: str, password: str) -> Any: body = urllib.parse.urlencode({"apiKey": self.api_key, "username": username, "password": password}).encode() url = f"{self.base_url}/login" headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"} return self._request("POST", url, body, headers) def _request(self, method: str, url: str, body: bytes | None = None, headers: dict[str, str] | None = None) -> Any: request = urllib.request.Request(url, data=body, headers=headers or {"Accept": "application/json"}, method=method) try: ...[truncated 3277 chars]
- Remediation
- ## Remediation Suggestions 1. Remove the production base-URL override and always use `https://brickset.com/api/v3.asmx`. 2. If an override is operationally necessary, parse it with `urllib.parse.urlsplit()` and enforce: - Scheme exactly equal to `https`. - Hostname exactly equal to `brickset.com`. - Expected API path only. - No embedded username or password. - No query string or fragment. - No unexpected port. 3. Resolve endpoint paths against a validated origin rather than using direct string concatenation. 4. Do not load or transmit `BRICKSET_USER_HASH` for public operations unless the operation explicitly requires authenticated context. 5. For local testing, require a conspicuous development-only switch and refuse to send non-placeholder credentials to custom endpoints. 6. Add automated tests confirming that HTTP URLs, non-Brickset hosts, lookalike domains, embedded credentials, unexpected ports, query strings, and fragments are rejected. 7. Rotate any API key, user hash, or password that may already have been sent to an untrusted endpoint.
