T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/model-catalog-updater.py:46
- Finding
- API Credential Disclosure and SSRF Through Unvalidated Provider Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/model-catalog-updater.py`, lines 46–74 **Vulnerability Type**: Unvalidated outbound request with sensitive authorization data **Risk Level**: High ### Vulnerable Code ```python def get_providers(config): """Extract provider info from config.""" providers = config.get("models", {}).get("providers", {}) result = [] for name, data in providers.items(): base_url = data.get("baseUrl", "unknown") api_key = data.get("apiKey", "") result.append({ "name": name, "baseUrl": base_url, "apiKey": api_key, "api": data.get("api", "openai-completions") }) return result def fetch_models(provider): """Fetch models from provider's /v1/models endpoint.""" base_url = provider["baseUrl"].rstrip("/") api_key = provider["apiKey"] # Build auth header based on key type if api_key.startswith("env:"): import os env_var = api_key[4:] api_key = os.environ.get(env_var, "") headers = {"Accept": "application/json"} if api_key and api_key not in ["lmstudio", "qwen-oauth", "minimax-oauth"]: headers["Authorization"] = f"Bearer {api_key}" url = f"{base_url}/models" try: req = Request(url, headers=headers) with urlopen(req, timeout=30) as response: ``` ### Technical Analysis The script reads both `baseUrl` and `apiKey` from the local OpenClaw configuration and constructs an outbound request without validating the URL scheme, normalized hostname, port, resolved address, or trust relationship between the endpoint and credential. An `env:` API-key reference is resolved into the actual environment-variable value before the request is made. The resulting secret is then placed in the `Authorization` header for most providers. Consequently, a provider configured with an attacker-controlled URL causes the script to disclose the credential ...[truncated 2485 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Restrict URL schemes** - Require HTTPS for credential-bearing remote requests. - Permit HTTP only for explicitly approved loopback development endpoints. - Reject unsupported schemes, embedded URL credentials, malformed hosts, and ambiguous URLs. 2. **Validate destinations** - Normalize and parse the URL with `urllib.parse.urlsplit`. - Maintain an allowlist of trusted provider hostnames where feasible. - Resolve destination addresses and reject loopback, link-local, multicast, reserved, and private ranges unless the user has explicitly enabled local-provider access. - Revalidate after DNS resolution to reduce DNS-rebinding risk. 3. **Control redirects** - Disable automatic redirects or validate every redirect destination. - Never forward an `Authorization` header when the scheme, hostname, or port changes. - Reject HTTPS-to-HTTP downgrade redirects. 4. **Bind credentials to trusted providers** - Associate each credential with an expected hostname or provider identifier. - Do not attach a token merely because an arbitrary provider entry contains both a URL and key. - Require explicit confirmation before sending credentials to a new or changed origin. 5. **Reduce credential privileges** - Prefer short-lived, narrowly scoped tokens. - Avoid broad account-level keys where model-listing-only credentials are available. - Keep environment-backed secrets out of logs and error messages. 6. **Harden local-provider support** - Treat unauthenticated local services as a separate provider mode. - Do not send cloud-provider credentials to local endpoints. - Require an explicit configuration flag for private-network access. 7. **Fail securely** - Reject missing or invalid URLs instead of using the placeholder value `"unknown"`. - Display the normalized destination and whether authorization will be sent before making the request. ]]>
