DATE:
AUTHOR:
Ory Team
Ory Network Ory Hydra Ory Kratos Ory Keto Ory Polis Ory Elements Ory Enterprise License

Ory Network, Ory Hydra, Ory Kratos, Ory Keto, Ory Polis, Ory Elements v26.3.7 released

DATE:
AUTHOR: Ory Team

Ory Network

Stricter validation for the Ory Permission Language

Ory Keto now rejects OPL mistakes it previously accepted silently, most importantly a name declared as both a relation and a permit in one namespace. In that case, the relation shadows the permit, so the permit is never evaluated and checks can return wrong results. Other more obvious mistakes include duplicate namespace or relation declarations, or references that mix up relations and permits.

For backward compatibility, Ory Keto continues to load and run existing configurations with the relation/permit collision, but a future release will reject them. Configurations with the other mistakes, such as duplicate declarations, fail to load after upgrading.

Before upgrading a self-hosted deployment, validate your configuration with /opl/syntax/check on the new version, or start the new version against your configuration in a test environment, and fix reported errors — for example by renaming a permit that clashes with a relation, or removing duplicate relations from the OPL, and updating the application code accordingly.

The Console validates email header names as you save

Saving the email configuration with an invalid header name; a space, a colon, or a character your delivery channel doesn't allow; now shows an error on that row instead of silently dropping it. SMTP and HTTP delivery are checked against their own rules, so a header name that would break HTTP webhook delivery is caught in the form before it's stored.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Hydra

Prevent cross-client consent verifier redemption

An attacker who obtained a live consent verifier for another user's accepted flow could redeem it for a different OAuth 2.0 client. This could yield an authorization code for the attacker's client carrying the user's subject.

Exploitation required an accepted, unexpired, unconsumed consent verifier and the matching browser and CSRF context. An attacker could not use a verifier from their own flow. Obtaining the required verifier therefore depended on a separate compromise or leak in the trusted handoff between Hydra, the consent application, and the user's browser.

Hydra now binds the verifier to the OAuth 2.0 client resolved for the authorization request. Self-hosted Ory Hydra and Ory Enterprise License users should upgrade to a version containing this fix. The fix is deployed across the Ory Network, and Ory Network users do not need to take action.

Hydra also rejects a mismatched login verifier before recording authentication session state. The login-verifier path did not yield an authorization code because Hydra rejected the client mismatch later in the flow, but it could apply session or subject-mapping side effects before that rejection.

This change also allows POST authorization requests to carry all parameters in the URL-encoded request body. Hydra previously rejected such requests with invalid_client after a login or consent verifier was issued.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Kratos

Force sessions to AAL2 with session.whoami.required_aal

Setting session.whoami.required_aal: aal2 now forces every session to reach AAL2 before it is treated as valid. Users who already have a second factor are sent through the existing ?aal=aal2 step-up. Users who do not have a second factor are redirected to the settings flow to enroll one, instead of being silently allowed through.

While a session still owes a second factor, its settings flow becomes enrollment-only: it carries a new UI message (ID 1050024) telling the user to set up a second factor, offers only the second-factor enrollment methods (TOTP, lookup secrets, and WebAuthn when configured for MFA), and rejects a submission for any other method. Native and API clients receive a show_settings_ui continue_with item on the login response and a new error ID session_aal2_enrollment_required alongside the existing session_aal2_required. The required_aal setting on the settings flow itself is unchanged and still accepts only aal1 or highest_available.

GET /sessions continues to require the configured AAL, as it did before. DELETE /sessions and DELETE /sessions/{id} deliberately do not: signing other devices out is what a user needs after losing a second factor, so revocation stays available to a session that has not yet met the requirement. Such a revocation is recorded in the log with the configured required_aal and the session's own AAL.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Keto

Stricter validation for the Ory Permission Language

Ory Keto now rejects OPL mistakes it previously accepted silently, most importantly a name declared as both a relation and a permit in one namespace. In that case, the relation shadows the permit, so the permit is never evaluated and checks can return wrong results. Other more obvious mistakes include duplicate namespace or relation declarations, or references that mix up relations and permits.

For backward compatibility, Ory Keto continues to load and run existing configurations with the relation/permit collision, but a future release will reject them. Configurations with the other mistakes, such as duplicate declarations, fail to load after upgrading.

Before upgrading a self-hosted deployment, validate your configuration with /opl/syntax/check on the new version, or start the new version against your configuration in a test environment, and fix reported errors — for example by renaming a permit that clashes with a relation, or removing duplicate relations from the OPL, and updating the application code accordingly.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Polis

Require client authentication when exchanging IdP-initiated authorization codes

Polis now requires client authentication on the OAuth token endpoint for authorization codes issued by IdP-initiated SSO flows. A token request must supply a valid client_secret. Requests that omit all credentials are rejected with 401 Please specify client_secret or code_verifier.

Previously, authorization codes from IdP-initiated flows had no associated session, and every client-authentication branch on the token endpoint was guarded by that session. A token request that omitted client_id and client_secret skipped all checks and was issued an access token. Anyone who intercepted such a code could exchange it without knowing the application's client secret. SP-initiated flows were not affected. Integrations that already send client credentials continue to work unchanged.

Breaking change. Any integration that exchanges an IdP-initiated authorization code without a client_secret (relying on the previous behavior) now receives 401. To restore access, send client_secret on the token request. If you do not use per-connection secrets, send the deployment's CLIENT_SECRET_VERIFIER value (which defaults to dummy when unset).

Bind Google Directory Sync OAuth callback to a single-use state

The Google Directory Sync OAuth flow now uses an unguessable, single-use state handle that is stored server-side and bound to the directory the flow was started for. The callback resolves the directory from that stored record and deletes it on use, instead of trusting a directoryId supplied in the state query parameter.

Previously the state carried the directoryId with no binding, so a crafted or replayed callback could install one tenant's Google Workspace tokens onto another tenant's directory (CWE-352). Callbacks with a missing, unknown, or already-used state are now rejected. Any authorization flow started before the upgrade must be restarted from the directory's authorization URL.

Clean up partially initialized controllers when startup fails

If constructing the Polis controllers fails after the database has been created -- for example when a later initialization step throws -- controllers() now closes the database and stops the background workers it already started before rethrowing, instead of leaving their timers and connection pools on the event loop with no close() for the caller to invoke. The database wrapper's close() now also always clears its stats-reporting interval, even if the underlying driver close rejects.

This affects self-hosted Polis.

Fix SCIM authentication bypass on non-SCIM directories

Public SCIM endpoints now reject requests to directories that do not use SCIM (such as Google), and reject any request that presents an empty or missing bearer token. Previously, directory types that store an empty SCIM secret were authenticated by a request that also carried no token, because a constant-time comparison of two empty values returns true. An attacker who knew a directory id could read and write the tenant's SCIM users and groups without credentials. This affected the self-hosted and OEL distributions; the endpoint is not exposed on the Ory Network.

Fixed a security issue

This release includes a fix for a security issue. Additional details will be shared with customers directly.

Release background timers on shutdown and await webhook batch processing

Embedding Polis and calling the controllers' close() now stops every background timer it started, so the host process can shut down cleanly. Previously close() left the SSO traces daily cleanup timer and the in-memory store's TTL cleanup timer running, which kept the Node.js event loop alive after shutdown and forced callers to terminate the process explicitly.

The webhook batch processor now waits for a batch to finish before it returns. EventProcessor.process() previously started processing without awaiting it, so its error handling never ran and the "already running" guard could clear before a batch completed, allowing overlapping runs. It now awaits the batch, logs any failure through the configured logger, and holds the run guard for the full duration.

This affects self-hosted Polis.

SAML IdP Single Logout (SLO) for Identity Federation

Polis can now act as a SAML IdP for Single Logout. When a Service Provider sends a SAML LogoutRequest, Polis initiates an OIDC RP-Initiated Logout against the upstream OIDC provider and returns a signed SAML LogoutResponse to the SP. This mirrors the existing login flow.

SAML responses are protected against assertion replay

Jackson now rejects a SAML response whose assertion has already been used. Each consumed assertion is recorded until it expires, so a captured response can no longer be submitted more than once to obtain a fresh login.

This closes a gap in the IdP-initiated flow. Those responses carry no in-flight request to consume, so previously the same signed response could be replayed within its validity window to mint a new authorization code for the victim on each submission. Service-provider-initiated flows were already protected because their one-time request state is consumed on success.

The anti-replay binding (InResponseTo) is now read only from the signed part of the assertion, so it can no longer be bypassed by editing the unsigned response wrapper.

Single Logout responses now verify the Identity Provider signature before acting on the response status or request ID, so an unsigned or tampered logout response is rejected outright.

Setup links are now scoped to their service

A setup link is now rejected with 403 when it is used for a service it was not issued for. A Single Sign-On setup link can only manage SSO connections, and a Directory Sync setup link can only manage directories.

Before, the service on a setup link was not checked at the setup routes, so the holder of a Directory Sync link could create SSO connections for the tenant (and the reverse), which is an authorization bypass. The SSO connection route also no longer accepts the tenant, product, or redirect URLs from the request body; it takes them only from the setup link.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Elements

Ory Elements handles rate limited responses

Ory Elements now handles 429 Too Many Requests responses: the pre-built components show a form message and emit a new rate_limited event on the onError callback, instead of leaving the form in a frozen state.

All other unexpected HTTP errors now emit a new unexpected_error event before the error is thrown, so integrators can observe them without changing the existing throwing behavior.


This will be available in the next Ory Open Source release.


Ory Oathkeeper and Ory Terraform

No significant changes in this release.

Powered by LaunchNotes