T09 · Insecure Skill Coding Practices
- Location
lib/wahoo_api.py:118- Finding
Unrestricted FIT URL Retrieval Enables Server-Side Request Forgery and Resource Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
lib/wahoo_api.py:118-130; invoked fromscripts/fetch_workouts.py:522-534
Vulnerability Type: Unvalidated remote URL retrieval
Risk Level: HighVulnerable Code
python def download_fit(url: str, dest: Path) -> Path: """Download a FIT file from Wahoo's CDN. No auth required.""" dest.parent.mkdir(parents=True, exist_ok=True) req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) with urllib.request.urlopen(req, timeout=120) as resp, open(dest, "wb") as f: while True: chunk = resp.read(64 * 1024) if not chunk: break f.write(chunk) return destThe caller passes the URL directly from the API response:
python summary = (detail.get("workout_summary") or {}) file_obj = summary.get("file") or {} fit_url = file_obj.get("url") if not fit_url: continue dest = FIT_DIR / f"{wid}.fit" if not dest.exists(): try: wahoo_api.download_fit(fit_url, dest) fits_downloaded += 1 print(f" ⬇️ {wid}.fit ({dest.stat().st_size // 1024} KB)") except Exception as e: print(f" ⚠️ download {wid}: {e}") continueTechnical Analysis
download_fit()accepts an arbitrary URL and passes it tourllib.request.urlopen()without validating:- The URL scheme
- The destination hostname
- Redirect destinations
- The response content type
- The response size
- Whether the resolved address belongs to a private or loopback network
Although the current caller obtains the URL from an authenticated Wahoo API response, it crosses a remote trust boundary and should not be assumed safe. If that response, account data, CDN metadata, proxy traffic, or upstream service is compromised, the URL can point somewhere other than an approved Wahoo CDN.
urllibsupports multiple URL handlers and follows H ...[truncated 1329 chars]- Remediation
View remediation
Remediation Suggestions
- Parse URLs with
urllib.parse.urlsplit()before opening them. - Permit only
httpsURLs. - Maintain an explicit allowlist of documented Wahoo CDN hostnames.
- Reject URLs containing user information, nonstandard ports, fragments, or malformed hostnames.
- Resolve the hostname and reject loopback, link-local, private, multicast, and reserved addresses where appropriate.
- Disable automatic redirects or validate every redirect target against the same policy.
- Require an expected FIT content type or validate the downloaded FIT header before retaining the file.
- Enforce a strict maximum download size using both
Content-Lengthand a running byte counter. - Download into a restricted temporary file and atomically rename it only after successful validation.
- Delete partial downloads after any error.
- Parse URLs with
