Skip to content

security(core): scope auth tokens with explicit CRUD permissions and server-assigned login (24.05) - #7964

Open
ar2rsawseen wants to merge 1 commit into
release.24.05from
security/token-permission-model-2405
Open

security(core): scope auth tokens with explicit CRUD permissions and server-assigned login (24.05)#7964
ar2rsawseen wants to merge 1 commit into
release.24.05from
security/token-permission-model-2405

Conversation

@ar2rsawseen

@ar2rsawseen ar2rsawseen commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

Backport of #7963 to release.24.05.

Replaces the token app/endpoint scoping model with explicit CRUD permissions, and makes permission to open a dashboard session an explicit, server-assigned property of the token. See #7963 for the full description of the model.

One addition specific to this branch

This backport also takes hasAdminAccess from 5e6014496da (SER-2766), which is on later versions but was never backported here. On this branch it treats an app with no permission entry for the member as one they administer, because isAdmin starts as true and is only cleared when an entry exists and is not all.

That has to be corrected for the permission model to mean anything here: an intersected member expresses "no access to app B" as the absence of an entry for B, which this branch would otherwise read as administering B. It is also reachable independently of tokens, since validateUserForWrite gates solely on hasAdminAccess.

Verified on this branch before and after: a member with access to app A only previously returned true from hasAdminAccess(member, B), hasReadRight('core', B, member) and hasDeleteRight('core', B, member); all now return false, while legitimate access to A is unchanged.

Verification

Ported by cherry-pick; three conflicts resolved by hand (the rights.js import list, which lacks getBaseAppFilter here, and the CHANGELOG and localization sections, which differ from master).

The same 21 unit tests pass on this branch. The 5 unrelated failures in api.utils.common.js and api.utils.countly-request.js are pre-existing on release.24.05 and fail identically without this change.

Related

Supersedes the earlier containment PR #7958, closed in favour of this model.

Reported through the security bug bounty program (received 2026-08-18).

🤖 Generated with Claude Code

…server-assigned login

Token scope was expressed as an app list and an endpoint regex, which verify_token compared
against the request path and, when present, the request's app_id. That is a data-scoping
control: it does not describe what the resolved member may do, and rights.js did not carry
it past token resolution, so a token authorized whatever its owner could. Login capability
was likewise inferred from the token's purpose string, which is supplied by the caller at
creation.

Tokens now carry explicit permissions and an explicit login capability.

- token_permission stores a member.permission-shaped grant on the token. rights.js
  intersects it with the owner's own permissions as soon as the member is loaded, before any
  validator authorizes anything, so every existing feature check becomes scope-aware without
  being touched. The intersected member does not carry global_admin. The intersection is
  recomputed per request, so it follows the owner's current permissions.
- A grant is bounded by the credential that creates it, not by the owner. Because the
  intersection has already happened, params.member is that credential's authority, and
  isPermissionSubset refuses anything wider. An "all" grant may only be passed on by a
  holder of "all", since it covers features that do not exist yet.
- can_login is a server-assigned property of the token. It is set where the server
  establishes or propagates a session (setLoggedInVariables, the renderer, the ban-warning
  mail), and by /i/token/create only when the creating credential holds it and the child is
  not narrowed. purpose returns to being a description.
- Token creation, listing and deletion require a full-permission credential, since each of
  them hands out, exposes or revokes the owner's credentials.

This branch also takes hasAdminAccess from 5e60144, which is on later versions but was
never backported here. It treated an app with no permission entry for the member as one they
administer. The permission model depends on that being correct, since an intersected member
expresses "no access" as the absence of an entry.

Tokens created before this change are unaffected: with no token_permission they are neither
intersected nor re-scoped, the endpoint regex still applies to them, and /login/token still
accepts the unrestricted login-purpose tokens it accepted previously.

The token manager grants app, CRUD and feature permissions using the same permission grid
and helpers as user management, instead of endpoint regexes.

Covered by unit tests for the permission algebra and the token record, and by API tests for
the create, login, list and delete boundaries.

Reported through the security bug bounty program (received 2026-08-18).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants