Sep 16, 2026·7 min read·7 visits
The OpenFGA ListUsers API fails to apply logical exclusions during intersection resolution if the base relation utilizes a type-bound public wildcard, causing excluded users to be returned as authorized.
An authorization-decision over-inclusion vulnerability exists in the OpenFGA authorization engine. The flaw manifests within the `ListUsers` API evaluation path when evaluating complex relationship intersections containing exclusions. Under certain configurations involving wildcards, the exclusion is bypassed, leading to incorrect permission lists.
OpenFGA is an open-source relationship-based access control (ReBAC) engine designed to model and resolve fine-grained authorization policies. It implements the concepts outlined in the Google Zanzibar paper, resolving complex permission graphs via structured API requests such as Check, Expand, and ListUsers. The ListUsers API allows downstream applications to retrieve a complete list of users who possess a specific relation to an object, facilitating bulk synchronization and view-filtering logic.
In authorization models that define nested policy logic, relations frequently incorporate set operators like intersection (and) and difference/exclusion (but not). The core architecture relies on graph-traversal algorithms to evaluate user sets across these relations. A logical breakdown within the traversal mechanics of the ListUsers engine allows specific users to bypass exclusion rules under targeted model architectures.
This flaw resides in the sub-expression evaluation logic of the evaluation solver. Specifically, the vulnerability is classified under CWE-863 (Incorrect Authorization) and CWE-281 (Improper Preservation of Permissions). Because downstream microservices depend on the accurate outputs of ListUsers to enforce access restrictions, this bypass can lead to unauthorized information disclosure or privilege escalation within the client application.
The root cause of CVE-2026-61709 lies in how OpenFGA's ListUsers solver joins user sets during intersection evaluation. The engine uses a tracking structure to gather users returned by different branches of an intersection. During the evaluation of an exclusion expression like (org_member but not banned), the solver tracks explicitly excluded users in a local state map called excludedUsersMap. Simultaneously, the solver evaluates other branches of the intersection, such as an active status check.
When a base relation like org_member is assigned a type-bound public wildcard tuple (e.g., user:*), any queried user implicitly matches this relation. If an administrative actor explicitly excludes a specific user (e.g., user:eve) by adding them to the banned relation, the engine records user:eve in its excludedUsersMap. However, if user:eve also has a direct, concrete relationship tuple matching the secondary intersection criteria (e.g., user:eve is active), a logical collision occurs.
During the intersection expansion phase in the expandIntersection function, the engine aggregates candidate matches across all operands inside a tracking map called foundUsersCountMap. When compiling counts, the solver counted the wildcard match and the concrete active match. The engine then verified if the sum of these matches satisfied the operand count threshold. Because the function completely omitted a verification step against the excludedUsersMap during this counting phase, it incorrectly validated the excluded user as satisfying the entire intersection.
To trace the flaw, we examine the vulnerable code path in pkg/server/commands/listusers/list_users_rpc.go. In versions prior to 1.18.1, the expandIntersection method processed candidate users inside the foundUsersCountMap as follows:
// Vulnerable execution block in list_users_rpc.go
for key, count := range foundUsersCountMap {
// Compare the number of times the specific user was returned for
// all intersection operands plus the number of wildcards.
if count == targetCount {
results = append(results, key)
}
}The code directly appended the user to the results slice based solely on the match count reaching the targetCount threshold. It completely failed to reference the excludedUsersMap that was populated during the evaluation of the left-hand operand (org_member but not banned).
To correct this defect, the OpenFGA maintainers implemented a lookup within the loop to check whether the candidate user resides in the exclusion map. The patched logic in Commit 171806c93b86bca29e0212ceb8b6ee9c48eb9ac3 introduces this safeguard:
// Patched logic in list_users_rpc.go
for key, count := range foundUsersCountMap {
// A user may have already been explicitly excluded by an operand, and cannot satisfy the intersection
// e.g. the banned relation here: `(member but not banned) and active`
if _, excluded := excludedUsersMap[key]; excluded {
continue
}
// Compare the number of times the specific user was returned for
// all intersection operands plus the number of wildcards.
if count == targetCount {
results = append(results, key)
}
}By adding this conditional check, any user present in excludedUsersMap is immediately skipped. The check ensures that even if an excluded user satisfies other intersection operands and accumulates matching counts, they are correctly filtered out from the final results.
Exploiting CVE-2026-61709 requires a specific set of preconditions within the target OpenFGA store. An attacker does not inject malicious payloads; instead, the exploit is passive and triggers when the authorization model and relation states align in a vulnerable configuration.
First, the target model must define an intersection where one of the branches involves a subtraction/exclusion (e.g., (member but not banned) and active). Second, the system must have a wildcard assignment for the base relation, such as user:* assigned to member. Third, the target user (the attacker or unauthorized actor) must be explicitly banned via the exclusion relation. Finally, the target user must have a concrete tuple establishing their relationship to the other operand of the intersection (e.g., active).
When a downstream backend service calls the ListUsers API to find all authorized viewers, the vulnerable OpenFGA engine executes the logic. Due to the failure to check the exclusion map in expandIntersection, the engine incorrectly includes the banned user in the response array. The downstream application trusts this response and permits the banned user access to the restricted resource.
The security implications of CVE-2026-61709 depend heavily on how the calling application utilizes the output of the ListUsers API. If an application uses ListUsers to build access control lists (ACLs), sync internal caching databases, or populate user-facing lists of permitted operators, the logical bypass will translate directly into a security boundary failure.
The vulnerability is assigned a CVSS v3.1 score of 5.3 (Medium Severity). The attack complexity is rated as High (AC:H) because exploitation relies on a complex configuration of relationship definitions and concurrent tuple states. Privileges required are Low (PR:L), as normal platform operations can lead to the creation of the required tuples. The confidentiality impact is rated as High (C:H) because a restricted or explicitly banned user can gain unauthorized access to data by being returned as a valid user of the relation.
No integrity or availability impacts exist within OpenFGA itself. However, downstream systems executing decisions based on the corrupt permission list are highly susceptible to further integrity and confidentiality failures.
The primary and recommended mitigation is upgrading the OpenFGA deployment to version 1.18.1 or higher. This update applies the necessary code-level patch to the expandIntersection solver, preventing exclusions from being bypassed during wildcard processing.
If upgrading immediately is not possible, security teams can implement several defensive strategies. First, review existing authorization models to identify instances where wildcards are used as the base of an exclusion inside an intersection. These models can be refactored to use explicit group memberships or direct user relations instead of type-bound public wildcards (user:*).
Second, configure downstream systems to use the OpenFGA Check API for critical runtime authorization checks instead of relying on ListUsers. The Check API employs a different traversal path that evaluates individual user graphs and is not affected by this specific logic bypass. Additionally, implement automated schema analysis to audit DSL schemas for vulnerable intersection patterns during CI/CD cycles.
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
OpenFGA OpenFGA | < 1.18.1 | 1.18.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-863 / CWE-281 |
| Attack Vector | Network (AV:N) |
| CVSS Base Score | 5.3 (Medium) |
| EPSS Score | Not Assigned |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
| Impact | Confidentiality (High) |
The software does not prove or improperly preserves the permissions of an actor, leading to incorrect authorization.
A cross-site scripting (XSS) vulnerability was identified in @nuxtjs/mdc prior to version 0.22.1. Gaps in the HTML/SVG attribute verification and URL protocol parsing allow unauthenticated remote attackers to bypass the application's sanitization routines. By embedding malicious SVG links or data-encoded iframe elements within Markdown, attackers can execute arbitrary JavaScript in the victim's browser context.
CVE-2026-58657 is a critical stored CSS injection vulnerability in Grav CMS's media processing pipeline. By exploiting improper sanitization of image dimensions in the resize helper, low-privileged users with page editing permissions can inject arbitrary CSS styles. This can lead to visual defacement, UI redressing, and indirect data exfiltration.
An authorization bypass vulnerability exists in the djust framework (djust-org/djust) prior to version 1.0.7. The framework fails to enforce standard Django view-level authorization mechanisms, such as AccessMixins or dispatch decorators, when mounting reactive views over stateful transport layers (WebSockets and Server-Sent Events). Unauthenticated or low-privileged attackers can establish persistent connections to mount arbitrary protected views and execute state-changing event handlers.
CVE-2026-61560 is a critical security vulnerability in the @zereight/mcp-gitlab Server-Sent Events (SSE) server. By utilizing default, unauthenticated route setups and exposing vulnerable administrative tools, remote attackers can execute path traversal attacks to read internal process variables and hijack GitLab operations.
A critical access control vulnerability in djust prior to 1.0.7 exposes diagnostic endpoints and remote method-invocation capabilities to unauthorized network actors. The vulnerability arises due to decoupling IP boundary validation into an opt-in middleware that was omitted from official configuration documentation, leaving views to rely solely on the status of Django's DEBUG flag.
The multi-tenant isolation mechanism in djust prior to version 1.0.7 fails open on active WebSocket and Server-Sent Events (SSE) connections. Because the tenant context is stored in thread-local variables and initialized exclusively via HTTP middleware, asynchronous event loops executing ASGI/WebSocket code paths do not carry the resolved tenant identifier. When queries are executed without this context, the default database manager fails open, allowing authenticated users of any tenant to query and read sensitive rows across all other tenant accounts.