Two advisories were published against Open WebUI on 10 September 2026, against the same component, describing the same defect. Its OAuth token exchange endpoint issued a session without running the checks its normal OAuth login callback runs. One variant skipped the email domain allowlist. The other skipped role management entirely.
Neither variant creates an account. Neither raises a role. Both break something administrators rely on daily: revocation.
An admin who narrows a domain allowlist expects the excluded users to lose access at their next sign-in. The login callback does refuse them. Token exchange kept issuing sessions, so those users kept working access. An admin who relies on provider roles expects a user to lose admin as soon as the provider stops reporting the role. The callback enforces that. Token exchange never re-evaluated, so the user kept admin after the identity provider had already revoked it.
The class is not exotic and it is not specific to this product. It is the second door problem: two code paths that can both mint a session, with the security checks written into only one of them.
What actually happened
Open WebUI is a self-hosted AI platform, the kind of deployment that sits in front of a company’s models, prompts and internal knowledge. It supports OAuth single sign-on and, since 0.8.0, a token exchange endpoint for turning a provider access token into a platform session.
Both advisories are rated medium at CVSS 6.5, both are CWE-863, incorrect authorization, and both were published on 10 September. They are worth reading together because they are two instances of one design flaw, not two coincidences.
| Advisory | Check missing at token exchange | Fixed in |
|---|---|---|
| GHSA-4qg5-cxx4-g927 (CVE-2026-88005) |
The email domain allowlist (OAUTH_ALLOWED_DOMAINS) |
0.9.0 |
| GHSA-wvm9-9g5j-623f (CVE-2026-88006) |
OAuth role management, including demotion (OAUTH_ALLOWED_ROLES, OAUTH_ADMIN_ROLES) |
0.11.1 |
The preconditions are honest and narrow, and I will state them rather than bury them. Token exchange is disabled by default, so a stock deployment is not affected. The domain variant needs an allowlist configured to something other than a wildcard. The role variant needs role management switched on. Both need an already-linked account and a valid, unexpired provider access token. The endpoint never creates an account, so a valid token for an unknown subject is rejected.
That framing matters because it tells you who was exposed. Not every deployment, and not a stranger. The people affected are exactly the people the administrator had just tried to remove.
The two doors, side by side
A normal OAuth login and a token exchange both end in the same place: a session for an existing account. They are different paths to the same outcome, which is precisely why they have to run the same checks.
The callback runs the checks an administrator configures. It applies the domain allowlist, so an account from a domain that is no longer permitted is refused. It runs role management, so a user whose provider no longer reports the required role is denied, and a user whose provider demotes them is demoted locally too.
The token exchange endpoint validated the provider access token and issued a session. It did not re-run either check. The provider token was genuine, the account was linked, and the request was satisfied.
Read the bottom two rows of that matrix carefully, because they define what this is not. Neither path creates accounts, and neither raises a role. This is not privilege escalation and it is not an unauthenticated bypass. It is continued access.
That distinction is not a technicality. It is the reason the severity is medium rather than critical, and it is also the reason the flaw is easy to underrate. A user who keeps the access they already had generates no alert, no failed login, and no anomalous action. Nothing in the logs looks like an intrusion, because from the platform’s perspective nothing unusual happened.
Why revocation is harder than it looks
Most teams reason about revocation as a property of the identity provider. Remove the role in the provider, and the provider stops reporting it, so the platform should stop honouring it.
That reasoning holds only if every path that issues a session consults the provider’s current state. The moment a second path issues a session from a token alone, without re-evaluating policy, revocation becomes a property of the least careful path rather than of the identity provider.
Token exchange is not a strange design. RFC 8693 defines OAuth 2.0 token exchange for exactly this purpose: taking a token from one context and obtaining a token for another. It exists because it is useful. The same endpoint pattern appears in API gateways, service meshes, internal developer platforms and dashboards that accept a token from a browser extension or a CLI.
What makes this case instructive is how ordinary the mistake is. Nobody decided to skip the domain allowlist. A new endpoint was added alongside an existing one, and the checks that lived in the existing path were not carried across. The role variant compounds it: the endpoint did not just miss an allow check, it missed the demotion logic, which is the part that makes revocation time-sensitive.
Where the gap opens
The timeline below is the part I would put in front of an executive, because it shows how a perfectly correct control produces no effect.
Every step in that sequence is a reasonable expectation. The gap is at step three, and it stays invisible because nothing in the system reports it. The administrator sees a successful revocation. The user sees a working session. Both are correct about what they can observe.
The control that is worth building
The transferable output of reading these two advisories together is not a patch note. It is a question you can ask about any system that issues sessions: how many paths can mint a session, and does each one run the same checks?
I call the answer a path parity inventory. It is deliberately small, and it works for SSO-fronted SaaS, internal developer platforms, API gateways with token endpoints, and any AI platform sitting behind corporate identity.
| Step | What to do | What you are looking for |
|---|---|---|
| 1. Enumerate | List every route that can return a session, a cookie or a bearer token. Include exchanges, refreshes, CLI and device flows, impersonation, shared links and service tokens. | Paths you did not know existed, especially ones added recently next to an older, hardened path. |
| 2. Attribute | For each path, name the checks it runs. | Domain allowlists, group and role checks, demotion, MFA state, account status, tenant boundaries. |
| 3. Diff | Compare each path against the strongest one. | The missing check is the vulnerability. Two advisories here exist purely because of a diff. |
| 4. Test revocation | Revoke a role or narrow a domain, then attempt every path rather than the one you normally use. | Any path that still issues a session. Test the ones you did not build. |
| 5. Fix structurally | Move issuance behind one choke point that always applies policy. | Fewer places for a check to be forgotten. This is what prevents the next advisory. |
Step five is the one that matters beyond this incident. Both upstream fixes add the missing check to the second path, which is correct and immediate but leaves the check duplicated across paths. A duplicated control is a control that will diverge again the next time someone adds a third path. The durable fix is a single issuance function that every path calls, so policy is applied once and cannot be omitted by omission.
This is a different failure from the one I wrote about yesterday. There, a control existed and was bypassed because it was applied at the wrong layer. Here, no control was applied at all on one path. Both end in the same uncomfortable place: a security property you believed you had, and the unaudited code path that decides whether you actually do.
What this does not prove
Three limitations, stated plainly. First, a default deployment is not affected, because token exchange is off and role management is off by default, so the population at risk is deployments that deliberately enabled both SSO features. Second, neither variant grants new access: no account creation, no role elevation. Third, and most importantly, the advisories were published with fixes and I have not conducted independent testing against a running instance. What I can defend is the mechanism and the architectural lesson, both of which come from the primary advisories and the code paths they describe.
I would also resist the temptation to read this as a story about one product’s mistake. The pattern needs a specific shape to occur: more than one way to obtain a session, policy checks embedded in the flow rather than in a shared function, and a product that has grown new entry points over time. That shape is common. It is common in AI platforms precisely because they are young products that added SSO, then added token exchange, then added role management, each one next to what already existed.
Why platforms accumulate session paths
It is worth understanding why this shape appears so often, because the answer is not carelessness. It is growth.
A product starts with one login. Eventually someone wants to authenticate from a CLI, so a device flow or a personal access token appears. Then a browser extension needs to act on behalf of the user, which calls for token exchange. Then an IDE plugin wants the same, so it reuses that endpoint. Then service-to-service access is needed, so a client credentials path is added. Then an administration console needs to impersonate a user for support, which is a fifth path.
Every one of those is a reasonable request, and each was probably built as a small, well-scoped addition next to code that already worked. The checks that protect access tend to live in the first path, written when there was only one. The new path validates what it was built to validate, which is usually the credential in front of it, and it does not know about policy that was written elsewhere.
AI platforms are unusually prone to this. They adopt SSO early because they hold sensitive material and enterprise buyers insist on it. They then grow a wide surface of integrations, because the entire value proposition is connecting a model to tools, editors, agents and internal systems. Each integration is a caller that needs a session. The result is a product with several doors and a security model documented for one of them.
A practical way to look for it
The inventory is easier to build than it sounds, because session issuance is rarely spread evenly through a codebase. It is concentrated in routing and middleware layers, and it is usually visible by name.
Start from the artefacts rather than the code. List the cookies your platform sets and the token types it issues. For each, note where it is created. Then map those creation points to the routes that reach them. In most applications this yields a short list, and the interesting entries are the ones that were added most recently, because a new path beside an old one is the exact geometry of both advisories here.
Then do the same for the checks. Search for the configuration names an administrator actually sets, such as a domain allowlist or a role mapping, and see which code paths read them. Any path that issues a session without reading the policy configuration is a candidate, and a configuration option that only one code path honours is a finding regardless of whether it is currently exploitable.
Finally, describe the intended behaviour in one sentence and test it directly. For the role variant here, that sentence is: a user whose provider no longer reports the required role loses that role on the platform. Written down, it is obvious. Tested against every path, it fails visibly. The reason nobody caught it earlier is that the sentence was never written, and the only path anyone tested was the one that worked.
Detecting it before an advisory tells you
You cannot detect this from the answer to a single request, because the request is legitimate and the token is real. Detection has to come from comparing paths and from watching for the specific blind spot the pattern creates.
| Signal | Why it matters |
|---|---|
| Sessions issued that were not preceded by an interactive login | Token exchange is non-interactive by design. Baseline how often it happens, then alert on change. |
| A user who is not in the identity provider’s current group or role set still holding an active session | This is the exact condition the role variant produced. It is queryable if you store the provider-side and platform-side role together. |
| Revocations that produce no corresponding termination or downgrade event | A revocation with no downstream effect is the fingerprint. Log an expectation, then verify it happened. |
| A session path that appears in logs but not in your inventory | The most reliable early warning. If logging shows a route you have not assessed, you have found the gap before someone else does. |
The third row is the one I would implement this week, and it is cheap. When an administrator revokes a role, record the expectation: this user should lose this privilege. Then assert it. In the cases described here, the expectation would have been recorded and never met, and the discrepancy would have surfaced immediately rather than in a security advisory.
What I take from this
Two medium advisories about an AI platform’s SSO are not dramatic. They are useful for a reason that has nothing to do with severity. They show a class of failure that produces no alerts, no exploit traffic and no unusual behaviour, and that survives because the system’s own reporting says everything worked.
The practical lesson is narrow enough to act on. Revocation is not a property of your identity provider. It is a property of every path in your platform that can turn a credential into a session, and it is only as sharp as the least careful of them.
If you run a self-hosted AI platform behind corporate SSO, the version matters: Open WebUI users should be on 0.11.1 or later to have both checks present at token exchange. If you cannot upgrade immediately, the compensating control is to disable token exchange until you can, which removes the second door entirely.
One more observation from reading the two advisories side by side. The first was fixed in 0.9.0 and the second in 0.11.1, which means an administrator who upgraded promptly after the first advisory and stopped there was still exposed to the second. Remediation that tracks advisories one at a time will always lag a root cause. When two disclosures share a cause, the useful response is to fix the cause and re-audit everything with the same shape, rather than to install two patches and consider the matter closed.
Frequently asked questions
What was the Open WebUI token exchange vulnerability?
Open WebUI’s OAuth token exchange endpoint issued a platform session for a provider access token without running the checks its normal OAuth login callback runs. Two variants were published on 10 September 2026: one skipped the email domain allowlist (CVE-2026-88005, fixed in 0.9.0), and one skipped OAuth role management and demotion (CVE-2026-88006, fixed in 0.11.1). Both are CWE-863 and rated medium at CVSS 6.5.
Was it privilege escalation?
No. The advisories are explicit that the endpoint cannot create an account or raise a role. The impact is continued access: a user whose domain was removed, or whose role the provider had already revoked, kept working access at their existing account and role, including an admin role the provider had revoked. That is why the severity is medium and why it is easy to miss, since no unusual action appears in the logs.
Who was affected?
Deployments that enabled ENABLE_OAUTH_TOKEN_EXCHANGE, which is off by default, plus the relevant policy feature: a domain allowlist other than a wildcard for the first variant, and OAuth role management for the second. The endpoint also requires an already-linked account and a valid, unexpired provider access token. A default deployment is unaffected.
What is the general lesson for systems that issue sessions?
Enumerate every path that can turn a credential into a session, and compare the checks each one runs. A check that exists on one path and not another is not a control, and revocation is a property of the set of paths rather than of the identity provider. The durable fix is a single issuance function that always applies policy, instead of the same check duplicated across each path.
How do you test for this class?
Revoke a role or narrow a domain allowlist in the identity provider, then attempt every session path rather than the one you normally use. Confirm that each path refuses, and confirm that an existing session is terminated or downgraded. Also log the expectation at revocation time and assert that it was met, so an unmet revocation surfaces as an operational discrepancy instead of as a future advisory.
What is CWE-863 and why does it fit here?
CWE-863 is incorrect authorization: the software performs an authorization check but does not correctly perform it, or omits it where it is required. Here the callback performed the check correctly and the token exchange endpoint omitted it, which is the omission case, and it produces exactly the effect the weakness describes: a request that should have been denied succeeds.
Sources
- GHSA-4qg5-cxx4-g927, Open WebUI: users denied by the OAuth domain allowlist can still sign in via token exchange (CVE-2026-88005)
- GHSA-wvm9-9g5j-623f, Open WebUI: users denied by the OAuth role policy can still sign in via token exchange (CVE-2026-88006)
- RFC 8693, OAuth 2.0 Token Exchange
- CWE-863, Incorrect Authorization
- Open WebUI 0.11.1 release, which contains the role management fix
Related reading on this site: the tool boundary is covered in The MCP Server Is the Attack Surface: Three Ways Agent Tooling Fails, and the containment view of agent authority is covered in The Agent Disabled Its Own Sandbox: CVE-2026-82533 and the Containment Trust Boundary.
One next action
If you run an agent that executes tools, AgentCrash is an open-source harness that crash-tests it in a synthetic workplace: synthetic tools, visual evidence and regression tests. It is built for exactly this problem, turning a containment claim into something you can run against the system instead of arguing about it in review.