Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/7c71603a25d84f372f2275dd570e99d4 to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/7c71603a25d84f372f2275dd570e99d4 to your computer and use it in GitHub Desktop.
hermes-agent: xAI image generation provider performs an unguarded host-side fetch of provider-controlled image URLs

Advisory Details

Title: xAI image generation provider performs an unguarded host-side fetch of provider-controlled image URLs

Description:

Summary

Hermes' image_generate tool allows the configured xAI image-generation provider to return a data[0].url value that Hermes then downloads from the host with a raw requests.get(). Because this second-hop fetch does not use the repository's existing URL safety checks or redirect-target revalidation, a malicious or compromised provider, relay, or upstream response path can cause the Hermes host to request attacker-chosen URLs. In practical deployments this can expose loopback services, internal HTTP endpoints, or cloud metadata targets reachable from the Hermes process.

Details

The public entrypoint is the image_generate tool in tools/image_generation_tool.py. When image_gen.provider is configured to xai, _handle_image_generate() dispatches the request into the plugin provider path:

def _handle_image_generate(args, **kw):
    prompt = args.get("prompt", "")
    ...
    dispatched = _dispatch_to_plugin_provider(prompt, aspect_ratio)

At the xAI provider layer, the response parser accepts either inline base64 image data or a remote image URL. If data[0].url is present, the provider hands that URL to save_url_image():

first = data[0]
b64 = first.get("b64_json")
url = first.get("url")
...
elif url:
    saved_path = save_url_image(url, prefix=f"xai_{model_id}")

The sink is in agent/image_gen_provider.py. save_url_image() performs a direct network fetch from the Hermes host:

response = requests.get(url, timeout=timeout, stream=True)
response.raise_for_status()

There is no call to tools.url_safety.is_safe_url, no equivalent async guard, and no redirect-target revalidation before dereferencing the provider-controlled URL. This creates an SSRF-style trust boundary failure on the provider response path rather than on a user-supplied URL field.

I verified the issue through the real Hermes tool interface, not a unit-test-only harness. The exploit path was:

model_tools.handle_function_call("image_generate", ...) -> configured xai provider -> provider response data[0].url -> save_url_image(url) -> requests.get(url)

The attached integration PoC uses a localhost xAI-compatible stub service. In the control case the stub returns b64_json and Hermes does not perform a second-hop fetch. In the vulnerable case the stub returns data[0].url pointing at /canary.png, and Hermes performs the fetch from the host. The observation logs show:

  • Control: provider_hits=1, canary_hits=0
  • Vulnerable case: provider_hits=1, canary_hits=1

PoC

Prerequisites

  • A local checkout of NousResearch/hermes-agent
  • Python environment able to import Hermes from the repository root
  • No special credentials beyond setting XAI_API_KEY to any non-empty value for provider availability checks
  • The PoC scripts below downloaded from the linked secret gists

Reproduction Steps

  1. Download the provider stub from: stub_xai_server.py

  2. Download the vulnerable-path reproducer from: verification_test.py

  3. Download the control script from: control-normal-b64.py

  4. Place the three scripts in the same directory and run the control case first from the repository root: python3 llm-enhance/cve-finding/similar/Injection/Advisory-GHSA-qxgf-hmcj-3xw3-image-gen-provider-save-url-image-exp/control-normal-b64.py

  5. Confirm the control case prints: [CONTROL-PROTECTED]

  6. Run the vulnerable case: python3 llm-enhance/cve-finding/similar/Injection/Advisory-GHSA-qxgf-hmcj-3xw3-image-gen-provider-save-url-image-exp/verification_test.py

  7. Confirm the vulnerable case prints: [DEFECT-CONFIRMED-WITH-LIMITATIONS]

  8. Inspect the generated logs:

    • control_observation.log should contain provider_hits=1 and canary_hits=0
    • verification_observation.log should contain provider_hits=1 and canary_hits=1

Log of Evidence

Control run:

mode=control-b64
provider_hits=1
canary_hits=0
provider_requests=[{"model": "grok-imagine-image", "prompt": "ssrf-control-b64", "aspect_ratio": "1:1", "resolution": "1k"}]
rc=0

Vulnerable run:

mode=vuln-url
provider_hits=1
canary_hits=1
provider_requests=[{"model": "grok-imagine-image", "prompt": "ssrf-vuln-url", "aspect_ratio": "1:1", "resolution": "1k"}]
rc=0

Impact

This is an SSRF-style host-side fetch issue on the image provider response path. Any Hermes deployment that enables the xai image-generation provider is affected if the provider endpoint, a relay in front of it, or an otherwise trusted upstream can be induced to return attacker-chosen image URLs. The impacted asset is the Hermes host's network position. An attacker can potentially use Hermes to request loopback services, internal HTTP endpoints, cloud metadata resources, or other network targets reachable from the process but not directly reachable from the attacker.

Affected products

  • Ecosystem: pip
  • Package name: hermes-agent
  • Affected versions: <= 0.16.0
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

Weaknesses

  • CWE: CWE-918: Server-Side Request Forgery (SSRF)

Occurrences

Permalink Description
https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/tools/image_generation_tool.py#L1033-L1043 Public image_generate handler that dispatches the user request into the configured plugin provider path.
https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/plugins/image_gen/xai/init.py#L267-L294 xAI provider response parser that accepts data[0].url and forwards it to save_url_image().
https://github.com/NousResearch/hermes-agent/blob/3c231eb3979ab9c57d5cd6d02f1d577a3b718b43/agent/image_gen_provider.py#L207-L229 Vulnerable downloader that performs a raw requests.get(url, timeout=timeout, stream=True) on the provider-controlled URL without SSRF validation or redirect-target revalidation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment