Summary
transcribe.download_audio shells out to yt-dlp directly on the user-supplied URL without going through security.validate_url. The SSRF guards in security.py (private-IP block, link-local block, scheme allowlist) are bypassed for the YouTube/audio ingest path.
Where
graphify/transcribe.py:48-88 — download_audio builds a yt-dlp argv from the URL and runs it; no validate_url call on the path.
Impact
SECURITY.md documents the SSRF guards in validate_url / safe_fetch but does not disclose this exemption. If a caller passes an attacker-controlled URL through this path:
yt-dlp will attempt many extractors against the host, including ones that fall back to direct HTTP fetches.
yt-dlp does not honor your _NoFileRedirectHandler and has its own redirect-following logic.
- A URL pointed at internal infrastructure (e.g. metadata endpoints, internal APIs) bypasses every check
validate_url performs.
This is the same class of issue as the DNS rebinding one I just filed (#591) — same underlying assumption ("our SSRF guard runs on every URL") doesn't actually hold.
Suggested fix shape
Two reasonable options, depending on how strict you want to be:
- Allowlist hosts.
yt-dlp is realistically used against ~10 video platforms in this codebase. Validate the URL host against a small allowlist (youtube.com, youtu.be, vimeo.com, …) before calling yt-dlp. Cheapest defence.
- Run
validate_url first, then pass to yt-dlp. Catches the link-local / private-IP cases. Doesn't help if yt-dlp itself follows a redirect to a private IP — so combine with --no-redirect if yt-dlp supports it for your use case.
Happy to PR #1 if you'd like.
Related
This was found alongside the issues fixed in #589 (cache race + clone arg injection) and #591 (DNS rebinding TOCTOU), but it's a distinct exposure so I'm filing separately.
Summary
transcribe.download_audioshells out toyt-dlpdirectly on the user-supplied URL without going throughsecurity.validate_url. The SSRF guards insecurity.py(private-IP block, link-local block, scheme allowlist) are bypassed for the YouTube/audio ingest path.Where
graphify/transcribe.py:48-88—download_audiobuilds ayt-dlpargv from the URL and runs it; novalidate_urlcall on the path.Impact
SECURITY.mddocuments the SSRF guards invalidate_url/safe_fetchbut does not disclose this exemption. If a caller passes an attacker-controlled URL through this path:yt-dlpwill attempt many extractors against the host, including ones that fall back to direct HTTP fetches.yt-dlpdoes not honor your_NoFileRedirectHandlerand has its own redirect-following logic.validate_urlperforms.This is the same class of issue as the DNS rebinding one I just filed (#591) — same underlying assumption ("our SSRF guard runs on every URL") doesn't actually hold.
Suggested fix shape
Two reasonable options, depending on how strict you want to be:
yt-dlpis realistically used against ~10 video platforms in this codebase. Validate the URL host against a small allowlist (youtube.com,youtu.be,vimeo.com, …) before callingyt-dlp. Cheapest defence.validate_urlfirst, then pass toyt-dlp. Catches the link-local / private-IP cases. Doesn't help ifyt-dlpitself follows a redirect to a private IP — so combine with--no-redirectifyt-dlpsupports it for your use case.Happy to PR #1 if you'd like.
Related
This was found alongside the issues fixed in #589 (cache race + clone arg injection) and #591 (DNS rebinding TOCTOU), but it's a distinct exposure so I'm filing separately.