T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/create_and_wait.py:63
- Finding
- Bearer API Key Can Be Exfiltrated Through Attacker-Controlled Request URLs## Vulnerability Details **File Location**: `scripts/create_and_wait.py:63-68, 161-169, 221, 324-325, 334-364`; `scripts/wait_generation.py:85-97, 107, 118, 153-159` **Vulnerability Type**: Unrestricted authenticated network destination **Risk Level**: High ### Vulnerable Code In `scripts/create_and_wait.py`, an endpoint may be supplied as an unrestricted full URL: ```python def endpoint_url(base_url: str, endpoint: str) -> str: if endpoint.startswith("http://") or endpoint.startswith("https://"): return endpoint if not endpoint.startswith("/"): endpoint = "/" + endpoint return f"{base_url.rstrip('/')}{endpoint}" ``` The API key is attached to the resulting destination without validating its hostname or transport security: ```python req = request.Request( url, headers={ "Authorization": f"Bearer {api_key}", "Accept": "text/event-stream", "Content-Type": "application/json", }, data=json.dumps(payload).encode("utf-8"), method="POST", ) ``` ```python with request.urlopen(req, timeout=request_timeout) as resp: ``` Both the endpoint and base URL are caller-controlled: ```python parser.add_argument("--sse-endpoint", required=True, help="SSE create endpoint path or full URL") parser.add_argument("--base-url", default="https://open.skills.video/api/v1") ``` ```python api_key = os.environ.get("SKILLS_VIDEO_API_KEY", "").strip() payload = load_payload(args) url = endpoint_url(args.base_url, args.sse_endpoint) sse_rc, generation_id, terminal_payload = run_sse( url=url, api_key=api_key, payload=payload, request_timeout=args.sse_request_timeout, ) ``` The polling helper in `scripts/wait_generation.py` has the same trust-boundary issue: ```python def fetch_generation( base_url: str, generation_id: str, api_key: str, request_timeout: float, ) -> tuple[int, An ...[truncated 3757 chars]
- Remediation
- ## Remediation Suggestions 1. **Remove support for unrestricted absolute endpoint URLs.** Require `--sse-endpoint` to be a relative API path beginning with an approved prefix such as `/generation/sse/`. 2. **Validate the final URL before attaching credentials.** Parse it with `urllib.parse.urlsplit` and require: - Scheme exactly equal to `https`. - Hostname exactly equal to `open.skills.video`. - No embedded username or password. - No unexpected port. - No IP-literal or deceptive suffix hostname. 3. **Restrict or remove `--base-url`.** If custom environments are required, make them an explicit opt-in mode and require a separate credential intended for that origin. Do not reuse the production key automatically. 4. **Constrain redirects.** Disable automatic redirects for authenticated calls or implement a redirect handler that permits redirects only when the destination remains on the same approved HTTPS origin. Strip the `Authorization` header before any cross-origin redirect. 5. **Apply one shared origin-validation function** in both `create_and_wait.py` and `wait_generation.py` before constructing an authenticated request. 6. **Fail closed.** Reject malformed URLs, plaintext HTTP, scheme-relative URLs, unexpected ports, and any origin not explicitly approved. 7. **Add security regression tests** covering: - `http://open.skills.video` - `https://attacker.example` - `https://open.skills.video.attacker.example` - URLs containing user-info - IP-address destinations - Cross-origin redirects - Valid relative paths on `https://open.skills.video` 8. **Rotate exposed credentials.** If either helper has been run with an untrusted URL, revoke the affected API key, issue a new one, and review API usage and credit consumption.
