T09 · Insecure Skill Coding Practices
- Location
scripts/local_client.py:44- Finding
Caller-Controlled Base URL Bypasses the Documented Loopback-Only Network Restriction
- Content
View full analysis
None: self.base_url = base_url.rstrip("/") self.timeout = timeout self.session = requests.Session() self.session.headers.update({"Content-Type": "application/json"}) ``` ```python def get_client( base_url: str = LOCAL_BASE_URL, timeout: int = DEFAULT_TIMEOUT, ) -> DYULocalClient: """ 获取全局 DYULocalClient 单例。 无需任何凭证,直接使用即可。 示例: from local_client import get_client client = get_client() status = client.status() """ global _client if _client is None: _client = DYULocalClient(base_url, timeout) return _client ``` ### Technical Analysis The Skill documentation states that all requests are restricted to `http://127.0.0.1:17321`. However, both `DYULocalClient` and the public `get_client()` function accept an arbitrary `base_url` without validating its scheme, hostname, or port. The URL is subsequently used by `_url()`, `_get()`, and `_post()` for every request. Therefore, a caller can redirect client operations to an external or unintended internal HTTP service. The global singleton makes the issue more significant: the first endpoint supplied to `get_client()` is cached and silently reused by later calls in the same process. Generation operations call `check_client()` before submission, but this does not enforce the destination. A malicious server can return a forged `/api/status` response containing `running: true` and `ready: true`, after which the client submits the complete generation request to that server. Query operations do not call `check_client()` and can be redirected immediately. ### Attack Path 1. An attacker influences code that initialize ...[truncated 1896 chars]- Remediation
View remediation
None: self.base_url = LOCAL_BASE_URL ``` 2. If configurability is required, strictly validate the parsed endpoint: ```python from urllib.parse import urlparse def validate_base_url(base_url: str) -> str: parsed = urlparse(base_url) if ( parsed.scheme != "http" or parsed.hostname != "127.0.0.1" or parsed.port != 17321 or parsed.username is not None or parsed.password is not None or parsed.path not in ("", "/") or parsed.query or parsed.fragment ): raise ValueError("Only http://127.0.0.1:17321 is permitted") return "http://127.0.0.1:17321" ``` 3. Apply validation inside `DYULocalClient.__init__()` so direct construction cannot bypass restrictions. 4. Remove the global singleton or ensure that singleton identity includes the validated configuration. A caller-selected endpoint must not silently affect later users. 5. Add automated tests rejecting: - External hostnames and IP addresses. - Alternate loopback ports. - HTTPS or unsupported schemes. - User-information URL syntax. - Redirect-style or parser-confusion URLs. - Paths, queries, and fragments in the base URL. 6. Configure the HTTP session not to follow redirects, or validate every redirect target, so a permitted loopback endpoint cannot redirect requests to an external destination. ]]>
