Class SafeAddressGuard

java.lang.Object
io.meeds.commons.http.SafeAddressGuard

public class SafeAddressGuard extends Object
Decides which URLs the platform may be made to read on a user's or a sender's behalf, and which addresses it may connect to.

Why. A URL the server fetches for someone else — a subscribed calendar, a sender's logo, an image an agent was given — would otherwise make the platform a client of whatever sits on its own network: an administration console on loopback, a database's HTTP port on a private address, the cloud metadata endpoint handing out credentials.

What it decides. The URL's shape — an allowed scheme, no credentials, a host, an allowed port, and the address itself when the host is an IP literal (checkTarget(java.net.URI)) — and the addresses its host resolves to (resolveAllowed(java.lang.String)): loopback, link-local (the metadata address 169.254.169.254 among them), private, carrier-grade NAT, multicast, broadcast and reserved, unspecified, IETF assignments, benchmarking, IPv6 unique-local and site-local, and each of those spelled as an IPv4 address inside an IPv6 one (mapped, compatible, NAT64, 6to4) are refused unless the policy allows internal addresses.

Where the address is checked. resolveAllowed(java.lang.String) is the resolver of SafeHttpFetcher's connection manager (GuardedDnsResolver), so it judges the very addresses the client then connects to, for every connection it opens — every redirect hop included. A name that resolved to a public address when it was first seen and to a private one when the connection opens (DNS rebinding) is refused at that connection, not trusted from an earlier answer. An IP literal is judged twice: by checkTarget(java.net.URI) before the request, so its refusal does not depend on how the HTTP client resolves a literal host, and by the resolver when the connection opens. What stays outside its reach: a public server that itself relays the request inward, and the time a refused or unreachable answer takes, which says something about a public host.

  • Field Details

    • MAX_URL_LENGTH

      public static final int MAX_URL_LENGTH
      Longest URL accepted.
      See Also:
  • Constructor Details

    • SafeAddressGuard

      public SafeAddressGuard(SafeFetchPolicy policy)
      The guard of a policy.
      Parameters:
      policy - what may be read
  • Method Details

    • normalize

      public URI normalize(String url) throws SafeFetchException
      Reads a URL as it was given: trimmed, the fragment dropped, then checked by checkTarget(java.net.URI). Nothing is resolved here.
      Parameters:
      url - the URL as given
      Returns:
      the URL to read
      Throws:
      SafeFetchException - with the reason it is refused
    • checkTarget

      public URI checkTarget(URI uri) throws SafeFetchException
      Checks the shape of a URL about to be read — the one given, or the target of a redirect: absolute, an allowed scheme, no credentials, a host, an allowed port, not too long, no white space or control character; and, when the host is an IP literal, the address it names, under the same rules as resolveAllowed(java.lang.String). A host name is not resolved here.
      Parameters:
      uri - the URL
      Returns:
      the URL with its scheme lower-cased
      Throws:
      SafeFetchException - with the reason it is refused
    • resolveAllowed

      public InetAddress[] resolveAllowed(String host) throws UnknownHostException
      Resolves a host and refuses it when ANY of its addresses is one the platform must not reach — a name answering one public and one loopback address is a name reaching loopback. This is the HTTP client's resolver: its answer is what the client connects to.
      Parameters:
      host - the host, brackets of an IPv6 literal allowed
      Returns:
      the addresses, every one allowed
      Throws:
      RefusedAddressException - when an address is refused
      UnknownHostException - when the host resolves to nothing
    • isBlocked

      public static boolean isBlocked(InetAddress address)
      Whether one address is outside what a fetched URL may point at.

      The JDK predicates cover the any-local, loopback, link-local, site-local (RFC 1918 in IPv4, fec0::/10 in IPv6) and multicast addresses; the byte inspection covers the rest, and the IPv4 address an IPv6 address may carry, which is how ::ffff:127.0.0.1 or 64:ff9b::a9fe:a9fe would otherwise walk past a loopback or metadata check.

      Parameters:
      address - one resolved address
      Returns:
      true when the platform must not reach it