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
{{ message }}
Repository navigation
Commit 8036a9d
Browse filesBrowse the repository at this point in the historyBrowse files
fix(egress): close two IPv6 fail-opens, and separate a carve-out from an allowlist
An audit sweep over the whole diff turned up four things worth fixing in the
policy core and two in the profile layer.
Fail-opens, both IPv6 translation prefixes the address folding missed:
- `::ffff:0:a9fe:a9fe` (RFC 6145 IPv4-translated) was not folded, so a vouched
destination whose resolver answered with it reached cloud metadata — the
guarantee that metadata is never liftable did not hold for that spelling.
- `64:ff9b:1::/48` (RFC 8215 local-use NAT64) carries its IPv4 destination at an
offset the network operator chooses, so it cannot be read off the address at
all. `ipaddr.js` calls it plain unicast, which made it a route to metadata or
loopback under even the strict hosted policy. An address whose real
destination cannot be determined is now refused rather than judged on its
wrapper.
Policy corrections:
- The loopback carve-out no longer lifts the blocked-port list. It is granted
without anyone asking for it, and loopback is exactly where Sim's own Postgres
and Redis listen, so `http://localhost:5432` was reachable from an HTTP block
on any self-hosted deployment. Only an operator naming a destination lifts
ports now — a vouch carries the kind that earned it.
- A hostname that says it is loopback is refused before DNS when the policy does
not permit loopback, so the synchronous validator stops accepting
`https://localhost/x` on the hosted platform.
- `insecureHttp: 'always'` is capped at `whenVouched` on the hosted platform,
where nothing is vouched. Software served without TLS is a self-hosted
arrangement; a hosted deployment should not send a credential in the clear to
a user-supplied MCP or vLLM host.
- Wildcard allowlist entries are validated like every other entry. `*.foo.com/x`
and `*..com` were accepted silently and then matched nothing.
Transport:
- An IP-literal redirect target is judged as the literal even when the caller
supplied a resolved address, because `net.connect` dials a numeric host
directly and the literal is what the socket reaches.
- The connect-time lookup classifies against the request's own policy instead of
a hand-rolled private-address filter, so an allowlisted private destination
the redirect check permitted is no longer stranded at connect.
- 300, 305 and 306 are no longer followed. 305 redirects a request into a
server-named proxy, which is the one hop a guard must never take.
Provenance corrections: a Vision image resolved from an internal file URL and
Buffer's media probe are presigned URLs against Sim's own storage, so they take
`configuredEndpoint` the way STT and Textract already do — on a self-hosted
deployment with private object storage they were unreachable. Microsoft Word's
upload URL comes out of a Graph response rather than configuration, so it takes
`contentFetch`, matching SharePoint and Windchill.
Also: `policyCanVouch` is gone, since the synchronous callers both need the
narrow predicate; `validateDatabaseHost` classifies each address once instead of
twice; the dead `|| !validation.resolvedIP` conjuncts the discriminated union
made unreachable are removed across ~32 call sites; the boundary check scans
`background/` and `blocks/`; and the docs correct the MCP row, the in-cluster
naming claim, the refusal-message description, and add the upgrade notes.
Copy file name to clipboardExpand all lines: apps/docs/content/docs/agents/mcp.mdx
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -87,6 +87,8 @@ 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.
91
+
90
92
## Using MCP Tools in Agents
91
93
92
94
Once MCP servers are configured, their tools become available within your agent blocks:
Copy file name to clipboardExpand all lines: apps/docs/content/docs/platform/self-hosting/environment-variables.mdx
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -147,8 +147,11 @@ Without a remote provider, user code runs in an in-process V8 isolate inside the
147
147
|`WEBHOOK_EXECUTION_CONCURRENCY_LIMIT`|`75`| Webhook-triggered executions in parallel |
148
148
|`SCHEDULE_EXECUTION_CONCURRENCY_LIMIT`|`30`| Scheduled executions in parallel |
149
149
|`RESUME_EXECUTION_CONCURRENCY_LIMIT`|`50`| Resumed executions in parallel |
150
-
|`EGRESS_ALLOWED_HOSTS`| unset | Comma-separated hostnames outbound requests may reach on a private network. Leading wildcard allowed, e.g. `host.docker.internal,*.svc.cluster.local`|
151
-
|`EGRESS_ALLOWED_IP_RANGES`| unset | Comma-separated CIDRs or IPs outbound requests may reach on a private network, e.g. `10.0.0.0/8`|
150
+
|`EGRESS_ALLOWED_HOSTS`| unset | Comma-separated hostnames outbound requests may reach on a private network. Leading wildcard allowed, e.g. `host.docker.internal,*.svc.cluster.local`. Not honored for URLs harvested from content or a third-party response, nor for an HTTP block's `proxyUrl`|
151
+
|`EGRESS_ALLOWED_IP_RANGES`| unset | Comma-separated CIDRs or IPs outbound requests may reach on a private network, e.g. `10.0.0.0/8`. Same exclusions |
152
+
|`ALLOW_PRIVATE_DATABASE_HOSTS`| unset |**Deprecated.** Vouches for the entire private address space, for database, cache, and mail connector hosts only. Replace it with the two settings above naming specific destinations |
153
+
154
+
A malformed entry in either allowlist stops the app at startup with a message naming the setting. See [the SSRF boundary](/platform/self-hosting/security#the-ssrf-boundary) for the accepted syntax and for what the allowlist does and does not reach.
152
155
153
156
Your reverse proxy's body-size limit must be at least as large as the app limits above. See [Networking](/platform/self-hosting/networking).
Copy file name to clipboardExpand all lines: apps/docs/content/docs/platform/self-hosting/security.mdx
+18-6Lines changed: 18 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -134,12 +134,12 @@ Resource ceilings for the in-process path:
134
134
135
135
## The SSRF boundary
136
136
137
-
By default Sim blocks outbound requests to private, reserved, and loopback addresses. This stops a workflow from being used to scan your internal network. Two things soften it on a self-hosted deployment: the provenances marked **Yes** below reach whatever you allowlist, and a configured endpoint, self-hosted service, or request target written as `localhost` or a loopback literal is reachable without any allowlist at all — a local Ollama or Jupyter is the ordinary case. That second carve-out stops there: a database, cache, or mail connector on `localhost` still has to be allowlisted, because loopback is where Sim's own database and Redis listen. Neither softening applies on Sim Cloud. Every outbound request is classified by where its URL came from:
137
+
By default Sim blocks outbound requests to private, reserved, and loopback addresses. This stops a workflow from being used to scan your internal network. Two things soften it on a self-hosted deployment: the provenances marked **Yes** below reach whatever you allowlist, and a configured endpoint, self-hosted service, or request target written as `localhost` or a loopback literal is reachable without any allowlist at all — a local Ollama or Jupyter is the ordinary case. That second carve-out stops short in two places: it does not lift the blocked-port list, and it does not extend to a database, cache, or mail connector on `localhost` — loopback is where Sim's own database and Redis listen, so reaching them has to be asked for. Neither softening applies on Sim Cloud. Every outbound request is classified by where its URL came from:
| Configured endpoint | GitHub Enterprise, Grafana, an MCP server, a data-drain destination, a connector's host | Yes |
142
-
| Self-hosted service | vLLM, Jupyter, 1Password Connect, ClickHouse — software usually run on-prem without TLS, so plain HTTP is expected | Yes |
141
+
| Configured endpoint | GitHub Enterprise, Grafana, a data-drain destination, a connector's host | Yes |
142
+
| Self-hosted service | vLLM, Jupyter, 1Password Connect, ClickHouse, an MCP server — software usually run on-prem without TLS, so plain HTTP is expected | Yes |
143
143
| Request target | The HTTP block's URL, an A2A agent, an RSS feed, a Function block's `fetch` | Yes |
144
144
| Database host | A database, cache, or mail connector's host | Yes |
145
145
| Content fetch | An image URL, a file imported by URL, a link from a third-party API response | **No** |
A wildcard (`*.svc.cluster.local`) and a broad range (`10.0.0.0/8`) are accepted, but they hand every workflow author the whole namespace or network. Name the hosts you actually use.
158
158
159
-
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. 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.
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
+
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.
160
162
161
163
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.
172
+
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`.
171
173
</Callout>
172
174
175
+
### Upgrading from an earlier release
176
+
177
+
The allowlist replaces four separate escape hatches, so a few deployments that worked before now need a destination named:
178
+
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.
180
+
- **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
+
- **`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
+
173
185
## Client IP and forwarded headers
174
186
175
187
Behind a load balancer, `X-Forwarded-For` is client-controllable. Set `AUTH_TRUSTED_PROXIES` to your proxies' actual addresses so Better Auth resolves the real client IP, and `TRUSTED_ORIGINS` if users reach Sim from more than one origin. Both are covered in [Authentication](/platform/self-hosting/authentication#behind-a-load-balancer).
@@ -216,5 +228,5 @@ The service bundles ~2.2 GB of spaCy models, so first start takes around three m
216
228
{ question: "Can I rotate ENCRYPTION_KEY?", answer: "Not without re-encrypting everything it protects. Changing it makes workspace environment variables, stored provider API keys, MCP OAuth credentials, and deployment secrets permanently unreadable. Treat it as a permanent, backed-up value rather than a rotating secret."},
217
229
{ question: "Where does user-authored code run?", answer: "By default in an in-process V8 isolate inside the app container, which isolates at the JS-engine level but shares the container's network and filesystem context. For untrusted authors, or to run Python at all, use E2B or Daytona so each execution runs in a remote sandbox."},
218
230
{ question: "Why does the chart's NetworkPolicy allow traffic from any pod?", answer: "networkPolicy.ingressFrom defaults to an empty peer selector as a simple default that works on any cluster. On a shared cluster you should scope it to your ingress controller's namespace."},
219
-
{ question: "How do I reach an internal service from a workflow?", answer: "Name it in EGRESS_ALLOWED_HOSTS (hostnames, leading wildcard allowed) or EGRESS_ALLOWED_IP_RANGES (CIDRs). That permits plain HTTP to it and lifts the blocked-port list for it. Cloud metadata endpoints stay blocked regardless, content fetches never use the allowlist, and both variables are ignored on Sim Cloud."},
231
+
{ question: "How do I reach an internal service from a workflow?", answer: "Name it in EGRESS_ALLOWED_HOSTS (hostnames, leading wildcard allowed) or EGRESS_ALLOWED_IP_RANGES (CIDRs). That permits plain HTTP to it and lifts the blocked-port list for HTTP destinations; a database, cache, or mail host carries no scheme or port of its own, so naming it only lifts the private-address block. Cloud metadata endpoints stay blocked regardless, content fetches never use the allowlist, and both variables are ignored on Sim Cloud."},
Copy file name to clipboardExpand all lines: apps/docs/content/docs/platform/self-hosting/troubleshooting.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,7 @@ OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
27
27
28
28
## A Workflow Cannot Reach a Service on Your Network
29
29
30
-
Outbound requests to private, reserved, and loopback addresses are blocked by default, so a workflow pointed at your Docker host, a LAN service, or a Kubernetes service name fails with a message naming the address it resolved to.
30
+
Outbound requests to private, reserved, and loopback addresses are blocked by default, so a workflow pointed at your Docker host, a LAN service, or a Kubernetes service name fails with a message naming the blocker — the private or loopback address it resolved to, a blocked port, or `must use https:// to a public destination` when the URL is plain HTTP — and pointing at the allowlist variables.
31
31
32
32
Name the destination:
33
33
@@ -41,7 +41,7 @@ Naming a destination also permits plain HTTP to it and lifts the blocked-port li
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— never reach a private network, allowlist or not.
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`.
# EGRESS_ALLOWED_HOSTS=host.docker.internal,*.svc.cluster.local # Uncomment to let outbound requests reach these hosts on a private network. Widens the SSRF boundary; only use on a trusted private network.
# EGRESS_ALLOWED_IP_RANGES=10.0.0.0/8 # Same, by CIDR. Cloud metadata endpoints stay blocked regardless, and neither setting is honored for URLs harvested from content or for a proxy.
0 commit comments