anomalyco / anomalyco/opencode
`webfetch` description states an automatic HTTPS upgrade that does not happen
@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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.