Skip to content

fetch_url() lacks private IP blocklist and redirect limit, enabling SSRF #101

Closed

Description

@knight22-21

Summary

fetch_url() in tools/web_tools.py uses httpx.get(..., follow_redirects=True) with no validation of the target URL or its resolved IP address. This makes the tool exploitable as a Server-Side Request Forgery (SSRF) vector: a malicious prompt can instruct the agent to fetch http://169.254.169.254/ (AWS instance metadata), http://localhost:6379/ (local Redis), http://[::1]:8080/admin, or any other internal service accessible from the machine running DevAgent.

Because follow_redirects=True has no hop limit configured, a chain of redirects from an external URL to an internal address (open redirect → internal service) also bypasses any naive URL-prefix check.

Steps to Reproduce

  1. Prompt the agent: "Please fetch http://169.254.169.254/latest/meta-data/ and show me the result."
  2. Observe fetch_url makes the request and returns the cloud provider's instance metadata (IAM role credentials, etc.) to the LLM conversation.

Root Cause

tools/web_tools.py around line 155:

resp = httpx.get(
    url,
    headers={"User-Agent": _DEFAULT_UA},
    timeout=20.0,
    follow_redirects=True,   # no redirect limit; no IP validation
)

No private-IP blocklist is applied before or after DNS resolution, and redirects are followed transparently.

Fix Direction

  • After DNS resolution (using socket.getaddrinfo) but before the request, reject URLs that resolve to:
    • 127.0.0.0/8, ::1 (loopback)
    • 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (RFC-1918 private)
    • 169.254.0.0/16 (link-local / cloud metadata)
    • fc00::/7 (IPv6 ULA)
  • Set max_redirects=5 (httpx default is 20).
  • Re-validate the resolved IP after each redirect step.
  • Add a FETCH_URL_ALLOWED_SCHEMES = {"http", "https"} guard to block file://, ftp://, etc.
  • Add tests that assert fetch_url({"url": "http://169.254.169.254/"}) and fetch_url({"url": "http://localhost:6379/"}) return an error string rather than making the request.

Activity

  1. mikemikimike commented on Oct 17, 2026

    @mikemikimike
    Contributor

    PR #106 implements the SSRF protections and adds regression coverage for metadata and private addresses, IPv6 embedded addresses, DNS pinning, redirects, proxy bypass, and connection fallback. Local pre-PR validation passed on Python 3.12 and 3.13 (842 passed, 1 skipped; Ruff and package build passed). The upstream CI and Benchmark Canary runs currently report action_required and contain no jobs, so they have not executed yet. Could a maintainer approve the fork workflow runs so they can proceed? I will address any resulting CI feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions