Skip to content

Refuse feed connections to non-public addresses - #176

Open
mishan wants to merge 3 commits into
jhalter:masterfrom
mishan:feed-redirect-private-addresses
Open

mishan wants to merge 3 commits into
jhalter:masterfrom
mishan:feed-redirect-private-addresses

Conversation

@mishan

@mishan mishan commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

The feed docs say the URL must be public, but nothing enforces it, and redirects are followed to anything http(s): a feed host can send the server's fetch to loopback, the LAN or 169.254.169.254, and from https down to http.

This checks each direct connection's resolved address at dial time (net.Dialer.ControlContext), so neither the URL's host name nor a redirect can reach a loopback, private, link-local or carrier-grade NAT address, and a later DNS answer can't change that. It also refuses a redirect from https to http.

Proxies. A proxy from HTTP_PROXY/HTTPS_PROXY is the operator's choice and is often on the local network, so connections to it aren't checked. Through a proxy the server can't see what a host name resolves to, so it refuses only a URL that names a non-public address directly, such as http://169.254.169.254/, and leaves the rest to the proxy's own rules.

Both behaviors are documented in docs/feed-backed-news.md.

Tests:

  • a table for the address check;
  • the configured URL and a redirect, each pointed at a local httptest server, both refused before any request reaches it;
  • a fetch through a proxy on 127.0.0.1 succeeding, while a literal metadata address is refused without reaching the proxy;
  • the https to http redirect refused.

The feed URL is documented as public, but nothing enforced it, and
redirects were followed anywhere http(s) goes: a feed host could send the
server's fetch to loopback, the local network or 169.254.169.254, and
from https down to http.

Check every direct connection's resolved address at dial time, so
neither the URL's host name nor a redirect reaches a loopback, private,
link-local or carrier-grade NAT address, and refuse a redirect from
https to http.

A proxy from HTTP_PROXY or HTTPS_PROXY is the operator's choice and is
often on the local network, so connections to it are not checked.
Through it the server cannot see what a host name resolves to, so only
an address written into the URL is refused.
@jhalter

jhalter commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Is there anything necessarily bad or dangerous with a feed pointing to a private URL? The feed connection is purely from the server to the feed, so I don't know that a private feed exposes anything necessarily. I'm curious what your thoughts are on this. The docs should be updated for sure though, thanks for noticing.

A feed whose configured URL is on a private address is the operator's
choice and exposes nothing, so it is fetched as configured, redirects
included. What stays refused is a public feed reaching the local
network: every connection its fetch makes, redirects and all, must be to
a public address, and through a proxy a redirect to a literal private
address is refused before the proxy sees it.

A host counts as private when it is a literal non-public address or
resolves only to them; one that does not resolve, as behind a proxy,
counts as public.
@mishan

mishan commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

Good point! I may have read a little much into the docs when implementing it this way. Knowingly pointing a feed to a private URL is fine. My concern is more with a server operator pointing to a public URL that could then issue a redirect to a host on the LAN of the mobius instance.

I've updated the code to allow pointing to feeds at private URLs, but public feed redirects are checked to ensure that nothing malicious is being attempted

A feed's host name was looked up once to judge the feed private and
again to connect to it. A name server could answer loopback to the
first and its public address to the second, and the redirect that
server then sent toward 169.254.169.254 was followed unchecked.

The judging lookup's answers are now where connections to that host
go. Behind a proxy, which resolves names out of sight, only an address
written in the URL makes a feed private.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants