StacklokLabs / StacklokLabs/gofetch
SSRF: fetch tool dials arbitrary URLs with no address validation
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 27
- Forks
- 2
- Avg merge
- 3d 7h
- Merged PRs (30d)
- 3
Description
Summary
The fetch tool dials the model-supplied URL directly with no validation of the
resolved address. A caller can point it at loopback, link-local, private-range,
or cloud instance-metadata endpoints (e.g. http://169.254.169.254/latest/meta-data/)
and it will fetch them (CWE-918).
Where
pkg/fetcher/fetcher.go(fetchURL, ~line 77-90): builds the request straight
from the input URL and callshttp.NewRequest/httpClient.Dowith no IP
classification or scheme restriction. The only pre-check isrobots.txt
(politeness, not security). The existing//nolint:gosecon theDocall
("This is a fetch server; fetching user-provided URLs is its core purpose")
acknowledges the behavior but doesn't mitigate it.pkg/server/server.go(NewFetchServer, ~line 36-49): thehttp.Clientis
constructed with only aTimeout(and an optional proxy) — nothing on
Transport.DialContextto gate the resolved peer address.- The toolhive registry manifest for this server also declares
"network": {"outbound": {"allow_port": [443], "insecure_allow_all": true}},
so there's no compensating network-level restriction either. Note a
NetworkPolicy alone wouldn't close this anyway — the cloud-metadata route
isn't pod-to-pod traffic a NetworkPolicy governs.
Suggested fix
Add a Control hook (or a custom net.Dialer) on the http.Client's
Transport that classifies the resolved peer IP before connecting, blocking:
- loopback (unless explicitly opted in for local/dev use)
- link-local unicast (covers
169.254.0.0/16, including the AWS/GCP/Azure/Oracle
metadata endpoint) - multicast
- well-known cloud metadata addresses outside the link-local range that some
policies would otherwise miss (AWS IMDSv2-over-IPv6fd00:ec2::254, Alibaba
100.100.100.200) - optionally private/RFC1918 ranges, if this server is ever expected to run
somewhere those addresses are untrusted
This should run against the resolved address, not just the literal host in
the URL, to defend against DNS rebinding.
Context
Found while evaluating this server as a fetch-tool backend for
stacklok/atrium. Happy to contribute the
fix if useful — Atrium already carries a small, tested internal/ssrf package
implementing exactly this policy that could serve as a reference.
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.
Research direction
Start with pkg/fetcher/fetcher.go around fetchURL and pkg/server/server.go around NewFetchServer, then trace how the HTTP transport resolves and dials peers. The work is complete when resolved loopback, link-local, multicast, metadata, and any selected private ranges are blocked while permitted fetches still work, including protection against DNS rebinding.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100