ITADN

dspy.Audio auto-downloads from any http(s) string with no timeout and no SSRF guard

#9993OpenErenAta16 创建于 2026-07-11
E
ErenAta16commented
### Description `dspy.Audio` auto-downloads from any URL-shaped string it's given, with no timeout and no restriction on what the URL points to. This happens unconditionally, not behind an opt-in flag like `dspy.Image` has. `encode_audio()` (`dspy/adapters/types/audio.py`) is invoked from `Audio`'s pydantic `model_validator(mode="before")`, so it runs on every `Audio(...)` construction, including whenever a plain string is coerced into an `Audio`-typed field (a tool return value, a field parsed out of retrieved document content, a model output that an adapter is coercing into a typed field): ```python elif isinstance(audio, str) and audio.startswith("http"): a = Audio.from_url(audio) return {"data": a.data, "audio_format": a.audio_format} ``` ```python @classmethod def from_url(cls, url: str) -> "Audio": response = requests.get(url) response.raise_for_status() ... ``` Two issues with this specific call: 1. **No timeout.** `requests.get(url)` has no `timeout=`, so a slow or intentionally-hanging endpoint blocks the request indefinitely. 2. **No URL/host validation.** Nothing stops the URL from pointing at `http://169.254.169.254/latest/meta-data/` (cloud instance metadata), a private-network service, or `localhost`. Compare `dspy.Image`, where `_encode_image_from_url` has the same missing-timeout issue, but at least the download only happens when the caller explicitly passes `download=True` — `Audio` has no equivalent gate at all, so any code path that builds an `Audio` from a plain string (e.g. an adapter coercing a field, or application code doing `dspy.Audio(some_value)` on data that ultimately traces back to a retrieved document or tool output) triggers the fetch with no way to opt out short of pre-filtering the string before it reaches the constructor. In an agent/RAG context where documents, tool results, or model output can influence what string ends up being passed into an `Audio`-typed field, this is a real internal-network-probing/DoS surface, not just a hardening nicety. ### Suggested fix - Add `timeout=` to both `requests.get` calls (`Audio.from_url` here and `_encode_image_from_url` in `image.py`, which has the same gap). - For `Audio`, either gate the auto-download behind an explicit flag the same way `Image` does with `download`, or at minimum validate the resolved URL/IP isn't pointing at a private/link-local/loopback address before fetching (the same class of check `Predict.load_state`'s SSRF fix in #9549 is doing for a different code path). ### Version Checked against current `main`.
8 条评论