- DATE:
- AUTHOR:
- Ory Team
Ory Network, Ory Hydra, Ory Kratos, Ory Keto, Ory Polis, Ory Elements v26.3.7 released
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.