fix(yuanbao): block redirect-based SSRF in download_url - #56927
fix(yuanbao): block redirect-based SSRF in download_url#56927solyanviktor-star wants to merge 1 commit into
Conversation
|
Verified the core claim empirically and it's worse than "frequently None" — as far as I can tell it's always None in this context: in httpx's redirect loop, response event hooks run before Also confirmed LGTM — this deserves priority attention given the guard it replaces is dead code. |
|
Thanks for isolating the missed Yuanbao path. The premise holds on current The proposed use of No blocking correctness or scope issues found. This is an automated hermes-sweeper review. |
download_url()'s redirect guard keyed on response.next_request, which is None inside an httpx AsyncClient response event hook even for a genuine 302. The guard therefore never fired, so a public URL that passed the is_safe_url() pre-flight could 302-redirect to http://169.254.169.254/ (cloud metadata) or any private/internal address and httpx would follow it. Resolve the hop from the Location header via redirect_target_from_response, mirroring gateway/platforms/base.py::_ssrf_redirect_guard -- the same fix one path it missed. Add a regression test driving a 302 -> metadata through the guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Rebased onto current main to resolve the conflict with 42626da ( Honest severity re-assessment after that commit: connect-time IP validation now blocks the dial to a private address even when the response hook never fires, so this is no longer a live SSRF bypass. What this PR still fixes is the application-layer redirect guard itself, which remains dead code on main ( |
ced1a84 to
9e48e16
Compare
What does this PR do?
gateway/platforms/yuanbao_media.py::download_urlfetches model-supplied and inbound URLs server-side. It has anis_safe_url()pre-flight, but its per-hop redirect guard was inert:Inside an
httpx.AsyncClientresponse event hook,response.next_requestisNoneeven for a genuine 302 (it is populated later by the redirect-following machinery). So the guard condition was always falsy and never validated the redirect target. A public URL that passes the pre-flight check can therefore 302-redirect tohttp://169.254.169.254/(cloud metadata / IMDS) or any private address, and httpx (follow_redirects=True) follows it — a redirect-based SSRF.This is the same class that #56160 (merged) fixed across the other httpx download hooks by resolving the hop from the
Locationheader viaredirect_target_from_response;yuanbao_media.pywas the one path that migration missed. (The brokennext_requestguard here was introduced by #54470.)Related Issue
No open issue — found while auditing the httpx redirect guards after #56160. N/A.
Type of Change
Changes Made
gateway/platforms/yuanbao_media.py:_redirect_guardnow resolves the redirect target withredirect_target_from_response(response)(reads theLocationheader,urljoin-resolving relative Locations) instead of the always-Noneresponse.next_request, then rejects it withis_safe_url. This mirrorsgateway/platforms/base.py::_ssrf_redirect_guard.tests/gateway/test_yuanbao_media_ssrf.py: addedtest_redirect_to_metadata_blocked— drives a 302 whoseLocationpoints at169.254.169.254withnext_request=None(the real in-hook shape) through the registered guard and assertsValueError. Fails on the old guard, passes on the fix.How to Test
The new test reproduces the bug: with the previous
next_request-based guard, a 302 to the metadata endpoint (withnext_request=None, as httpx leaves it inside the hook) is not blocked; with the fix it raisesValueError: Blocked redirect to private/internal address.Checklist
Code
fix(yuanbao): ...)pytest tests/gateway/test_yuanbao_media_ssrf.py -q→ 6 passed); the full suite runs in CIDocumentation & Housekeeping
cli-config.yaml.example— N/ACONTRIBUTING.md/AGENTS.md— N/AScreenshots / Logs
See How to Test above.
🤖 Generated with Claude Code