T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/gitlab_client.py:38
- Finding
- GitLab Token Can Be Exfiltrated to an Attacker-Controlled Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gitlab_client.py:38-78, 122-125, 142-144, 196-199` **Vulnerability Type**: Credential disclosure through unvalidated network destination **Risk Level**: Critical ### Vulnerable Code ```python def parse_mr_url(url: str) -> tuple[str, str, str]: """ Parse a GitLab MR URL into (host, project_path_encoded, mr_iid). Supports: https://gitlab.com/group/subgroup/project/-/merge_requests/123 https://gitlab.example.com/namespace/project/-/merge_requests/456 Returns (host, url_encoded_project_path, mr_iid). """ pattern = r"(https?://[^/]+)/(.+)/-/merge_requests/(\d+)" m = re.match(pattern, url.rstrip("/")) if not m: raise ValueError(f"Cannot parse MR URL: {url}") host = m.group(1) project_path = m.group(2) mr_iid = m.group(3) encoded_path = project_path.replace("/", "%2F") return host, encoded_path, mr_iid def api_get(host: str, token: str, path: str, params: dict | None = None) -> dict | list: url = f"{host}/api/v4{path}" if params: url += "?" + urlencode(params) req = Request(url, headers={"PRIVATE-TOKEN": token, "Content-Type": "application/json"}) try: with urlopen(req) as resp: return json.loads(resp.read().decode()) except HTTPError as e: body = e.read().decode() raise RuntimeError(f"GitLab API error {e.code} on GET {path}: {body}") from e ``` The vulnerable data flow is invoked as follows: ```python def fetch_mr(mr_url: str) -> dict: """Return MR metadata (title, author, source_branch, target_branch, description, state).""" creds = load_credentials() host, project, iid = parse_mr_url(mr_url) data = api_get(host, creds["token"], f"/projects/{project}/merge_requests/{iid}") ``` ### Technical Analysis The Skill loads a privileged GitLab token from `~/.openclaw/credentials/gitlab.json`, but it does not bind that token to the configured GitLab ho ...[truncated 1865 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Normalize the `host` value from the credential file and use it as the exclusive API destination. 2. Parse the user-supplied MR URL with `urllib.parse.urlsplit`. 3. Require the URL scheme to be exactly `https`. 4. Compare the normalized scheme, hostname, and effective port against the configured host before loading or transmitting the token. 5. Reject URLs containing user information, unexpected ports, fragments, or origins that do not exactly match the configured GitLab origin. 6. Avoid redirects to different origins, or implement a redirect handler that rejects every cross-origin redirect while carrying credentials. 7. Add tests proving that arbitrary hosts, HTTP URLs, deceptive subdomains, and alternate ports cannot receive the token. 8. Revoke and replace any token that may already have been used with an untrusted MR URL. A safe design should resemble: ```python from urllib.parse import urlsplit def validate_mr_origin(mr_url: str, configured_host: str) -> None: supplied = urlsplit(mr_url) configured = urlsplit(configured_host) if supplied.scheme != "https": raise ValueError("MR URLs must use HTTPS") if ( supplied.hostname != configured.hostname or supplied.port != configured.port or supplied.username is not None or supplied.password is not None ): raise ValueError("MR URL origin does not match the configured GitLab host") def fetch_mr(mr_url: str) -> dict: creds = load_credentials() validate_mr_origin(mr_url, creds["host"]) _, project, iid = parse_mr_url(mr_url) return api_get( creds["host"].rstrip("/"), creds["token"], f"/projects/{project}/merge_requests/{iid}", ) ``` ]]>
