T09 · Insecure Skill Coding Practices
- Location
scripts/search.py:31- Finding
Unvalidated Environment-Controlled Proxy Exposes Search Queries and Session Identifiers
- Content
View full analysis
Vulnerability Details
File Location:
scripts/search.py, lines 31-58
Vulnerability Type: Unvalidated network destination and sensitive-data disclosure
Risk Level: HighVulnerable Code
python def resolve_sandbox_url(original_url: str) -> Tuple[str, Dict[str, str]]: """若当前在沙盒环境中,将目标 URL 替换为代理 URL,并返回需要附加的 headers。""" session_id = os.environ.get("DUMATE_SESSION_ID") scheduler_url = os.environ.get("DUMATE_SCHEDULER_URL") headers = { "Content-Type": "application/json", } if not session_id or not scheduler_url: # 优先使用传入的 api_key,否则从环境变量读取 api_key = os.environ.get("BAIDU_API_KEY") if not api_key: raise ValueError("未设置 API Key,请通过环境变量 BAIDU_API_KEY 设置或使用") headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", "X-Appbuilder-From": "openclaw", } return original_url, headers parsed = urlparse(original_url) proxy_url = f"{scheduler_url}/api/qianfanproxy{parsed.path}" if parsed.query: proxy_url += f"?{parsed.query}" headers.update({ "Host": parsed.netloc, "X-Dumate-Session-Id": session_id, "X-Appbuilder-From": "desktop", }) return proxy_url, headersThe returned URL and headers are subsequently used by the following request at lines 11-16:
python url = "https://qianfan.baidubce.com/v2/ai_search/web_search" url, headers = resolve_sandbox_url(url) # 使用POST方法发送JSON数据 response = requests.post(url, json=requestBody, headers=headers) response.raise_for_status() results = response.json()Technical Analysis
The Skill's declared purpose requires sending search queries to Baidu's AI Search API. The direct request to the fixed HTTPS origin
qianfan.baidubce.comis therefore consistent with its stated functionality.However, when both
DUMATE_SESSION_IDandDUMATE_SCHEDULER_URLare present, the implementation silently repl ...[truncated 2688 chars]- Remediation
View remediation
Remediation Suggestions
- Remove the sandbox proxy branch if it is not essential to the Skill's declared search functionality.
- If proxying is required, use a fixed trusted proxy origin or validate the destination against a strict hostname allowlist.
- Require the
httpsscheme and reject HTTP, unsupported schemes, embedded credentials, fragments, unexpected ports, and malformed origins. - Parse the configured proxy URL with
urllib.parse.urlparse()and construct the destination from validated components rather than concatenating an unrestricted string. - Document the proxy behavior, transmitted fields, and trust assumptions in
SKILL.md. - Avoid sending a reusable session identifier where possible. Otherwise, use a narrowly scoped, short-lived proxy token.
- Prevent unintended redirect-based data disclosure by disabling redirects or validating every redirect destination.
- Add a bounded connection and response timeout to the request.
- Remove or redact the input logging at
scripts/search.py:63, where the full parsed query is printed, to reduce secondary exposure through logs. - Add tests confirming that HTTP URLs, unapproved hosts, embedded credentials, and unexpected ports are rejected.
A hardened design should resemble:
python ALLOWED_PROXY_HOSTS = {"trusted-scheduler.example"} parsed_scheduler = urlparse(scheduler_url) if ( parsed_scheduler.scheme != "https" or parsed_scheduler.hostname not in ALLOWED_PROXY_HOSTS or parsed_scheduler.username is not None or parsed_scheduler.password is not None or parsed_scheduler.fragment or parsed_scheduler.port not in (None, 443) ): raise ValueError("Untrusted scheduler URL") proxy_url = ( f"https://{parsed_scheduler.hostname}" f"/api/qianfanproxy{urlparse(original_url).path}" ) response = requests.post( proxy_url, json=requestBody, headers=headers, timeout=(5, 30), allow_redirects=False, )
