T09 · Insecure Skill Coding Practices
Error
- Location
- ask_leonidas.py:43
- Finding
- Unrestricted API Base Can Disclose Bearer Credentials and User Data<![CDATA[ ## Vulnerability Details **File Location**: `ask_leonidas.py:43-54, 99-112`; `healthcheck.py:14-32` **Vulnerability Type**: Unvalidated network destination with sensitive credential transmission **Risk Level**: High ### Vulnerable Code #### `ask_leonidas.py:43-54` ```python def request_json(url: str, payload: Dict[str, Any], api_key: str, timeout_seconds: int) -> Dict[str, Any]: req = urllib.request.Request( url, data=json.dumps(payload).encode("utf-8"), headers={ "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", "X-Client": "openclaw", }, method="POST", ) with urllib.request.urlopen(req, timeout=timeout_seconds) as resp: return json.loads(resp.read().decode("utf-8")) ``` #### `ask_leonidas.py:99-112` ```python api_base = get_env("ASK_LEONIDAS_API_BASE", "").rstrip("/") api_key = get_env("ASK_LEONIDAS_API_KEY", "") timeout = int(get_env("ASK_LEONIDAS_TIMEOUT_SECONDS", str(DEFAULT_TIMEOUT))) if not api_base: print(json.dumps({"error": "ASK_LEONIDAS_API_BASE is not set."}, ensure_ascii=False)) return 1 if not api_key: print(json.dumps({"error": "ASK_LEONIDAS_API_KEY is not set."}, ensure_ascii=False)) return 1 url = api_base + "/api/v1/openclaw/generate" payload = build_payload(args) ``` #### `healthcheck.py:14-32` ```python api_base = os.environ.get("ASK_LEONIDAS_API_BASE", "").rstrip("/") api_key = os.environ.get("ASK_LEONIDAS_API_KEY", "") timeout = int(os.environ.get("ASK_LEONIDAS_TIMEOUT_SECONDS", str(DEFAULT_TIMEOUT))) if not api_base: print(json.dumps({"error": "ASK_LEONIDAS_API_BASE is not set."}, ensure_ascii=False)) return 1 if not api_key: print(json.dumps({"error": "ASK_LEONIDAS_API_KEY is not set."}, ensure_ascii=False)) return 1 req = urllib.request.Request( api_base + "/api/v1/openclaw/he ...[truncated 3691 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Use a fixed production endpoint by default** Define the trusted service endpoint in code instead of requiring a configurable production base: ```python DEFAULT_API_BASE = "https://askleonidas.com" api_base = get_env("ASK_LEONIDAS_API_BASE", DEFAULT_API_BASE) ``` 2. **Validate the URL before attaching credentials** Parse the URL with `urllib.parse.urlparse` and require: - Scheme exactly equal to `https` - Hostname exactly equal to `askleonidas.com` - No embedded username or password - No unexpected port - No malformed or ambiguous hostname Example: ```python from urllib.parse import urlparse TRUSTED_HOST = "askleonidas.com" def validate_api_base(value: str) -> str: parsed = urlparse(value) if parsed.scheme != "https": raise ValueError("ASK_LEONIDAS_API_BASE must use HTTPS.") if parsed.hostname != TRUSTED_HOST: raise ValueError("ASK_LEONIDAS_API_BASE must target askleonidas.com.") if parsed.username or parsed.password: raise ValueError("Embedded URL credentials are not allowed.") if parsed.port not in (None, 443): raise ValueError("Unexpected API port.") return "https://askleonidas.com" ``` 3. **Apply identical validation in every client** Centralize endpoint and credential handling in one shared function used by both `ask_leonidas.py` and `healthcheck.py`. This prevents the health check from becoming a less-protected credential disclosure path. 4. **Separate development endpoint support** If custom endpoints are required for local development, require an explicit development flag and use a separate non-production credential. Never transmit a live production key to a custom host. 5. **Constrain browser fallback** Open only the fixed trusted URL: ```python webbrowser.open("https://askleonidas.com/openclaw") ``` Do not derive the browser destination from a ...[truncated 618 chars]
