iOfficeAI/OfficeCLI · error · ArgumentException
Refusing to fetch {what} from non-public address '{addr}' (h
Error message
Refusing to fetch {what} from non-public address '{addr}' (host '{host}'). Remote {what} sources must resolve to a public IP (SSRF protection). What it means
Thrown by SsrfGuard.CreateGuardedHandler's ConnectCallback when any resolved IP for the target host is not globally routable (IsPublicAddress false). This protects every remote image/file fetch from SSRF: it blocks loopback, RFC1918 private ranges, link-local incl. 169.254.0.0/16 cloud-metadata, CGNAT 100.64/10, 0.0.0.0/8, multicast/reserved, and IPv6 unique-local/link-local. The check runs in ConnectCallback on the real connect IP, closing the DNS-rebinding/TOCTOU window, and applies to every redirect hop.
Source
Thrown at src/officecli/Core/SsrfGuard.cs:49
/// ConnectCallback — rather than resolving the hostname up front — also
/// closes the DNS-rebinding/TOCTOU window, since the address we vet is the
/// address we connect to.
/// </summary>
/// <param name="what">Noun used in the refusal message, e.g. "image" or "file".</param>
public static SocketsHttpHandler CreateGuardedHandler(string what)
{
return new SocketsHttpHandler
{
AllowAutoRedirect = true,
MaxAutomaticRedirections = 10,
ConnectCallback = async (ctx, ct) =>
{
var host = ctx.DnsEndPoint.Host;
var addresses = await Dns.GetHostAddressesAsync(host, ct).ConfigureAwait(false);
foreach (var addr in addresses)
{
if (!IsPublicAddress(addr))
throw new ArgumentException(
$"Refusing to fetch {what} from non-public address '{addr}' (host '{host}'). " +
$"Remote {what} sources must resolve to a public IP (SSRF protection).");
}
var target = addresses.FirstOrDefault()
?? throw new ArgumentException($"Could not resolve host '{host}'.");
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
try
{
await socket.ConnectAsync(target, ctx.DnsEndPoint.Port, ct).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};View on GitHub (pinned to 1ced45e900)
Solutions
- Host the resource on a genuinely public IP / public CDN.
- For local files, use a local filesystem path instead of an http://localhost URL.
- Use a public tunnel (e.g. a real public ingress) to expose your local asset.
- If you control deployment and must allow an internal host, that is a posture change requiring explicit security review — do not weaken the guard in code without sign-off.
Example fix
// before picture = "http://localhost:8080/logo.png" // resolves to 127.0.0.1 -> SSRF refuse // after picture = "/abs/path/logo.png" // local file, no fetch at all // or picture = "https://cdn.example.com/logo.png" // public CDN
Defensive patterns
Strategy: try-catch
Validate before calling
// Best-effort pre-check (note: library validates the connect IP to avoid TOCTOU)
async Task<bool> ResolvesPublicOnly(string url)
{
var host = new Uri(url).Host;
try { return (await System.Net.Dns.GetHostAddressesAsync(host)).All(SsrfGuard.IsPublicAddress); }
catch { return false; }
} Try / catch
try { bytes = await client.GetByteArrayAsync(url); }
catch (System.ArgumentException ex) when (ex.Message.Contains("SSRF protection"))
{ /* URL resolves to a private/loopback address; use a public source or local path */ } Prevention
- Prefer local file paths over http:// for assets you control.
- Treat any URL originating from a document or agent instruction as untrusted.
- Never disable the guarded handler to 'make it work' locally.
When it happens
Trigger: An image=/picture= or data=/media=/model3d= URL whose host resolves to a non-public IP; a redirect chain whose final hop lands on an internal host; an agent-supplied URL crafted to reach 127.0.0.1, 169.254.169.254, or a 10/192.168/172.16-31 address.
Common situations: Pointing at a local dev server (localhost) during testing; an automation/agent tool consuming a URL from an untrusted document; a public domain that DNS-rebinds to an internal address; localhost tunneled services.
Related errors
- Could not resolve host '{host}'.
- Remote {what} at {url} exceeds {max / (1024 * 1024)} MB limi
- Remote file exceeds {SsrfGuard.MaxRemoteBytes / (1024 * 1024
- Remote image exceeds {SsrfGuard.MaxRemoteBytes / (1024 * 102
- body too large
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/0088b6f0d1640a0e.
Report an issue: GitHub.