Skip to content

EgressAddressValidator

Namespace AgentPrism · Assembly AgentPrism.Core.dll

The single place that decides whether an outbound target address is safe.

public static class EgressAddressValidator

objectEgressAddressValidator

object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

AgentPrism reaches the network from three surfaces — webhook delivery, MCP server connections and model provider calls. Each one accepts an address that ultimately came from a user, so each one is an SSRF surface: left uncontrolled, they become a means of reaching internal-network services, including cloud metadata endpoints (169.254.169.254), which often hand out unauthenticated temporary credentials.

The rules live here, once. Three copies of the same resolve-and-check loop end the same way: one gets fixed and two go stale.

IsAllowedTarget(IPAddress, EgressAddressPolicy)

Section titled “ IsAllowedTarget(IPAddress, EgressAddressPolicy)”

Reports whether an address may be connected to under a policy.

public static bool IsAllowedTarget(IPAddress address, EgressAddressPolicy policy)

address IPAddress

The resolved address.

policy EgressAddressPolicy

The rules of the calling surface.

bool

true if a connection to the address may be opened.

ArgumentNullException

address is null.

Reports whether an IP address falls within a private or local range.

public static bool IsPrivate(IPAddress address)

address IPAddress

The address.

bool

true if the address falls within a private range.

IPv4 ranges covered: 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16 (cloud metadata!), 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 (CGNAT), and everything from 224.0.0.0 up (multicast and reserved).

IPv6 ranges covered: ::1, ::, fc00::/7, fe80::/10, site-local, and multicast.

An IPv6 address that embeds an IPv4 address is reduced to that IPv4 address first and then judged by the IPv4 rules. Skipping this is a classic bypass: ::ffff:169.254.169.254, ::169.254.169.254, 64:ff9b::a9fe:a9fe and 2002:a9fe:a9fe:: all reach the metadata endpoint through a host that has the matching translation or relay configured. See EgressAddressValidator.TryGetEmbeddedIPv4 for the forms handled.

ResolveAndValidateAsync(string, EgressAddressPolicy, CancellationToken)

Section titled “ ResolveAndValidateAsync(string, EgressAddressPolicy, CancellationToken)”

Resolves a host name and validates every address it resolves to.

public static ValueTask<EgressAddressVerdict> ResolveAndValidateAsync(string host, EgressAddressPolicy policy, CancellationToken cancellationToken = default)

host string

The host name or IP literal.

policy EgressAddressPolicy

The rules of the calling surface.

cancellationToken CancellationToken

The cancellation token.

ValueTask<EgressAddressVerdict>

The verdict, carrying every resolved address when allowed.

If any resolved address is rejected, the target is rejected. Picking the first suitable address instead would let an attacker publish a name that resolves to one public and one private address, and reach the private one on the next attempt.

TryGetEmbeddedIPv4(IPAddress, out IPAddress)

Section titled “ TryGetEmbeddedIPv4(IPAddress, out IPAddress)”

Extracts the IPv4 address an IPv6 address embeds, if it embeds one.

public static bool TryGetEmbeddedIPv4(IPAddress address, out IPAddress embedded)

address IPAddress

The IPv6 address.

embedded IPAddress

The embedded IPv4 address, when the method returns true.

bool

true if the address carries an embedded IPv4 address.

Forms handled, each of which routes to the embedded IPv4 address on a host with the matching translation configured:

  • IPv4-mapped, ::ffff:0:0/96::ffff:1.2.3.4.
  • IPv4-translated, ::ffff:0:0:0/96 (RFC 2765).
  • IPv4-compatible, ::/96 (RFC 4291, deprecated) — ::1.2.3.4.
  • NAT64 well-known prefix, 64:ff9b::/96 (RFC 6052).
  • 6to4, 2002::/16 (RFC 3056) — the IPv4 address sits in the second and third groups.

Teredo (2001::/32) is deliberately not decoded: its client address is obfuscated and its server address is not the destination.

ValidateAddresses(IPAddress[], EgressAddressPolicy)

Section titled “ ValidateAddresses(IPAddress[], EgressAddressPolicy)”

Validates every address a host resolved to.

public static EgressAddressVerdict ValidateAddresses(IPAddress[] addresses, EgressAddressPolicy policy)

addresses IPAddress[]

The resolved addresses.

policy EgressAddressPolicy

The rules of the calling surface.

EgressAddressVerdict

The verdict, carrying the addresses unchanged when allowed.

If any address is rejected, the whole target is rejected. Picking the first suitable address instead would let an attacker publish a name that resolves to one public and one private address, and reach the private one on a later attempt.

ArgumentNullException

addresses is null.

ValidateLiteral(Uri, EgressAddressPolicy)

Section titled “ ValidateLiteral(Uri, EgressAddressPolicy)”

Validates a target whose host is written as an IP literal, without resolving DNS.

public static string? ValidateLiteral(Uri target, EgressAddressPolicy policy)

target Uri

The target address.

policy EgressAddressPolicy

The rules of the calling surface.

string?

The rejection reason, or null when the target is acceptable.

Used at save time, in the HTTP endpoints. DNS is deliberately not resolved there: it would slow the save down, and a target that is unreachable at that moment — or a name that does not resolve yet — is not by itself an error. A host name that resolves to a private address still gets rejected, but at connection time, by EgressSocketGuard. That check is the one that cannot be evaded; this one only turns the obvious mistake into an immediate 400 instead of a silent failure hours later.

ArgumentNullException

target is null.