T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/run.py:20
- Finding
- Unrestricted API Base URL Allows Credential and User-Content Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py`, lines 20–35 and 79–86 **Vulnerability Type**: Unvalidated destination URL for authenticated API requests **Risk Level**: High ### Vulnerable Code ```python def build_base_url(): return os.getenv("AISKILLS_BASE_URL", DEFAULT_BASE_URL).rstrip("/") def build_headers(): api_key = os.getenv("AISKILLS_API_KEY", "").strip() tenant_id = os.getenv("AISKILLS_TENANT_ID", "default").strip() or "default" if not api_key: fail("AISKILLS_API_KEY is required") return { "Content-Type": "application/json", # Cloudflare blocks urllib's default Python user agent for this endpoint. "User-Agent": "ai-skills-runner/1.0 (+https://ai-skills.ai)", "Accept": "application/json", "X-API-Key": api_key, "X-Tenant-Id": tenant_id, } ``` ```python def request_json(method, path, payload): body = json.dumps(payload).encode("utf-8") req = urllib.request.Request( f"{build_base_url()}{path}", data=body, method=method, headers=build_headers(), ) try: with urllib.request.urlopen(req, context=SSL_CONTEXT) as response: return json.loads(response.read().decode("utf-8")) ``` The initial execution request also places all supplied user parameters into the request body: ```python response = request_json("POST", EXECUTE_PATH, {"skillId": SKILL_ID, "params": params}) ``` ### Technical Analysis The destination of authenticated API requests is controlled directly through the `AISKILLS_BASE_URL` environment variable. The value is only processed with `rstrip("/")`; the code does not validate its scheme, hostname, port, embedded credentials, or trust relationship. `request_json()` sends the following sensitive information to the resulting destination: - The `AISKILLS_API_KEY` credential in the `X-API-Key` header. - The tenant identifier in the `X-Tenant-Id` header. - Complete user parameter ...[truncated 2511 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Require HTTPS** - Parse the configured URL with `urllib.parse.urlsplit()`. - Reject every scheme except `https`. - Reject URLs containing embedded usernames or passwords. 2. **Allowlist trusted destinations** - Prefer a fixed production endpoint rather than an environment-controlled base URL. - If endpoint customization is required, compare the normalized hostname and port against an explicit allowlist. - Do not rely on suffix matching such as `hostname.endswith("ai-skills.ai")`, which can accept attacker-controlled names. 3. **Prevent credential forwarding** - Only add `X-API-Key` and `X-Tenant-Id` after confirming that the final destination is trusted. - Use separate, limited credentials for explicitly supported non-production endpoints. - Ensure production credentials are never sent to arbitrary development or testing servers. 4. **Restrict redirects** - Prevent automatic redirects to untrusted hosts, or validate the scheme, hostname, and port of every redirect target before forwarding sensitive headers or request bodies. - Strip authentication and tenant headers whenever a redirect changes origin. 5. **Apply least privilege** - Scope API keys to the required skill and operations where supported. - Apply tenant restrictions, expiration, usage quotas, and credential rotation. - Rotate any credential that may already have been used while an untrusted base URL was configured. 6. **Fail closed** - Terminate execution with a clear error when the configured URL is malformed, uses plaintext HTTP, specifies an unexpected port, or does not match the trusted endpoint policy. - Add automated tests covering malicious hosts, plaintext URLs, embedded credentials, malformed URLs, and cross-origin redirects. ]]>
