Skip to content

fix(security): close SSRF redirect-guard bypass across all httpx download hooks - #56160

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-713c1408
Jul 1, 2026
Merged

fix(security): close SSRF redirect-guard bypass across all httpx download hooks#56160
teknium1 merged 1 commit into
mainfrom
hermes/hermes-713c1408

Conversation

@teknium1

@teknium1 teknium1 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes an SSRF redirect-guard bypass that affected every gateway/tool httpx download path, not just Slack.

Inside httpx.AsyncClient response event hooks, response.next_request is often None even for a genuine redirect (it's populated later by the redirect-following machinery). Guards written as if response.is_redirect and response.next_request: therefore never fire — a public URL that 302s to http://169.254.169.254/ gets followed anyway, defeating the pre-flight is_safe_url() check.

The fix resolves the redirect target from the Location header (via urljoin, so relative Locations work too), falling back to next_request only when no Location is present.

Changes

  • tools/url_safety.py: new shared helper redirect_target_from_response().
  • gateway/platforms/base.py: _ssrf_redirect_guard — shared image and audio download for all platforms.
  • tools/vision_tools.py: both download hooks.
  • plugins/platforms/slack/adapter.py: Slack adapter guard.
  • tests/tools/test_url_safety.py: regression tests for the helper (absolute/relative Location, non-redirect, next_request fallback, empty).

Root cause / why the original PR moved

@zapabob's PR #35940 fixed the right bug but targeted gateway/platforms/slack.py, whose per-URL download loop has since been refactored into the shared base.py helpers. Reconstructed onto the current sites and widened to the whole bug class (4 guard sites). The vulnerable pattern is now gone from all of them.

Note: tools/mcp_tool.py's cross-origin auth-header-strip hook has the same next_request-None blind spot but a different mechanism (it mutates the outgoing request's headers, which the Location value can't do). Left out of scope — different fix shape.

Validation

Live-reproduced with httpx.MockTransport (httpx 0.28.1):

OLD guard NEW guard
Redirect 302 → 169.254.169.254 private IP requested (bypassed) blocked, follow-up never issued
  • redirect_target_from_response resolves absolute + relative Location with next_request=None; returns None for non-redirects.
  • The actual base.py guard and vision helper path both raise before the private request is issued (verified through a real AsyncClient with redirect-following on).
  • tests/tools/test_url_safety.py: 127 passing. tests/gateway/test_slack.py + test_send_multiple_images.py: green.

Original fix by @zapabob (#35940); authorship preserved.

Infographic

SSRF redirect-guard bypass sealed

…load hooks

Inside httpx AsyncClient response event hooks, response.next_request is
often None even for a genuine redirect, so guards keyed on
`if response.is_redirect and response.next_request` silently never fire.
A public URL that 302s to http://169.254.169.254/ was followed anyway,
defeating the pre-flight is_safe_url() check.

Resolve the redirect target from the Location header (via urljoin, so
relative Locations work too), falling back to next_request only when no
Location is present. Extracted as tools.url_safety.redirect_target_from_response
and wired into every SSRF redirect guard:

  - gateway/platforms/base.py  (shared image + audio download for all platforms)
  - tools/vision_tools.py       (two download hooks)
  - plugins/platforms/slack/adapter.py

Original fix by @zapabob (PR #35940), which targeted the since-refactored
gateway/platforms/slack.py; reconstructed onto the current shared sites and
widened to the whole bug class.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/gateway Gateway runner, session dispatch, delivery tool/vision Vision analysis and image generation platform/slack Slack app adapter sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data P2 Medium — degraded but workaround exists labels Jul 1, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Reconstruction/widening of #35940 by @zapabob (original targeted the since-refactored slack.py) — related, not a duplicate. Widens the next_request-None SSRF blind spot fix to all four httpx download guard sites. Part of the SSRF-redirect-guard family: #55942 (Feishu/WeCom), merged #54470 (yuanbao), open cluster #22340/#43938.

@teknium1
teknium1 merged commit 500c2b1 into main Jul 1, 2026
31 checks passed
@teknium1
teknium1 deleted the hermes/hermes-713c1408 branch July 1, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/slack Slack app adapter sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/vision Vision analysis and image generation type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants