T09 · Insecure Skill Coding Practices
- Location
scripts/reelwords_caption_job.py:86- Finding
ReelWords API Key May Be Disclosed to a Cross-Origin Redirect Target
- Content
View full analysis
None: """Download via GET /api/v1/caption-jobs/{id}/video which 302-redirects to signed URL.""" url = f"{BASE_URL}/api/v1/caption-jobs/{job_id}/video" with requests.get(url, headers={"x-api-key": api_key, "Accept": "application/json"}, stream=True, allow_redirects=True, timeout=timeout_s) as r: if r.status_code == 409: raise ReelWordsError(f"Job not ready for download yet (HTTP 409): {r.text}") if r.status_code >= 400: raise ReelWordsError(f"Download endpoint failed: HTTP {r.status_code}: {r.text}") with open(out_path, "wb") as f: for chunk in r.iter_content(chunk_size=1024 * 1024): if chunk: f.write(chunk) ``` ### Technical Analysis The authenticated `/video` endpoint is documented as returning an HTTP 302 redirect to a signed storage URL. The request supplies the reusable ReelWords API key through the custom `x-api-key` header while enabling `allow_redirects=True`. Python Requests has special cross-origin redirect handling for standard authentication information such as the `Authorization` header, but arbitrary sensitive headers such as `x-api-key` are not guaranteed to be removed when the redirect changes origin. The automatically followed request may therefore transmit the ReelWords credential to the storage host named by the redirect. Sending the API key to `https://api.reelwords.ai` for job creation and status polling is necessary for the Skill's declared functionality. Forwarding that credential to a separate download host is not necessary because the redirected URL is already signed. This redirect beh ...[truncated 1730 chars]- Remediation
View remediation
None: endpoint = f"{BASE_URL}/api/v1/caption-jobs/{job_id}/video" with requests.get( endpoint, headers={"x-api-key": api_key, "Accept": "application/json"}, stream=True, allow_redirects=False, timeout=timeout_s, ) as initial: if initial.status_code == 409: raise ReelWordsError( f"Job not ready for download yet (HTTP 409): {initial.text}" ) if initial.status_code not in {301, 302, 303, 307, 308}: raise ReelWordsError( f"Expected download redirect, received HTTP {initial.status_code}" ) location = initial.headers.get("Location") if not location: raise ReelWordsError("Download redirect omitted the Location header.") download_url = urljoin(endpoint, location) parsed = urlparse(download_url) if parsed.scheme != "https" or not parsed.hostname: raise ReelWordsError("Rejected unsafe download redirect.") # Deliberately omit x-api-key from the ...[truncated 681 chars]
