Skip to content

webfetch tool: SSRF via redirects, incomplete private-IP checks, unbounded response buffering #36376

Closed as not planned

Description

@sontani2017

The webfetch tool (packages/opencode/src/tool/webfetch.ts) fetches URLs on the model's behalf. A few related problems:

  1. No SSRF re-validation on redirects. The tool registry provides FetchHttpClient, and fetch follows redirects transparently — so even with host validation on the requested URL, a public URL can 302 to http://169.254.169.254/latest/meta-data/, localhost, or any RFC-1918 address and the body is fetched and returned to the model silently. Combined with always: ["*"] webfetch permission rules, a prompt-injected page can exfiltrate cloud metadata or hit internal services without the user ever seeing the internal URL.

  2. Private-address checks (where attempted) are easy to miss with string matching. Uncompressed IPv6 loopback (0:0:0:0:0:0:0:1), most of fe80::/10, NAT64/6to4-embedded IPv4, and reserved IPv4 ranges (TEST-NETs, 198.18.0.0/15, 192.0.0.0/24, multicast) need real address parsing, not prefix checks.

  3. The 5MB response cap is enforced only after buffering the entire body. content-length is checked when advertised, but a chunked/streaming response is fully read into memory via response.arrayBuffer before the size check, so a hostile server can make the process buffer arbitrarily large responses.

  4. extractTextFromHTML skip-tracking desyncs on void elements. onopentag increments the skip depth for every nested tag once inside a <script>/<style> region; a void element (e.g. <br>) that emits no matching close leaves the counter stuck and the rest of the document is silently dropped from format: "text" output.

Separately, the .opencode/tool/github-pr-search.ts plugin tool computes page = floor(offset/limit) + 1, so offset values that are not multiples of limit silently overlap or skip results; it also sends Content-Type: application/json on GET requests.

I have a PR ready that addresses these: manual redirect following with per-hop validation (redirect cap, non-http(s) refused), a real IPv6/IPv4 range parser, streaming body reads with early abort at the cap, tag-name-based skip tracking, and an OPENCODE_WEBFETCH_ALLOW_PRIVATE=1 escape hatch so fetching local dev servers still works.

Activity

  1. trickyfalcon commented on Aug 23, 2026

    @trickyfalcon

    Verified still-live at HEAD (2026-08-22): webfetch freely targets any http(s) URL server-side with no loopback/link-local guard, and (via the default-follow HTTP client) follows redirects into private hosts too. I reproduced the SSRF against a local 127.0.0.1:<port> test server (both a direct fetch and a redirect-follow into it); the only guard proposal, PR #40851 (which adds isLoopbackHostname / REDIRECT_STATUSES and even updates webfetch's docs to claim metadata/loopback are blocked), appears never to have been merged — none of those symbols exist at current main.

    Impact is real for this project because webfetch is a core tool invoked on any user/LLM-supplied URL (and recursively on links in fetched pages): a crafted link → internal service discovery + cloud metadata (169.254.169.254) exposure, with the response body returned straight into the model context.

    Happy to help land/widen #40851 or drop a minimal gist repro. Either way, happy to be credited as: Mo (@trickyfalcon, https://trickyfalcon.com)

  2. github-actions commented on Oct 22, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions