You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The webfetch tool (packages/opencode/src/tool/webfetch.ts) fetches URLs on the model's behalf. A few related problems:
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.
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.
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.
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.
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.
Activity
trickyfalcon commentedon Aug 23, 2026
Verified still-live at HEAD (2026-08-22):
webfetchfreely targets anyhttp(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 local127.0.0.1:<port>test server (both a direct fetch and a redirect-follow into it); the only guard proposal, PR #40851 (which addsisLoopbackHostname/REDIRECT_STATUSESand even updates webfetch's docs to claim metadata/loopback are blocked), appears never to have been merged — none of those symbols exist at currentmain.Impact is real for this project because
webfetchis 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)
github-actions commentedon Oct 22, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.