mirror of
https://github.com/twentyhq/twenty.git
synced 2026-09-17 00:22:48 -04:00
Fixes #25638. Supersedes #25647, which was closed pending a decision on self-hosted deployments that run their identity provider on an internal network. ## What **SSO requests now go through the SSRF-safe agent.** `Issuer.discover()`, the JWKS fetch during ID token validation, and the token and userinfo calls were the only outbound requests not going through `SecureHttpClientService`, so a workspace admin with the `SECURITY` permission could point them at internal targets. They are wired through openid-client's `custom.http_options` hook. openid-client resolves that hook on whichever object issues the request (the `Issuer` class for discovery, the issuer instance for JWKS, the client instance for token and userinfo), so it is set on all three. #25647 only covered the first, which left the endpoints in an attacker-controlled discovery document unprotected. `OidcAuthGuard` now calls `SsoService.discoverOidcIssuer` rather than `Issuer.discover` itself, so setup and login share one path. **`OUTBOUND_HTTP_SAFE_MODE_ENABLED` is replaced by `OUTBOUND_HTTP_ALLOWED_INTERNAL_HOSTS`.** Self-hosters commonly run their IdP, mail or calendar server on a private address (a sibling docker-compose container, a LAN host). The only escape hatch was the global toggle, which also disables protection for webhooks and workflow actions, and applying it to SSO would have locked SSO-only workspaces out on upgrade since discovery runs on every login. The new variable is a comma-separated list of hostnames or IP literals that may be reached on a private network. Matching is exact and case-insensitive on the hostname, with no wildcard or suffix patterns; a pasted issuer URL or `host:port` is reduced to its hostname. `*` allows all, which is what the old `false` did. Everything else stays protected, and an allowlisted host can never reach the link-local range (`169.254.0.0/16`, `fe80::/10`) where cloud metadata services live, so a DNS change cannot turn an allowlisted name into a path to instance credentials. **Deprecation.** The old boolean stays declared, marked deprecated, and `false` is honoured as `*` in `SecureHttpClientService.getAllowedInternalHosts()` so existing self-hosted setups keep working after upgrading. While it is `false` the allowlist is ignored; the server logs a warning once per process saying so. It can be deleted in a later release; that is the only remaining reference. **Setup-time error.** A blocked issuer used to surface as a bare "Invalid issuer". The underlying reason (e.g. `Request to internal IP address 172.18.0.5 is not allowed.`) is now included in the setup error so the admin can diagnose it there. At login the failure only shows as a redirect. ## Config | Value | Effect | | --- | --- | | empty (default) | private-network addresses are blocked for HTTP workflow actions, webhooks, SSO and IMAP/SMTP/CalDAV | | `keycloak,mail.internal,192.168.1.10` | those hosts may reach private networks (never link-local) | | `*` | protection off (previous `OUTBOUND_HTTP_SAFE_MODE_ENABLED=false`) | ## Tests - Unit: allowlist cases for the agent and hostname utils (private address allowed, link-local still blocked, wildcard), `isLinkLocalIp`, a `SecureHttpClientService` spec covering `*`, the deprecated fallback with its one-time warning and URL/`host:port` entry normalisation, and an `SsoService` spec that builds the real `SecureHttpClientService` and asserts the agent refuses `169.254.169.254` for discovery, JWKS, token and userinfo. - Integration: the suites that flipped the toggle off to reach the dovecot, greenmail, radicale and webhook-receiver containers now set the list to `['*']`. `imap-smtp-outbound` sets it to the actual container host instead, so the per-host exemption is exercised end to end. - `npx tsgo --noEmit`, oxlint and oxfmt are clean on the changed files. ## Docs The self-hosting note in the calendar and emails guide now points at the allowlist instead of telling people to turn safe mode off, and the self-host setup guide gains an "Outbound Connections to Internal Hosts" section. Translated copies under `l/` are left to the i18n pipeline. ## Release note Self-hosted deployments whose identity provider, mail or calendar server resolves to a private address should add its hostname or IP to `OUTBOUND_HTTP_ALLOWED_INTERNAL_HOSTS`. Deployments that set `OUTBOUND_HTTP_SAFE_MODE_ENABLED=false` keep working unchanged for now, but that flag overrides the new list, so remove it when migrating. Session: https://claude.ai/code/session_01WPVhQuV1QgbTS63ivuTjjH