T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/jira_cli.py:18
- Finding
- Jira credentials can be transmitted to an attacker-controlled host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/jira_cli.py:18-27` **Vulnerability Type**: Unvalidated credential destination **Risk Level**: High ### Vulnerable Code ```python BASE_URL = f"https://{os.getenv('JIRA_DOMAIN')}/rest/api/3" AUTH = (os.getenv('JIRA_EMAIL'), os.getenv('JIRA_API_TOKEN')) if not all([os.getenv('JIRA_DOMAIN'), os.getenv('JIRA_EMAIL'), os.getenv('JIRA_API_TOKEN')]): sys.stderr.write('Missing required JIRA environment variables.\n') sys.exit(1) def jira_request(method, path, **kwargs): url = f"{BASE_URL}{path}" resp = requests.request(method, url, auth=AUTH, headers={'Accept': 'application/json'}, **kwargs) resp.raise_for_status() return resp.json() ``` ### Technical Analysis The destination hostname is constructed directly from the `JIRA_DOMAIN` environment variable without validating that it is the intended Jira Cloud workspace or even an Atlassian-controlled hostname. The same request is supplied with HTTP Basic Authentication containing `JIRA_EMAIL` and `JIRA_API_TOKEN`. HTTPS only protects transport to the selected server; it does not establish that the selected server is trustworthy. If `JIRA_DOMAIN` is changed to a hostname controlled by an attacker, the first Jira operation sends the victim's Basic Authentication header to that server. This contradicts the Skill metadata, which identifies a specific Jira workspace, but the implementation does not pin or allowlist that workspace. ### Attack Path 1. An attacker influences the Skill's runtime configuration, deployment environment, shell profile, `.env` source, or CI variables. 2. The attacker sets `JIRA_DOMAIN` to a server under their control while valid values remain configured for `JIRA_EMAIL` and `JIRA_API_TOKEN`. 3. A user or agent invokes any command, such as `jira list`. 4. The script constructs a URL under the attacker's hostname. 5. `requests` creates a Basic Authentication header from the Jira email and API token and sends it ...[truncated 832 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Pin the expected hostname when the Skill is designed for one specific workspace: ```python from urllib.parse import urlparse EXPECTED_HOST = "omeshkshatriya.atlassian.net" configured_host = os.environ.get("JIRA_DOMAIN", "").strip().lower() if configured_host != EXPECTED_HOST: raise ValueError("JIRA_DOMAIN is not an approved Jira hostname") BASE_URL = f"https://{EXPECTED_HOST}/rest/api/3" ``` 2. If multiple tenants must be supported, parse and validate the value as a hostname and enforce a strict allowlist. Do not accept URL schemes, credentials, ports, paths, query strings, fragments, IP addresses, or arbitrary domains. 3. Restrict approved hosts to explicitly configured Jira tenants rather than relying only on a broad suffix check. 4. Disable redirects for authenticated requests unless they are explicitly required: ```python resp = requests.request( method, url, auth=AUTH, headers={"Accept": "application/json"}, allow_redirects=False, timeout=30, **kwargs, ) ``` 5. If redirects are required, validate every redirect destination before resending a request and never forward credentials to a different origin. 6. Add connection and read timeouts and fail closed on validation errors. 7. Protect deployment environment variables and CI configuration from modification by untrusted users. 8. Revoke and rotate the Jira API token if the Skill has previously run with an untrusted `JIRA_DOMAIN`. ]]>
