Network Fetch Guard

Wallet-service endpoints fetch URLs that are effectively caller-supplied: issuer metadata, request_uri, credential_offer_uri, and token/credential endpoints all come from an offer or presentation request, not from a trusted admin. Without a guard, a malicious offer could point one of those fetches at an internal address — another tenant's service, an admin endpoint, or a cloud provider's metadata service — and have the Enterprise API make that request from inside your network. network-fetch-guard.conf controls what those fetches are allowed to reach. It is a base feature, so it is always loaded, not something you switch on in _features.conf.

Some categories are never reachable, regardless of configuration: link-local addresses (which includes 169.254.169.254, the cloud-metadata address used by AWS/GCP/Azure), multicast, and wildcard addresses. There is no setting that opens these up.

Example File

This is the shipped, most-restrictive default:

network-fetch-guard.conf
allowLoopback = false
allowPrivateNetworks = false

additionalTrustedHostSuffixes = []

Fields

allowLoopback

Boolean — defaults to false. Set true only if this deployment's issuer, verifier, and wallet services run as siblings on the same host (for example, local development, or a single all-in-one container) and the wallet legitimately needs to reach an issuer at 127.0.0.1 or localhost.

allowPrivateNetworks

Boolean — defaults to false. Set true only if those services legitimately reach each other over a private network (RFC 1918 IPv4, or an IPv6 unique-local address) — for example, sibling containers on the same Docker network, or pods in the same Kubernetes cluster.

Both flags are process-wide: turning one on to fix one tenant's topology reopens that whole address range to every tenant's caller-supplied URLs on this process — including the internal targets this guard exists to block. On a shared multi-tenant deployment, prefer additionalTrustedHostSuffixes below instead.

additionalTrustedHostSuffixes

Array of strings, optional — defaults to an empty list. Hostnames listed here (and any subdomain of them) are allowed to resolve to a loopback or private address, without opening up allowLoopback / allowPrivateNetworks for anyone else.

This exists for one specific situation: DNS hairpinning. Every organization is hosted at {organizationId}.{baseDomain} (baseDomain is set in enterprise.conf), and that hostname is always trusted automatically — you don't need to list it here. But some DNS resolvers optimize a request for a name that points back at the resolver's own network by handing back a loopback or private address instead of the real public one, purely as a routing shortcut. That means a wallet fetching its own organization's issuer, at its own public hostname, can sometimes see that hostname resolve to 127.0.0.1 or a private IP — which looks identical, at the IP level, to an attacker trying to reach something internal. The guard can't tell those two cases apart from the IP address alone, so it trusts the hostname itself instead, for names you control.

Use additionalTrustedHostSuffixes only if you need to extend that same trust to a hostname beyond your own baseDomain — most deployments should leave it empty.

Trusting a hostname this way never overrides the always-blocked categories above. Even a trusted hostname cannot be used to reach the cloud-metadata address, for example.

Last updated on November 3, 2026