T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/jira_api.py:187
- Finding
- Jira API Token Disclosure Through Arbitrary Generic Request Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/jira_api.py`, lines 117–119, 187–200, and 636–646 **Vulnerability Type**: Arbitrary-host credential disclosure caused by insufficient request destination validation **Risk Level**: High ### Vulnerable Code Credential construction at lines 117–119: ```python def _basic_auth_header(user: str, token: str) -> str: raw = f"{user}:{token}".encode("utf-8") return "Basic " + base64.b64encode(raw).decode("ascii") ``` Generic request handling at lines 187–200: ```python def cmd_request(args, cfg): server = cfg["server"] url = urllib.parse.urljoin(server + "/", args.path.lstrip("/")) if args.query: q = urllib.parse.urlencode(args.query) url += ("&" if "?" in url else "?") + q body = None if args.data_json: body = json.loads(args.data_json) elif args.data_file: with open(args.data_file, "r", encoding="utf-8") as f: body = json.load(f) status, j, raw = _http(args.method, url, args.headers, body) ``` Credential loading and header assignment at lines 636–646: ```python cfg = _read_jira_config(args.jira_config) host = urllib.parse.urlparse(cfg["server"]).hostname if not host: raise RuntimeError("Could not parse Jira hostname") user, token = _netrc_auth_for_host(host, args.netrc) args.headers = { "Accept": "application/json", "Authorization": _basic_auth_header(user, token), "User-Agent": "openclaw-skill/jira-api", } ``` ### Technical Analysis The script legitimately reads a Jira API token from `.netrc` and encodes the username and token using Base64 for HTTP Basic authentication. Base64 encoding is required by the authentication protocol and is not, by itself, evidence of covert exfiltration. The vulnerability occurs because the generic `request` command does not require `args.path` to be a relative Jira API path. It passes the value to `urllib.parse.urljoin()`: ```python url = urllib.parse.urljoin(server + ...[truncated 3103 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Reject absolute request targets.** Require `args.path` to be a relative Jira API path. Reject values containing a URL scheme, hostname, user information, or network-path reference. 2. **Validate the final origin.** After constructing the URL, compare its scheme, normalized hostname, and effective port with the configured Jira origin. Require HTTPS. ```python base = urllib.parse.urlsplit(cfg["server"]) candidate = urllib.parse.urlsplit(args.path) if candidate.scheme or candidate.netloc: raise ValueError("Absolute URLs are not allowed") target = urllib.parse.urljoin(cfg["server"].rstrip("/") + "/", args.path.lstrip("/")) parsed_target = urllib.parse.urlsplit(target) base_port = base.port or (443 if base.scheme == "https" else 80) target_port = parsed_target.port or (443 if parsed_target.scheme == "https" else 80) if ( base.scheme != "https" or parsed_target.scheme != "https" or parsed_target.hostname != base.hostname or target_port != base_port ): raise ValueError("Request target must remain on the configured Jira HTTPS origin") ``` 3. **Protect credentials across redirects.** Disable automatic redirects for authenticated requests or validate every redirect destination before forwarding the `Authorization` header. Credentials must never be forwarded to a different origin. 4. **Separate authenticated and unauthenticated HTTP logic.** Add the Jira authorization header only after the destination has passed origin validation. Avoid placing credentials in a globally reused header dictionary. 5. **Constrain local file input.** If `--data-file` is needed for Agent-driven use, resolve the path and restrict it to an explicitly approved workspace directory. Reject symlinks and paths escaping that directory. Alternatively, remove this option when it is not essential. 6. **Apply Jira-side least privilege.** Use a dedicated Jira service account and a narrowly scoped API token where supported. Limit projec ...[truncated 522 chars]
