diff --git a/docs/self-hosted/oel/keto/changelog/v26.3.5.md b/docs/self-hosted/oel/keto/changelog/v26.3.5.md new file mode 100644 index 000000000..e4601b085 --- /dev/null +++ b/docs/self-hosted/oel/keto/changelog/v26.3.5.md @@ -0,0 +1,10 @@ +## v26.3.5 + +### Strict mode requires subject sets + +If you run Ory Keto with `feature_flags.strict_mode` (or the deprecated `namespaces.experimental_strict_mode`) enabled, creating +relationships or checking permissions with a plain `subject_id` now fails with `400 Bad Request` (`InvalidArgument` over gRPC). +Reading and deleting existing `subject_id` relationships keeps working. + +Before upgrading, rewrite `subject_id` relationships as subject sets or disable strict mode. See the +[migration guide](https://www.ory.com/docs/keto/guides/strict-mode). diff --git a/docs/self-hosted/oel/keto/changelog/v26.3.6.md b/docs/self-hosted/oel/keto/changelog/v26.3.6.md new file mode 100644 index 000000000..9cc2a2ac5 --- /dev/null +++ b/docs/self-hosted/oel/keto/changelog/v26.3.6.md @@ -0,0 +1 @@ +No changelog entries found for keto/oel in versions v26.3.6 diff --git a/docs/self-hosted/oel/kratos/changelog/v26.3.5.md b/docs/self-hosted/oel/kratos/changelog/v26.3.5.md new file mode 100644 index 000000000..7fab5584e --- /dev/null +++ b/docs/self-hosted/oel/kratos/changelog/v26.3.5.md @@ -0,0 +1,66 @@ +## v26.3.5 + +### Passkey display names now follow one deterministic precedence rule + +When an identity schema flags more than one trait with `passkey.display_name: true` (for example `email` and `phone`), the +settings flow previously picked the display name for a new passkey at random from the populated flagged traits. A user adding a +passkey from account settings would sometimes see it named after their email and sometimes after their phone number. + +The display name is now resolved by a single rule in every flow: traits flagged with `passkey.display_name` take precedence over +traits flagged with `webauthn.identifier`, and within each group the alphabetically-first trait with a non-empty value wins. With +`email` and `phone` flagged, the email address is now always used when it is set. + +For schemas that flag both `passkey.display_name` and `webauthn.identifier` traits, this also changes the candidate order the +registration flow publishes to the browser: `passkey.display_name` traits now come first, where previously all flagged traits were +sorted alphabetically in one list. A passkey created during registration may therefore get its name from a `passkey.display_name` +trait where an alphabetically-earlier `webauthn.identifier` trait was used before. Existing passkeys keep their names, and schemas +that flag only one kind of trait see no change at registration. + +### Prometheus metrics for the Jsonnet process pool + +The Prometheus endpoint now reports the state of the Jsonnet worker process pool, which evaluates webhook payload templates, OIDC +claims mappers, and session token claims. Use these metrics to see whether requests are queueing for a free worker. + +The metrics mirror the existing SQL connection pool metrics and carry no labels, because each process holds exactly one pool: + +| Metric | Type | Description | +| -------------------------------------------------- | ------- | ------------------------------------------- | +| `ory_jsonnet_pool_max_processes` | gauge | Maximum number of worker processes. | +| `ory_jsonnet_pool_processes` | gauge | Started worker processes, in use and idle. | +| `ory_jsonnet_pool_in_use_processes` | gauge | Worker processes currently in use. | +| `ory_jsonnet_pool_idle_processes` | gauge | Idle worker processes. | +| `ory_jsonnet_pool_constructing_processes` | gauge | Worker processes currently starting. | +| `ory_jsonnet_pool_wait_count_total` | counter | Acquires that had to wait for a worker. | +| `ory_jsonnet_pool_wait_duration_seconds_total` | counter | Total time blocked waiting for a worker. | +| `ory_jsonnet_pool_acquired_processes_total` | counter | Worker processes ever acquired. | +| `ory_jsonnet_pool_acquired_duration_seconds_total` | counter | Total duration of all acquires. | +| `ory_jsonnet_pool_canceled_acquires_total` | counter | Acquires canceled before a worker was free. | + +#### The courier now evaluates Jsonnet in an isolated process + +The courier renders the request body of an HTTP delivery channel from a Jsonnet template, and SMS delivery always uses an HTTP +channel. It was evaluating those templates inside the courier process instead of in a sandboxed worker, so a template that +consumed excessive memory or CPU affected message delivery as a whole. The courier now uses the same isolated worker pool as the +rest of Kratos, where evaluation runs under a memory limit and a filesystem sandbox. + +No configuration changes are needed, and template behavior is unchanged. + +### Serve the WebAuthn Related Origin Requests document + +Ory Kratos now serves the WebAuthn Related Origin Requests document at `/.well-known/webauthn`. It lists the relying party origins +configured for the enabled WebAuthn and passkey methods. + +Browsers that support Related Origin Requests (Chrome 128+, Safari 18+) consult this document when a page requests a WebAuthn +relying party ID that does not match the page's own origin. This lets passkeys bound to a relying party ID served by Ory work on +additional origins, for example across several custom domains: add the extra origins to the relying party origins of the WebAuthn +or passkey configuration and they appear in the document automatically. + +See https://www.w3.org/TR/webauthn-3/#sctn-related-origins for details. + +### Webhooks now receive enrolled credential types and available AAL + +Login, registration, settings, recovery, and verification webhooks now receive the identity's enrolled credential types and +available authenticator assurance level. The payload's `identity.credentials` lists each enrolled credential's type and +identifiers, plus its version and its creation and update timestamps when known — never secrets: credential configuration is +stripped and identity-provider (OIDC and SAML) credentials list no identifiers. `identity.available_aal` reports whether the +identity has a usable second factor. Use this to build risk-based skip-vs-challenge decisions in your webhook. diff --git a/docs/self-hosted/oel/kratos/changelog/v26.3.6.md b/docs/self-hosted/oel/kratos/changelog/v26.3.6.md new file mode 100644 index 000000000..a966f1322 --- /dev/null +++ b/docs/self-hosted/oel/kratos/changelog/v26.3.6.md @@ -0,0 +1 @@ +No changelog entries found for kratos/oel in versions v26.3.6 diff --git a/docs/self-hosted/oel/oathkeeper/changelog/v26.3.5.md b/docs/self-hosted/oel/oathkeeper/changelog/v26.3.5.md new file mode 100644 index 000000000..377dc9222 --- /dev/null +++ b/docs/self-hosted/oel/oathkeeper/changelog/v26.3.5.md @@ -0,0 +1 @@ +No changelog entries found for oathkeeper/oel in versions v26.3.5 diff --git a/docs/self-hosted/oel/oathkeeper/changelog/v26.3.6.md b/docs/self-hosted/oel/oathkeeper/changelog/v26.3.6.md new file mode 100644 index 000000000..7313dc136 --- /dev/null +++ b/docs/self-hosted/oel/oathkeeper/changelog/v26.3.6.md @@ -0,0 +1,15 @@ +## v26.3.6 + +### Oathkeeper hardens request path handling against path-confusion attacks + +Ory Oathkeeper now normalizes and validates the request URL path before matching access rules, closing a class of path-confusion +attacks that could bypass authorization. A new `security` configuration section tunes this behavior, and the defaults are secure. + +Read [Request path normalization and validation](https://www.ory.com/docs/oathkeeper/guides/request-path-security) if you run +Oathkeeper as a Decision API, place it behind another proxy, or rely on unusual path encodings. + +#### Breaking changes + +The Access Control Decision API no longer trusts incoming `X-Forwarded-*` headers by default. ForwardAuth-style integrations +(Traefik, NGINX, Ambassador, Envoy) must set `security.decision.x_forwarded_headers: trust` to keep working. See the +[documentation](https://www.ory.com/docs/oathkeeper/guides/request-path-security#x-forwarded-headers) for details. diff --git a/docs/self-hosted/oel/oauth2/changelog/v26.3.5.md b/docs/self-hosted/oel/oauth2/changelog/v26.3.5.md new file mode 100644 index 000000000..a3bbe31ea --- /dev/null +++ b/docs/self-hosted/oel/oauth2/changelog/v26.3.5.md @@ -0,0 +1,24 @@ +## v26.3.5 + +### Add oauth2.grant_types_disabled config key + +Ory Hydra's configuration schema now supports `oauth2.grant_types_disabled`: a list of OAuth 2.0 grant type identifiers clients +may not be registered with. An absent or empty list disables nothing, so deployments that never set the key are unaffected. + +The key can only disable grant types; it cannot enable grant types the server does not implement. In particular, the Resource +Owner Password Credentials grant remains unavailable in Ory Hydra OSS — it is implemented only in Ory Network and Ory Enterprise +License (OEL) deployments. + +Registering a new client with a disabled grant type, or adding a disabled grant type to an existing client, is rejected with +`invalid_client_metadata` (HTTP 400). Clients that already carry the grant type are unaffected: they keep working at runtime and +remain fully maintainable through the API — updates, secret rotation, and lifespan changes still succeed. + +Disabled grant types are also omitted from `grant_types_supported` in the OpenID Connect discovery and OAuth 2.0 authorization +server metadata documents, so new integrations only see grant types they can register. + +Hydra warns at startup about list entries that do not match a registered OAuth 2.0 grant type and are not custom extension grant +URIs — for example `device_code` instead of `urn:ietf:params:oauth:grant-type:device_code`, or a misspelled grant type URN — since +unknown entries disable nothing. + +Set the key through an environment variable as a comma-separated list +(`OAUTH2_GRANT_TYPES_DISABLED='password,urn:ietf:params:oauth:grant-type:device_code'`) or as a JSON array. diff --git a/docs/self-hosted/oel/oauth2/changelog/v26.3.6.md b/docs/self-hosted/oel/oauth2/changelog/v26.3.6.md new file mode 100644 index 000000000..087a612a5 --- /dev/null +++ b/docs/self-hosted/oel/oauth2/changelog/v26.3.6.md @@ -0,0 +1 @@ +No changelog entries found for hydra/oel in versions v26.3.6 diff --git a/docs/self-hosted/oel/oel-hydra-image-tags.md b/docs/self-hosted/oel/oel-hydra-image-tags.md index cd3d6045b..84a2197ac 100644 --- a/docs/self-hosted/oel/oel-hydra-image-tags.md +++ b/docs/self-hosted/oel/oel-hydra-image-tags.md @@ -1,5 +1,7 @@ | Image Tag | Release Date | | ---------------------------------------- | ------------ | +| 26.3.6 | 2026-08-07 | +| 26.3.5 | 2026-08-04 | | 26.3.4 | 2026-07-28 | | 26.3.3 | 2026-07-10 | | 26.3.2 | 2026-07-09 | diff --git a/docs/self-hosted/oel/oel-keto-image-tags.md b/docs/self-hosted/oel/oel-keto-image-tags.md index 3df8e416f..b99874393 100644 --- a/docs/self-hosted/oel/oel-keto-image-tags.md +++ b/docs/self-hosted/oel/oel-keto-image-tags.md @@ -1,5 +1,7 @@ | Image Tag | Release Date | | ---------------------------------------- | ------------ | +| 26.3.6 | 2026-08-07 | +| 26.3.5 | 2026-08-04 | | 26.3.4 | 2026-07-28 | | 26.3.3 | 2026-07-10 | | 26.3.2 | 2026-07-09 | diff --git a/docs/self-hosted/oel/oel-kratos-image-tags.md b/docs/self-hosted/oel/oel-kratos-image-tags.md index 97cff1abd..15a701ded 100644 --- a/docs/self-hosted/oel/oel-kratos-image-tags.md +++ b/docs/self-hosted/oel/oel-kratos-image-tags.md @@ -1,5 +1,7 @@ | Image Tag | Release Date | | ---------------------------------------- | ------------ | +| 26.3.6 | 2026-08-07 | +| 26.3.5 | 2026-08-04 | | 26.3.4 | 2026-07-28 | | 26.3.3 | 2026-07-10 | | 26.3.2 | 2026-07-09 | diff --git a/docs/self-hosted/oel/oel-oathkeeper-image-tags.md b/docs/self-hosted/oel/oel-oathkeeper-image-tags.md index 8fb022588..65953c8cd 100644 --- a/docs/self-hosted/oel/oel-oathkeeper-image-tags.md +++ b/docs/self-hosted/oel/oel-oathkeeper-image-tags.md @@ -1,5 +1,7 @@ | Image Tag | Release Date | | ---------------------------------------- | ------------ | +| 26.3.6 | 2026-08-07 | +| 26.3.5 | 2026-08-04 | | 26.3.4 | 2026-07-28 | | 26.3.3 | 2026-07-10 | | 26.3.2 | 2026-07-09 | diff --git a/docs/self-hosted/oel/oel-polis-image-tags.md b/docs/self-hosted/oel/oel-polis-image-tags.md index 54f987470..59dc04cf7 100644 --- a/docs/self-hosted/oel/oel-polis-image-tags.md +++ b/docs/self-hosted/oel/oel-polis-image-tags.md @@ -1,5 +1,7 @@ | Image Tag | Release Date | | ---------------------------------------- | ------------ | +| 26.3.6 | 2026-08-07 | +| 26.3.5 | 2026-08-04 | | 26.3.4 | 2026-07-28 | | 26.3.3 | 2026-07-10 | | 26.3.2 | 2026-07-09 | diff --git a/docs/self-hosted/oel/polis/changelog/v26.3.5.md b/docs/self-hosted/oel/polis/changelog/v26.3.5.md new file mode 100644 index 000000000..690e224b0 --- /dev/null +++ b/docs/self-hosted/oel/polis/changelog/v26.3.5.md @@ -0,0 +1 @@ +No changelog entries found for polis/oel in versions v26.3.5 diff --git a/docs/self-hosted/oel/polis/changelog/v26.3.6.md b/docs/self-hosted/oel/polis/changelog/v26.3.6.md new file mode 100644 index 000000000..62b9d3c95 --- /dev/null +++ b/docs/self-hosted/oel/polis/changelog/v26.3.6.md @@ -0,0 +1,178 @@ +## v26.3.6 + +### Harden OIDC discovery URL fetching against SSRF + +Creating or updating an OIDC SSO connection with an `oidcDiscoveryUrl` now validates the fetch target, and the value is +re-validated whenever it is fetched during an authorize or callback flow. + +Previously the discovery URL was stored verbatim with no scheme, host or address validation and fetched on every authorize and +callback. A caller with an SSO setup link or the deployment API key could point it at an internal address — such as the cloud +instance metadata service or an in-cluster service — and read a reachability and response oracle from the resulting SSO trace and +error redirect. + +The discovery URL now must be an HTTPS URL that does not resolve to a non-public address (loopback, link-local, carrier-grade NAT, +private, unique-local, reserved and unspecified ranges for both IPv4 and IPv6, and IPv4-mapped IPv6 literals). Every outbound OIDC +request — discovery, token, userinfo and JWKS — is validated at connection time, so a hostname that resolves to an internal +address, or one that changes its answer between validation and the request (DNS rebinding), is blocked. + +The `localhost` hostname is now blocked by default, so a connection owner cannot reach services on the loopback interface of the +host or pod. Local development against an IdP on `localhost` is enabled automatically in a Node `development` environment, or +explicitly by setting `POLIS_ALLOW_LOCALHOST_OUTBOUND=true`. + +The in-cluster federation tunnel — which rewrites a `.projects.oryapis:8080` discovery URL to the internal `hydra-public` +service — keeps working unchanged; its rewrite target is a fixed, code-controlled in-cluster service, never taken from the +request. A self-hosted deployment that does not use Ory federation can turn the tunnel off with +`POLIS_DISABLE_INTERNAL_ORY_FEDERATION=true`, after which a `*.projects.oryapis` discovery URL is validated like any other host. + +#### Upgrade impact + +Because the discovery URL is re-validated on every authorize and callback, an existing OIDC connection whose discovery, token, +userinfo or JWKS endpoint is plain HTTP, `localhost`, or a private, loopback, link-local or other non-public address will start +failing those flows immediately after upgrading. On Ory Network this is not expected — providers are public HTTPS hosts. +Self-hosted (OEL/OSS) deployments should audit existing OIDC connections before upgrading and re-point any such connection at a +public HTTPS endpoint. + +For a self-hosted deployment whose identity provider legitimately runs on an internal network, set +`POLIS_ALLOW_PRIVATE_IP_OUTBOUND=true` to allow HTTPS fetches to RFC1918 and IPv6 unique-local addresses. The opt-in is narrow: +HTTPS is still required, and link-local (including the cloud metadata address), loopback, carrier-grade NAT and reserved ranges +stay blocked. An IdP on the loopback interface uses `POLIS_ALLOW_LOCALHOST_OUTBOUND=true` instead. + +This path is not exposed through the Ory Network gateway. + +### Harden SAML metadata URL fetching against SSRF + +Creating or updating a SAML SSO connection with a `metadataUrl` now validates the fetch target against a strict allow list before +and during the request. + +The previous check only rejected RFC1918 private IPv4 literals and IPv6 unique-local literals. It let through loopback, link-local +(including the cloud metadata address `169.254.169.254`), carrier-grade NAT, reserved and unspecified addresses, every IPv6 +literal, and every DNS hostname, and the fetch followed HTTP redirects without re-validating the target. A caller with an SSO +setup link or the SSO API key could use this to read internal endpoints such as the cloud instance metadata service. + +The fetch now requires an HTTPS URL, resolves the hostname and rejects any address that is not a public unicast address (covering +loopback, link-local, carrier-grade NAT, private, unique-local, reserved and unspecified ranges for both IPv4 and IPv6, and +IPv4-mapped IPv6 literals). Resolution happens at connection time, so a hostname that resolves to an internal address — or one +that changes its answer between validation and the request (DNS rebinding) — is blocked. Redirects are no longer followed, so a +public host cannot redirect the request into an internal address. Unparseable input fails closed. + +The `localhost` hostname is blocked by default so a connection owner cannot reach services on the loopback interface of the host +or pod. Local development against an IdP on `localhost` is enabled automatically in a Node `development` environment, or +explicitly by setting `POLIS_ALLOW_LOCALHOST_OUTBOUND=true`. + +#### Upgrade impact + +The previous check allowed `localhost` and every non-RFC1918 address, so a SAML connection configured with a `metadataUrl` over +plain HTTP, on `localhost`, or on a private, loopback or link-local address may now be rejected when its metadata is fetched or +refreshed. On Ory Network this is not expected. Self- hosted (OEL/OSS) deployments should audit existing SAML connections before +upgrading and re-point any such `metadataUrl` at a public HTTPS endpoint. + +For a self-hosted identity provider on an internal network, set `POLIS_ALLOW_PRIVATE_IP_OUTBOUND=true` to allow HTTPS fetches to +RFC1918 and IPv6 unique-local addresses; HTTPS is still required and link-local (including the cloud metadata address), loopback, +carrier-grade NAT and reserved ranges stay blocked. An IdP on the loopback interface uses `POLIS_ALLOW_LOCALHOST_OUTBOUND=true` +instead. + +This path is not exposed through the Ory Network gateway. + +### Add YugabyteDB support to Ory Polis + +Ory Polis can now use YugabyteDB as its backing database. YugabyteDB is PostgreSQL wire-compatible, so it reuses the existing +PostgreSQL driver and needs no additional dependencies. This gives self-hosted and enterprise deployments a distributed, +horizontally scalable storage option alongside the existing engines (PostgreSQL, MySQL, MariaDB, Microsoft SQL Server, SQLite, +CockroachDB, MongoDB, Redis, PlanetScale, and DynamoDB). + +#### Configuration + +Select YugabyteDB by setting the SQL engine and the `yugabytedb` type, then point the connection URL at your YugabyteDB YSQL +endpoint (default port 5433): + +```bash +DB_ENGINE=sql +DB_TYPE=yugabytedb +DB_URL=postgresql://yugabyte@localhost:5433/yugabyte +``` + +### Fix cross-tenant admin takeover through SAML admin-portal login + +The Polis Admin Portal SAML login now accepts a login only when it went through the Admin Portal's own SSO connection. Previously +both the IdP-initiated (`boxyhq-saml-idplogin`) and SP-initiated (`boxyhq-saml`) flows trusted a login completed against any +tenant's SSO connection in the deployment. An attacker could complete a SAML login against a connection they control and receive +an Admin Portal session with read and write access to every tenant's SSO connections and secrets. + +The fix pins the resolved connection's tenant and product to the Admin Portal defaults (`_jackson_boxyhq` / +`_jackson_admin_portal`) and rejects anything else — in the IdP-initiated provider's `authorize()` and in the sign-in callback for +the SP-initiated provider. + +This affects self-hosted Polis (OSS) and the polis-oel chart. It is not reachable on the Ory Network, where the gateway does not +route `/api/auth/**` to Polis. + +### Fix cross-tenant data leak in concurrent SCIM directory-sync requests + +SCIM directory-sync requests could leak users and groups across tenants when two requests for different directories were processed +at the same time. + +The request handler bound the directory's tenant and product onto a single, process-wide store instance that every request shared. +Because that binding was read again at each database call after intervening `await`s, a second request for a different directory +could overwrite it mid-flight. A user or group created for directory A could then be written into directory B's namespace: +directory B's directory-sync APIs would return directory A's data, while directory A's own read returned nothing even though it +received `201 Created`. + +The handler now binds a request-scoped copy of the store per request, so concurrent requests can never observe one another's +tenant and product. The directory-sync admin and management API routes (`/api/v1/dsync/*`, `/api/admin/directory-sync/*`) were +bound the same way and are migrated too, which closes a related window where a concurrent read could redirect a "delete all +webhook events" request to another tenant's namespace. This affects self-hosted Polis; SCIM is not routed through the Ory Network +gateway. + +### Reject SAML responses that declare a DTD and stop leaking parser errors + +The SAML assertion consumer service (`POST /api/oauth/saml`) parsed the base64-decoded `SAMLResponse` with document type +definition (DTD) processing enabled. Because a SAML response never legitimately contains a DTD, this exposed the XML parser to +XXE-class attacks: a crafted `DOCTYPE` with a `SYSTEM` entity produced different parser errors depending on whether a referenced +path existed, giving an unauthenticated caller a file-existence oracle. Internal parser error text was also returned to the caller +through the `polis_error` cookie, which made the differential observable. + +Polis now rejects any SAML response that declares a `DOCTYPE`/DTD before it reaches the parser, and returns a generic error to the +client. Internal error detail is logged server-side only and is no longer disclosed to the caller: the `polis_error` cookie +returns a generic message for server errors, and the OAuth `error_description` returned on the redirect no longer includes raw +parser or exception text. Standard OAuth error codes and OIDC provider error descriptions are still forwarded. + +### Reject invalid magic-link tokens in the Polis admin portal + +The admin-portal magic-link (email) sign-in accepted any token value. The next-auth adapter looked up the stored verification +token by email but returned the record without checking that the presented token matched it. Because next-auth treats any non-null +adapter result as valid and the portal grants the `admin` role to every address, an unauthenticated attacker could complete the +email callback for any address — including an administrator's — with an arbitrary token and receive an authenticated admin +session. This required magic-link login to be enabled (`NEXTAUTH_ACL` set together with an SMTP host); a default install with +`NEXTAUTH_ACL` unset was not affected. + +Polis now returns the verification token only when the presented value matches the stored one, and consumes it on that single +successful use. Non-matching or missing tokens are rejected, closing the bypass (GHSA-j4rr-hrm4-hx4j). + +### Validate SAML SingleLogout URLs to prevent stored XSS and open redirect + +SAML connection metadata is now scheme-checked on its SingleLogout (SLO) bindings, not just its login (SSO) bindings. Previously, +the `SingleLogoutService` `Location` from an identity provider's metadata was stored without validation and later written into the +auto-submitting form and redirect served by the unauthenticated `/api/logout` endpoint. A `javascript:` URL placed in that field +executed in the browser of anyone who opened the tenant's logout link (stored cross-site scripting), and a crafted redirect +binding could send that visitor to an arbitrary host (open redirect). + +Creating or updating a SAML connection now rejects any SLO `Location` that does not use an `http` or `https` scheme, matching the +check already applied to SSO bindings. The localhost allowance in that check now also requires an `http`/`https` scheme: +previously it inspected only the host, so a `javascript://localhost/...` URL parsed with hostname `localhost` and slipped through. + +As defense in depth, the logout request builder re-checks the SLO scheme at the point of use, so a connection that already carries +a malicious SLO URL can never emit it into the `/api/logout` form or redirect. Existing connections that carry a malicious SLO URL +should still be recreated or updated so the stored value is revalidated. + +### Validate the post-logout redirect URL in SAML Single Logout + +The unauthenticated `GET /api/logout` endpoint accepted a `redirectUrl` query parameter and, after the identity provider completed +Single Logout, redirected the browser to it without checking it against the connection's configured redirect URL allow-list. An +attacker could send a victim a logout link with an arbitrary `redirectUrl` and have Polis redirect them to an external origin +(open redirect, CWE-601). + +Polis now validates `redirectUrl` against the connection's allow-list when the logout request is created and rejects a disallowed +or malformed value with `403 Redirect URL is not allowed.` — the same check the OAuth authorize flow applies to `redirect_uri`. If +a stored value no longer matches the connection resolved from the logout response, the callback falls back to the connection's +`defaultRedirectUrl`. + +This affects self-hosted Polis (OSS and OEL). The Ory Network gateway does not expose `/saml/api/logout` and is not affected.