fix(security): guard provider image URL downloads - #45537
Conversation
|
LGTM — this patches the #44728 SSRF gap on provider image-URL downloads ( One honest caveat worth a comment in the code: this is still vulnerable to DNS rebinding (TOCTOU between the pre-flight resolve and the socket connect) — that's a known limitation of pre-flight |
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Security hardening: guards save_url_image() against SSRF via redirect following. Implements redirect loop detection (max 10 hops) and validates each URL via is_safe_url() before each request. Uses allow_redirects=False to intercept redirects manually.
Security: Solid — SSRF defense-in-depth. is_safe_url from tools.url_safety is the right check. Redirect limit prevents infinite loop attacks.
Code Quality: Clean implementation. Minor note: the if response is None check after the loop is belt-and-suspenders (the loop always sets response before break), but harmless.
Reviewed by Hermes Agent (cron batch)
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing the provider image-download SSRF path. The premise remains valid on current main: agent/image_gen_provider.py:293 sends the provider-returned URL directly to requests.get().
Problems
agent/image_gen_provider.py:249does not actually handle a malformed redirect withoutLocation. In Requests,Response.is_redirectrequires both a redirect status and aLocationheader. A302without that header therefore takes thebreak, whileraise_for_status()accepts 3xx responses, and the body is subsequently written at line 282. The intended failure path at lines 251-254 is unreachable for this case. The added tests attests/agent/test_save_url_image.py:68-88cover only redirects withLocation.
Suggested changes
- Detect redirect statuses separately from
response.is_redirect; close and reject any redirect-status response lackingLocation, then add a real-server regression test proving no cache file is created.
Automated hermes-sweeper review.
| stream=True, | ||
| allow_redirects=False, | ||
| ) | ||
| if not response.is_redirect: |
There was a problem hiding this comment.
Response.is_redirect is false for a 302 without Location, so this branch breaks before the missing-Location check. Requests' raise_for_status() accepts 3xx responses, letting that response body reach the cache writer. Detect redirect status independently, then close and reject a redirect response that lacks Location; add a regression test.
Summary
allow_redirects=False, re-checking eachLocationbefore requesting itLocationor redirect budget exhaustion so redirect bodies are not cached as imagesFixes #44728.
Supersedes the remaining redirect-safety review gap on #44743.
Validation
uv run pytest -q tests\agent\test_save_url_image.py -o addopts=-> 12 passeduv run ruff check agent\image_gen_provider.py tests\agent\test_save_url_image.py-> passeduv run python -m py_compile agent\image_gen_provider.py tests\agent\test_save_url_image.py-> passedgit diff --check-> passed