high-flower-66335
06/30/2025, 11:56 AMimport { Namespace, Context, SubjectSet } from "@ory/permission-namespace-types"
class User implements Namespace { }
class Tenant implements Namespace {
related: {
admins: User[]
members: User[]
}
permits = {
isAdmin: (ctx: Context): boolean =>
this.related.admins.includes(ctx.subject),
isMember: (ctx: Context): boolean =>
this.related.members.includes(ctx.subject),
}
}
class Document implements Namespace {
related: {
owners: User[]
tenants: Tenant[]
}
permits = {
read: (ctx: Context): boolean =>
this.related.tenants.traverse((tenant) => tenant.permits.isMember(ctx)) ||
this.permits.write(ctx),
write: (ctx: Context): boolean =>
this.related.tenants.traverse((tenant) => tenant.permits.isAdmin(ctx))
}
}
created relationships:brief-egg-61061
06/30/2025, 11:56 AMbrief-egg-61061
06/30/2025, 11:57 AM.traverse
method to check permissions transitively through related tenants. This is a common and supported pattern in Ory Keto/Permissions, as shown in the official documentation and examples, where permissions are checked through parent relationships or group memberships using .traverse
or .transitive
methods[Define permissions in the OPL](https://www.ory.sh/docs/keto/modeling/create-permission-model#define-permissions-in-the-opl). However, there are a few important considerations and known issues: 1. Correctness of `.traverse`/`.transitive`: Your usage of .traverse((tenant) => tenant.permits.isMember(ctx))
and similar patterns is correct according to the OPL specification. This is how you would check if the subject is a member or admin of any related tenant, and thus should have read
or write
access to the document. 2. Known Issues with Transitive Checks: There are open issues in the Ory Keto project where .traverse
or .transitive
checks do not always work as expected, especially in certain versions or configurations. For example, [this issue](https://github.com/ory/keto/issues/1330) describes a scenario where a user is a viewer of a folder, and a document is a child of that folder, but the transitive permission check returns false
when it should return true
. This suggests that there may be bugs or limitations in the current implementation of transitive permission checks. 3. Version and Configuration: Some of these issues are version-dependent. If you are running into unexpected results, check your Ory Keto version and consider testing with the latest release, as fixes and improvements are ongoing. 4. Workarounds: In some cases, users have found that explicitly repeating the transitive logic in each permission (rather than delegating via this.permits
) can work around the issue. For example, instead of calling this.permits.write(ctx)
inside read
, you might inline the logic again, as described in [this issue](https://github.com/ory/keto/issues/1626). Summary: Your OPL model is correct in its use of .traverse
for transitive permission checks. If you are experiencing incorrect permission results, it is likely due to a known issue in the Ory Keto implementation of transitive checks. You may want to try inlining the logic or upgrading to the latest version. If the problem persists, consider following or contributing to the relevant GitHub issues for updates and workarounds. If you provide the specific relationships you created and the permission check you are running (with expected vs. actual results), I can help analyze further based on the documented behavior and known issues. References: - [OPL traverse not working issue](https://github.com/ory/keto/issues/1330) - [OPL permissions inconsistencies issue](https://github.com/ory/keto/issues/1626) - [Define permissions in the OPL](https://www.ory.sh/docs/keto/modeling/create-permission-model#define-permissions-in-the-opl)high-flower-66335
06/30/2025, 12:01 PMhigh-flower-66335
06/30/2025, 12:02 PMbrief-egg-61061
06/30/2025, 12:02 PMbrief-egg-61061
06/30/2025, 12:02 PM.traverse
or calling this.permits.*
inside another permission), this is a known area where inconsistencies have been reported. Specifically, users have found that chaining permissions (e.g., this.permits.write(ctx)
inside another permit) can sometimes yield incorrect results, while inlining the transitive logic directly works as expected. Recommended Change: Instead of delegating to another permit (e.g., this.permits.write(ctx)
), directly inline the logic for the transitive check in each permission. For your example, rewrite the read
and write
permissions like this: typescript class Document implements Namespace { related: { owners: User[] tenants: Tenant[] } permits = { read: (ctx: Context): boolean => this.related.tenants.traverse((tenant) => tenant.permits.isMember(ctx)) || this.related.tenants.traverse((tenant) => tenant.permits.isAdmin(ctx)), write: (ctx: Context): boolean => this.related.tenants.traverse((tenant) => tenant.permits.isAdmin(ctx)) } }
This approach avoids chaining through this.permits.write(ctx)
and instead repeats the logic, which has been shown to work around the current limitations in Ory Network's permission engine[OPL permissions have inconsistencies](https://github.com/ory/keto/issues/1626). Summary: - Inline the transitive logic in each permission instead of calling another permit. - This workaround is necessary due to current inconsistencies in Ory Network's handling of chained permission checks. If you continue to experience issues, monitor the referenced GitHub issue for updates, as improvements are planned for this area.high-flower-66335
06/30/2025, 12:08 PMbrief-egg-61061
06/30/2025, 12:08 PMbrief-egg-61061
06/30/2025, 12:09 PM.traverse
is used to check transitive relationships, such as nested group membership or role assignment. The OPL specification and examples show similar usage for hierarchical and recursive permission checks[Define permissions in the OPL](https://www.ory.sh/docs/keto/modeling/create-permission-model#define-permissions-in-the-opl). However, there are known issues with transitive checks and recursive traversal in Ory Keto and Ory Network, especially when using .traverse
or chaining permission checks. Users have reported that, in some cases, the expected transitive permission is not granted even though the relationships are set up correctly. This is particularly true for recursive group membership and when chaining permissions (e.g., calling this.permits.has(ctx)
inside another permission)[OPL traverse not working issue](https://github.com/ory/keto/issues/1330), [OPL permissions have inconsistencies](https://github.com/ory/keto/issues/1626). What you can do: - Inline the logic: If you experience issues, try to avoid chaining permissions (e.g., avoid calling this.permits.has(ctx)
inside another permission). Instead, inline the logic directly in each permission, as this has been shown to work around some of the current limitations in Ory Network. - Limit recursion depth: Deep or complex recursive traversals may hit performance or correctness issues. If possible, limit the depth or complexity of your group/role hierarchies. - Monitor for updates: These issues are known and being tracked by the Ory team. If you are affected, keep an eye on the referenced GitHub issues for updates and fixes. Summary: Your model is correct according to the OPL specification, but if you encounter incorrect permission results, it is likely due to current limitations in Ory Network's handling of recursive and transitive permission checks. Inlining logic and simplifying relationships can help as a workaround until these issues are resolved in future releases[OPL traverse not working issue](https://github.com/ory/keto/issues/1330), [OPL permissions have inconsistencies](https://github.com/ory/keto/issues/1626).high-flower-66335
06/30/2025, 12:10 PMbrief-egg-61061
06/30/2025, 12:10 PMbrief-egg-61061
06/30/2025, 12:10 PM.traverse
or chaining permission checks), the best-documented workaround is to inline the transitive logic directly in each permission rather than delegating to another permit (e.g., avoid calling this.permits.has(ctx)
inside another permit). This approach is recommended because there are known inconsistencies when chaining permissions, as reported by users and acknowledged in Ory's GitHub issues. For example, instead of: typescript read: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.readers.traverse((role) => role.permits.has(ctx)),
You would inline the logic from the has
permit directly: typescript read: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.readers.traverse( (role) => role.related.members.includes(ctx.subject) || role.related.groups.traverse((group) => group.related.members.includes(ctx.subject) || group.related.groups.traverse(/* ...and so on, as needed... */) ) ),
This workaround is based on user reports and Ory's own guidance in response to similar issues, where inlining the logic avoids the current limitations of the permission engine in Ory Network[OPL permissions have inconsistencies](https://github.com/ory/keto/issues/1626). Summary of mitigation steps: - Inline transitive logic in each permission instead of chaining permits. - Simplify relationship hierarchies where possible to reduce recursion depth. - Monitor Ory's GitHub issues for updates and fixes to this behavior. If you continue to experience issues, you may want to follow or contribute to the relevant GitHub issues for further updates and guidance from the Ory team.high-flower-66335
06/30/2025, 12:15 PMpermits = {
read: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
// this.related.readers.traverse((role) => role.permits.has(ctx)),
this.related.readers.traverse(
(role) =>
role.related.members.includes(ctx.subject) ||
role.related.groups.traverse((group) =>
group.related.members.includes(ctx.subject) ||
group.related.groups.traverse((group) => group.permits.has(ctx))
)
),
this has syntax error @chilly-king-10285brief-egg-61061
06/30/2025, 12:15 PMbrief-egg-61061
06/30/2025, 12:15 PMgroup.permits.has(ctx)
inside the innermost .traverse
. In Ory Permission Language (OPL), recursive permission calls like this can lead to infinite recursion or are not supported as written. The OPL specification allows for transitive checks, but recursive expansion must be handled carefully and is subject to the engine's limitations[OPL traverse not working issue](https://github.com/ory/keto/issues/1330). What's wrong: - The innermost .traverse((group) => group.permits.has(ctx))
is a recursive call to the same permission, which can cause issues both in OPL parsing and in runtime evaluation. - OPL expects the logic to be expressed in a way that avoids unbounded recursion. How to fix: - Limit the recursion depth explicitly if possible. - If you need to support deeply nested groups, consider flattening your group relationships outside of OPL, or limit the nesting in your model. - In OPL, you can only safely express a fixed depth of traversal by unrolling the logic, for example: typescript permits = { read: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.readers.traverse( (role) => role.related.members.includes(ctx.subject) || role.related.groups.traverse( (group) => group.related.members.includes(ctx.subject) // Do not recurse further, or manually unroll another level if needed ) ), }
This approach is a workaround for the current limitations in Ory Network and OPL, as recursive permission checks are not fully supported and can lead to errors or unexpected results[OPL permissions have inconsistencies](https://github.com/ory/keto/issues/1626). Summary: - The syntax error is due to unbounded recursion in the permission definition. - Limit recursion depth by unrolling the logic or flattening relationships. - Recursive permission checks are a known limitation in Ory Network and OPL. If you need true recursive group membership, you may need to wait for future improvements in Ory Keto/Network, as the current version does not fully support this pattern.high-flower-66335
06/30/2025, 12:28 PMimport { Namespace, Context, SubjectSet } from "@ory/permission-namespace-types"
class User implements Namespace { }
class Group implements Namespace {
related: {
members: (User | SubjectSet<Group, "members">)[]
}
}
class Role implements Namespace {
related: {
members: (User | SubjectSet<Group, "members">)[]
}
}
class Feature implements Namespace {
related: {
owners: User[]
readers: (User | SubjectSet<Role, "members">)[]
writers: (User | SubjectSet<Role, "members">)[]
}
permits = {
read: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.readers.includes(ctx.subject),
write: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.writers.includes(ctx.subject)
}
}
But the group cannot reference itself. How can I overcome thisbrief-egg-61061
06/30/2025, 12:28 PMbrief-egg-61061
06/30/2025, 12:29 PMSubjectSet
to model indirect or nested group membership, such as: typescript members: (User | SubjectSet<Group, "members">)[]
This allows a group to include users directly or to include all members of another group. However, self-referencing (a group referencing itself as a member) is not supported and is explicitly discouraged in the Ory documentation. This is to prevent infinite recursion and cycles in the relationship graph, which Ory Keto does not support or handle gracefully[Subjects: Subject sets](https://www.ory.sh/docs/keto/concepts/subjects). How to overcome this limitation: - Avoid self-referencing groups: You should not create a relationship where a group is a member of itself, either directly or indirectly. This is a fundamental limitation to prevent cycles in the permission graph. - Model group hierarchies without cycles: You can nest groups (A includes B, B includes C), but you must ensure there are no cycles (e.g., C does not include A, and so on). - Flatten group memberships if needed: If you need to ensure a user has all the permissions of nested groups, you may need to flatten the group memberships in your application logic before writing them to Ory Keto, or ensure your group structure is acyclic. Summary: Ory Keto and OPL do not support groups referencing themselves, either directly or through nested SubjectSet
relationships, to avoid cycles and infinite recursion. You must design your group hierarchy to be acyclic[Subjects: Subject sets](https://www.ory.sh/docs/keto/concepts/subjects). If you need more advanced group logic, you may need to handle cycle detection and flattening outside of Ory Keto before creating the relationships.