anomalyco / anomalyco/opencode

`webfetch` description states an automatic HTTPS upgrade that does not happen

Open
#43,396 1 comment 0 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Aug 19, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Description

Summary

The webfetch tool description tells the model that HTTP URLs will be automatically upgraded to HTTPS. No upgrade is performed. An http:// URL is fetched over plaintext HTTP, including its query string.

This is a mismatch between a shipped tool description and the tool's behaviour. The description is sent to the model as the tool's description field, so it is the only account of the tool the model has.

This is filed as a correctness issue, not a security report. No compromise is claimed, and nothing here asserts that webfetch ought to perform the upgrade. Either implementing it or deleting the sentence makes the description true.

Version

  • Reproduced on 1.18.18 (latest release at time of filing)
  • Linux x64
  • Reproduced with github-copilot/gpt-5-mini, and with a scripted provider to remove model discretion

Reproduction

webfetch is pointed at a loopback plaintext HTTP server. A marker token is placed in the query string so that what reaches the server is visible rather than inferred.

python3 - <<'PY'
import http.server, socketserver, threading, json, os, subprocess, tempfile

hits = []

class Handler(http.server.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def log_message(self, *a): pass
    def do_GET(self):
        hits.append(self.path)
        body = b"SERVED_OVER_PLAINTEXT"
        self.send_response(200)
        self.send_header("Content-Type", "text/plain")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

server = socketserver.TCPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
port = server.server_address[1]

ws = tempfile.mkdtemp()
env = {k: v for k, v in os.environ.items() if not k.startswith("OPENCODE_")}
env["OPENCODE_CONFIG_CONTENT"] = json.dumps({"permission": {"webfetch": "allow"}})
url = "http://127.0.0.1:%d/secret?token=SENSITIVE_VALUE" % port

subprocess.run(
    ["opencode", "run", "--pure", "--format", "json", "--dir", ws,
     "--model", "github-copilot/gpt-5-mini",
     "Fetch this URL and tell me the response body: " + url],
    capture_output=True, text=True, env=env, timeout=240)

print("requests received by the plaintext server:", hits)
server.shutdown()
PY

Actual

tool=webfetch  status=completed  SERVED_OVER_PLAINTEXT

requests received by the plaintext server: ['/secret?token=SENSITIVE_VALUE']

The request arrives over plaintext HTTP with the query string intact. No upgrade is attempted, and nothing in the tool output mentions the transport.

Expected

Given the description, one of the following:

  • the request is made to https://127.0.0.1:PORT/..., or
  • the description does not claim an upgrade

Root cause

packages/opencode/src/tool/webfetch.ts

The scheme is inspected once, only to reject anything that is neither http:// nor https://:

if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) {
  throw new Error("URL must start with http:// or https://")
}

The URL is then used unchanged:

const request = HttpClientRequest.get(params.url).pipe(HttpClientRequest.setHeaders(headers))

There is no rewrite, no attempt at https:// first, and no notice in the output when a request is made over plaintext. https appears in this file only in the check above.

The sentence itself is in packages/opencode/src/tool/webfetch.txt:

  - HTTP URLs will be automatically upgraded to HTTPS

The description does reach the model

Because the whole issue is about what the model is told, the request to the provider was captured and inspected rather than inferred from description: DESCRIPTION in the source.

Pointing OpenCode at a local OpenAI-compatible endpoint and recording the posted body shows the agent turn carrying 10 tool definitions, webfetch among them, with a 750 character description containing the sentence verbatim:

tools carried: bash, edit, glob, grep, read, skill, task, todowrite, webfetch, write

  - The URL must be a fully-formed valid URL
  - HTTP URLs will be automatically upgraded to HTTPS
  - Format options: "markdown" (default), "text", or "html"

Impact

The description is the model's only account of what the tool does. Stated as a property of the tool, it makes an http:// URL look safe to pass along, so there is no reason for the model to rewrite the URL to https://, to prefer a secure source, or to mention the transport when reporting the result. The user is told the fetch succeeded and nothing indicates it travelled in the clear.

The scope of the claim is deliberately narrow: the description is false, and the request including its query string is sent unencrypted. Whether that costs a given user anything depends on what they fetch, and no specific exposure is claimed.

Model behaviour varies in whether the call is issued at all. gpt-5-mini chose webfetch unprompted for a loopback URL, while claude-haiku-4.5 and gemini-3.5-flash made no tool call for one. Supplying the call directly through a scripted provider removes that variance and shows the runtime never upgrades.

Suggested fix

Either is a small change, and they are alternatives rather than steps.

Delete the sentence. One line in webfetch.txt, and the description becomes accurate.

Or implement the upgrade. Rewrite an http:// URL to https:// before the request. If a fallback to plaintext is wanted when the secure attempt fails, say so in the tool output so the model can report it, since a silent fallback restores the same mismatch.

Plugins

No response

OpenCode version

No response

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.