StacklokLabs / StacklokLabs/gofetch

SSRF: fetch tool dials arbitrary URLs with no address validation

Open
#156 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs-triage
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 calls http.NewRequest / httpClient.Do with no IP
    classification or scheme restriction. The only pre-check is robots.txt
    (politeness, not security). The existing //nolint:gosec on the Do call
    ("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): the http.Client is
    constructed with only a Timeout (and an optional proxy) — nothing on
    Transport.DialContext to 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-IPv6 fd00: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

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.

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.