HTTP GET via Req, returning the body as text.
Arguments:
url(required) —http://orhttps://only; other schemes rejected.timeout_ms(optional, default 10_000).
Response body capped at 1 MB. Redirects followed up to 5 hops.
SSRF guard
Every fetch target — the initial URL and every redirect hop — is validated
before a request is made: non-http(s) schemes are rejected, and hosts that
are (or resolve to) loopback, private, link-local, CGNAT, or otherwise
non-public addresses are refused (see ExAthena.Net.blocked_host?/1). This
applies regardless of confinement, so the default library configuration
cannot be steered to http://169.254.169.254/ (cloud metadata),
localhost, or internal services — including via a public URL that 302s to
one. Redirects are followed manually (Req auto-redirect is disabled) so each
Location target is re-validated before it is fetched.
Hosts that legitimately need local fetching (docs served from a dev server,
a localhost API) opt out with allow_local_hosts: true on ExAthena.run/2
(threaded into ctx.assigns[:allow_local_hosts]), which disables the host
block for that run; the scheme check still applies.
Known limit (documented rather than over-engineered): hostname validation resolves DNS at check time, but the HTTP client resolves again when connecting, so a resolver flipping records between the two lookups (DNS-rebinding TOCTOU) can still slip through. Deployments that must defend against that should pin egress at the network layer.
This is deliberately minimal — it's here so agents can fetch documentation
pages, not to replace a full HTTP client. For richer access (auth headers,
POST bodies, etc.), implement a custom tool that wraps Req directly.