You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(egress): judge an IPv6 address by the IPv4 it carries, whatever the wrapper
A second audit pass found the transition schemes the first one missed. Every one
of these is a real route to an IPv4 destination, and the guard was judging the
wrapper instead:
- RFC 3056 6to4 (`2002:a9fe:a9fe::`) and RFC 5214 ISATAP (`fe80::5efe:169.254.169.254`)
now fold to the IPv4 they name, so they read as the metadata endpoints they are.
- RFC 4380 Teredo carries two IPv4 addresses — the client's, obfuscated, and the
relay's — so rather than pick one it is refused, alongside the rest of the
reserved `::/64` block, which `ipaddr.js` calls plain unicast. `::5efe:7f00:1`
reached loopback under the strict hosted policy before this.
- A scope id (`fd00:ec2::254%eth0`) named an interface, not a destination, and
made an address a different string from the one it is.
- Address classification runs on the canonical form, so the folding above reaches
the private/loopback verdict and not only the metadata comparison. A 6to4
wrapper around a public address is now correctly reachable.
Also from the same pass:
- An explicit allowlist grant outranks the loopback carve-out. Checking the
carve-out first made the policy non-monotonic: an operator who named
`127.0.0.1/32` got *less* than one who named nothing, because the carve-out
does not lift the port denylist and short-circuited the range match.
- `localhost.` and `*.localhost` are loopback names too (RFC 6761), and a
trailing dot no longer defeats the host allowlist on either side.
- An operator range naming a translation prefix now matches an address inside
it; only the folded spelling was being compared.
- A Unicode allowlist entry is refused with a message naming the punycode form,
rather than being accepted and then matching nothing — a URL hostname is
always the A-label.
- `proxy` is exempt from the hosted plain-HTTP cap. Its scheme is fixed by the
protocol rather than by trust, and capping it left `proxyUrl` with no reachable
configuration on the hosted platform and two contradictory error messages.
- The egress policies are built in `instrumentation-node`, so a malformed
allowlist entry stops the process at boot naming the setting, which is what the
docs say and what the lazy cache had stopped doing.
- `sim-setup` validates both allowlists through the same parser at the prompt,
and now asks for `EGRESS_ALLOWED_IP_RANGES` as well as the hosts.
Docs: the MCP OAuth rule applies only to endpoints on a different origin than
the configured server; an SSO OIDC discovery URL is a configured endpoint, not
content; the HTTP downgrade refusal does not apply to the two provenances that
expect plain HTTP; plain HTTP is capped on Sim Cloud; the NetworkPolicy note is
conditional on `networkPolicy.enabled`.
Tests: `ssrf-guarded-lookup.test.ts` passes a real profile instead of relying on
the unrecognized-profile fallback; the hosted block regains its positive control
and a service-port case; the OAuth block sets its own posture rather than
inheriting the previous describe's.
Copy file name to clipboardExpand all lines: apps/docs/content/docs/agents/mcp.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -87,7 +87,7 @@ Self-hosted deployments can restrict which MCP server domains are allowed by set
87
87
88
88
This governs which domains may be used. It is separate from where those domains are allowed to resolve: an MCP server on a private address is reached by naming it in `EGRESS_ALLOWED_HOSTS` or `EGRESS_ALLOWED_IP_RANGES`, described in [Security](/platform/self-hosting/security#the-ssrf-boundary). Both checks apply.
89
89
90
-
The allowlist covers the server URL itself. If the server requires OAuth, the endpoints its authorization-server metadata names are treated as content rather than as configuration, so they have to be publicly routable.
90
+
The allowlist covers the server URL itself. If the server requires OAuth, any endpoint its metadata names on a *different* origin than the server you configured is treated as content rather than as configuration, so that one has to be publicly routable. Endpoints on the server's own origin keep the server's reachability.
Copy file name to clipboardExpand all lines: apps/docs/content/docs/platform/self-hosting/security.mdx
+7-5Lines changed: 7 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -158,7 +158,9 @@ A wildcard (`*.svc.cluster.local`) and a broad range (`10.0.0.0/8`) are accepted
158
158
159
159
Both lists are validated when Sim starts, and a malformed entry stops it with a message naming the setting. `EGRESS_ALLOWED_HOSTS` takes hostnames only — a URL or a CIDR is rejected — and a wildcard has to be a leading `*.` covering at least two labels, so `*.local` is refused and `*.svc.cluster.local` matches `vllm.ai.svc.cluster.local` but not the bare `svc.cluster.local`. `EGRESS_ALLOWED_IP_RANGES` takes CIDRs and bare addresses; `0.0.0.0/0` is refused as a catch-all.
160
160
161
-
Naming a destination permits plain HTTP to it and lifts the blocked-port list for it, since those are the same decision about the same host. A database, cache, or mail connector's host carries no scheme or port of its own, so naming one of those only lifts the private-address block. Cloud metadata endpoints (`169.254.169.254` and equivalents) stay blocked no matter how broad the allowlist is, and both variables are ignored entirely on Sim Cloud.
161
+
On Sim Cloud plain HTTP is refused for every provenance, self-hosted-service ones included: nothing is vouched there, so a credential would cross the wire in the clear.
162
+
163
+
Naming a destination permits plain HTTP to it and lifts the blocked-port list for it, since those are the same decision about the same host. The loopback carve-out does not: it is granted without being asked for, so `http://localhost:5432` stays refused until `localhost` is named. A database, cache, or mail connector's host carries no scheme or port of its own, so naming one of those only lifts the private-address block. Cloud metadata endpoints (`169.254.169.254` and equivalents) stay blocked no matter how broad the allowlist is, and both variables are ignored entirely on Sim Cloud.
162
164
163
165
The allowlist reaches the four provenances marked **Yes** above. It does not reach a content fetch, and it does not reach a proxy: an HTTP block's `proxyUrl` must be a public address, because the proxy is what decides where every other request may go. Adding an internal proxy to the allowlist will not make it work.
An allowlist widens what every workflow author on the instance can reach. Name specific hosts and narrow ranges rather than whole private networks, and pair it with a NetworkPolicy that constrains what the app can actually reach. The chart's own NetworkPolicy permits broad egress on port 443 only, so an allowlisted in-cluster target on another port also has to be added to `networkPolicy.egress`.
174
+
An allowlist widens what every workflow author on the instance can reach. Name specific hosts and narrow ranges rather than whole private networks, and pair it with a NetworkPolicy that constrains what the app can actually reach. When `networkPolicy.enabled` is true the chart permits broad egress on port 443 only, so an allowlisted in-cluster target on another port also needs a `networkPolicy.egress` rule — or `networkPolicy.allowExternalEgress: true` for unrestricted egress.
173
175
</Callout>
174
176
175
177
### Upgrading from an earlier release
176
178
177
179
The allowlist replaces four separate escape hatches, so a few deployments that worked before now need a destination named:
178
180
179
-
- **`ALLOW_PRIVATE_DATABASE_HOSTS`** still works, but it is deprecated and logs a warning at startup. It vouches for the whole private address space for database, cache, and mail connector hosts. Replace it with `EGRESS_ALLOWED_HOSTS` or `EGRESS_ALLOWED_IP_RANGES` naming the hosts you actually use.
181
+
- **`ALLOW_PRIVATE_DATABASE_HOSTS`** still works, but it is deprecated and logs a warning at startup. It vouches for the whole private address space, loopback included, for database, cache, and mail connector hosts. Replace it with `EGRESS_ALLOWED_HOSTS` or `EGRESS_ALLOWED_IP_RANGES` naming the hosts you actually use.
180
182
- **1Password Connect** on a private, non-loopback address, and an **MCP server** on a private address or reached through a DNS name that points at loopback, are no longer reachable implicitly. Name them.
181
183
- **`ALLOWED_MCP_DOMAINS`** governs which domains may be used; it no longer disables the address check, so an MCP server on a private address needs the allowlist too.
182
-
- **Content fetches** — an image URL, a file imported by URL, an OIDC endpoint discovered from a provider's metadata, an MCP OAuth endpoint the server's metadata names — never use the allowlist. Those destinations have to be publicly routable.
183
-
- **Redirects** are re-judged at every hop, so a redirect that downgrades to plain HTTP or lands on a blocked port is now refused. Credentials are dropped when a redirect crosses origins, and a cross-origin redirect that would carry a request body to the new origin is refused outright rather than replayed — a POST that lands on a cross-origin redirect now fails with a message saying so.
184
+
- **Content fetches** — an image URL, a file imported by URL, an MCP OAuth endpoint on a different origin than the MCP server itself — never use the allowlist. Those destinations have to be publicly routable. (An SSO OIDC *discovery* URL is a configured endpoint and does use the allowlist; the endpoints inside the discovery document are used by the auth library and sit outside this boundary.)
185
+
- **Redirects** are re-judged at every hop under the request's own provenance, so a redirect that lands on a blocked port is refused, and one that downgrades to plain HTTP is refused for every provenance except the self-hosted-service and proxy classes, which expect plain HTTP by design. Only 301, 302, 303, 307 and 308 are followed; 300, 305 and 306 are not. Credentials are dropped when a redirect crosses origins, and a cross-origin redirect that would carry a request body to the new origin is refused outright rather than replayed — a POST that lands on a cross-origin redirect now fails with a message saying so.
Naming a destination also permits plain HTTP to it and lifts the blocked-port list for it. Cloud metadata endpoints (`169.254.169.254` and equivalents) stay blocked however broad the list is, and both variables are ignored on Sim Cloud.
39
+
Naming a destination also permits plain HTTP to it and lifts the blocked-port list for it. A database, cache, or mail connector's host carries no scheme or port of its own, so naming one of those only lifts the private-address block. Cloud metadata endpoints (`169.254.169.254` and equivalents) stay blocked however broad the list is, and both variables are ignored on Sim Cloud.
40
40
41
41
Two things this does not cover:
42
42
43
43
- Inside a container `localhost` is the container itself, so it will never reach a service on your host. Use `host.docker.internal` (the Compose files map it) and name it above.
44
-
- URLs harvested from content or from a third-party API response — an image URL, a file imported by URL, an OIDC or MCP OAuth endpoint discovered from a provider's metadata — never reach a private network, allowlist or not. Nor does an HTTP block's `proxyUrl`.
44
+
- URLs harvested from content or from a third-party API response — an image URL, a file imported by URL, an MCP OAuth endpoint on a different origin than the MCP server itself — never reach a private network, allowlist or not. Nor does an HTTP block's `proxyUrl`.
0 commit comments