Sep 16, 2026·7 min read·31 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.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.