Fetches refuse internal addresses unless fetch_internal lists them (#320)

ipx's clients had no destination policy and followed redirects
anywhere. Adding a feed returned the status or first line of whatever
answered at the address; an enclosure declared as audio was downloaded
whole from any URL and served back through /media; /api/art fetched
any image a feed named. Feeds come from publishers and anyone signed
in, so loopback, the Docker network and the LAN were all reachable.

ipx-feed's guard is a connector layer: after the name resolves and the
connection opens, before the request is written, it looks at the
peer's address and refuses anything off the public internet unless
config.toml's [general] fetch_internal lists it. Being on the
connection, it covers every request on ctx.client and feed_client,
each redirect hop, IP literals (which never reach a DNS resolver) and
a name that resolves differently the second time. reqwest's rustls
connection reports the peer address; a test run by hand checks HTTPS
gets through, since were it lost every https fetch would be refused.

The directory is on the server's own network by design
(http://ipx-directory:8100), so it moves to local_client, unguarded,
set by an admin. The browser suite and load tests serve their feeds on
loopback and list 127.0.0.1.

Not covered: librqbit's own connections (trackers, peers), which do
not go through these clients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 13:27:40 +00:00
parent 6d71613e19
commit a20062d0f7
12 changed files with 311 additions and 3 deletions

View File

@@ -134,6 +134,8 @@ download_dir = "${root}/downloads"
socket = "${root}/ipx.sock"
schedule = "every 60m"
max_new_per_check = 1
# The generated feeds are on loopback, which ipx refuses otherwise (#320).
fetch_internal = ["127.0.0.1"]
[torrent]
enabled = false

View File

@@ -32,6 +32,8 @@ download_dir = "${path.join(root, 'downloads')}"
socket = "${path.join(root, 'ipx.sock')}"
schedule = "every 60m"
max_new_per_check = 1
# The fixture feeds are on loopback, which ipx refuses otherwise (#320).
fetch_internal = ["127.0.0.1"]
directory_url = "${DIRECTORY}"
directory_token = "${DIRECTORY_TOKEN}"
directory_share = true