Edit history

Earlier versions of Role add/remove endpoints incorrectly enforce a target-user hierarchy check, newest first.

Current version | Edited by Rex
Changes
DetailsBoth fixes may be needed independently.Removed: ## ChecksRemoved: Removed: - ☑ Searched for existing issues — [#469](https://feedback.fluxer.com/p/469) is related but addresses a different case (see above).Removed: - ☑ This is not a security issue.Removed: - ☑ Steps and observations are sufficient to reproduce on any server with a comparable 3-tier role hierarchy.Removed:
Show

Role add/remove endpoints incorrectly enforce a target-user hierarchy check

Observed behaviour

The bug was originally observed via a reaction-role bot using @discordjs/rest. Across multiple reaction events on the same server:
  • Calls targeting users whose highest role is below the bot's highest role consistently returned 200 OK and the role was added.
  • Calls targeting users whose highest role is at or above the bot's highest role consistently returned:
HTTP 403
{ "code": "MISSING_PERMISSIONS", "message": "You don't have the permissions required to perform this action." }
The same response was returned by DELETE. Across the failing cases observed so far, no successful call has been seen — the failure appears consistent rather than intermittent, but this has not been formally tested with a controlled retry loop. Affected endpoints:
  • PUT /guilds/{guildId}/members/{userId}/roles/{roleId}
  • DELETE /guilds/{guildId}/members/{userId}/roles/{roleId}
Note: the cause-effect relationship between "target user's highest role position" and the 403 is inferred from observed correlation, not from a controlled experiment in which only the target user's roles are varied. The controlled test described in "Steps to reproduce" above would confirm this. Expected behaviour Both calls succeed. Discord's permission model for role add/remove operations only requires:
  • The bot has the MANAGE_ROLES permission.
  • The role being assigned is below the bot's highest role in the hierarchy.
The target user's own role hierarchy is not part of the check for add/remove role. It is only consulted for operations that modify the member directly (kick, ban, timeout, voice operations).

Reproduction steps

The behavior was discovered on a real server while debugging a reaction-role bot. The pattern below is the suggested controlled test to reproduce it cleanly; the original observation is described under "Actual behavior". Set up a server with the following role hierarchy (top to bottom):
1. Admin            (granted to user A)
2. Bot              (granted to the bot, MANAGE_ROLES enabled)
3. Member           (granted to user B)
4. Reactable        (the role to assign)
5. `@everyone`
Then, with the bot:
  1. Call PUT /guilds/{guildId}/members/{userIdOfB}/roles/{reactableRoleId}.
  2. Call PUT /guilds/{guildId}/members/{userIdOfA}/roles/{reactableRoleId}.
  3. Same pattern for DELETE on both users.
The only variable across (1) and (2) is the highest role of the target user; the role being assigned and the bot's permissions are identical.

Details

Summary

PUT /guilds/{guildId}/members/{userId}/roles/{roleId} and the corresponding DELETE endpoint return 403 MISSING_PERMISSIONS when the target user's highest role is at or above the bot's highest role — even when the role being assigned is far below both in the hierarchy. Per the Discord permission model these endpoints should only consider the position of the role being assigned versus the bot's highest role; the target user's hierarchy should not be consulted.

Area

Backend / API

Environment

  • API base URL: https://api.fluxer.app
  • Client tested: @discordjs/rest and direct fetch
  • Bot permissions: MANAGE_ROLES confirmed via permission bit inspection (268435456 set on the bot's role)
  • Hierarchy verified visually in the web UI and via GET /guilds/{id}/roles

Logs

Failed call (raw response body):
{
  "code": "MISSING_PERMISSIONS",
  "message": "You don't have the permissions required to perform this action."
}
Working calls confirm baseline functionality:
  • GET /guilds/{id}/roles → 200
  • GET /guilds/{id}/members/{userId} → 200
  • PATCH /guilds/{id}/members/{userId} with empty body → 200

Hypothesis (not verified — separated from observations above)

The 403 pattern is consistent with a hierarchy check of the form:
botMaxRolePosition > targetUserMaxRolePosition  // applied incorrectly
This kind of check is appropriate for kick/ban/timeout operations, where Discord requires the actor to outrank the target. It should not gate addRoleToMember / removeRoleFromMember, which are governed only by the relationship between the bot and the role being assigned.

Relationship to #469

This appears to be a distinct bug from #469:
  • #469 describes the case where all non-privileged roles share the same position value (e.g. 1), causing the strict comparison UserMax > role_position(Role) in check_can_manage_roles to evaluate 1 > 1 = false. The proposed fix adds a tiebreaker by role ID for equal positions.
  • This issue is observed with distinct role positions: the role being assigned is several positions below both the bot's role and the target user's highest role. The tiebreaker fix from #469 does not apply here because there is no position equality involved — the failure depends on the target user's highest role, not on the assigned role.
Both fixes may be needed independently.
Edited by Rex
Changes
Reproduction steps3. Member (granted to user B)4. Reactable (the role to assign)Removed: 5. @everyoneAdded: 5. `@everyone````Then, with the bot:1. Call `PUT /guilds/{guildId}/members/{userIdOfB}/roles/{reactableRoleId}`.2. Call `PUT /guilds/{guildId}/members/{userIdOfA}/roles/{reactableRoleId}`.3. Same pattern for `DELETE` on both users.The only variable across (1) and (2) is the highest role of the target user; the role being assigned and the bot's permissions are identical.Details## Summary`PUT /guilds/{guildId}/members/{userId}/roles/{roleId}` and the corresponding `DELETE` endpoint return `403 MISSING_PERMISSIONS` when the **target user**'s highest role is at or above the bot's highest role — even when the role being assigned is far below both in the hierarchy. Per the Discord permission model these endpoints should only consider the position of the role being assigned versus the bot's highest role; the target user's hierarchy should not be consulted.## AreaBackend / API## Environment- API base URL: `https://api.fluxer.app`- Client tested: `@discordjs/rest` and direct `fetch`- Bot permissions: `MANAGE_ROLES` confirmed via permission bit inspection (`268435456` set on the bot's role)- Hierarchy verified visually in the web UI and via `GET /guilds/{id}/roles`## LogsFailed call (raw response body):```json{ "code": "MISSING_PERMISSIONS", "message": "You don't have the permissions required to perform this action."}```Working calls confirm baseline functionality:- `GET /guilds/{id}/roles` → 200- `GET /guilds/{id}/members/{userId}` → 200- `PATCH /guilds/{id}/members/{userId}` with empty body → 200## Hypothesis (not verified — separated from observations above)The 403 pattern is consistent with a hierarchy check of the form:```botMaxRolePosition > targetUserMaxRolePosition // applied incorrectly```This kind of check is appropriate for kick/ban/timeout operations, where Discord requires the actor to outrank the target. It should not gate `addRoleToMember` / `removeRoleFromMember`, which are governed only by the relationship between the bot and the role being assigned.Removed: ## Relationship to [#791](https://feedback.fluxer.com/p/469)Added: ## Relationship to [#469](https://feedback.fluxer.com/p/469)Removed: This appears to be a **distinct bug** from [#791](https://feedback.fluxer.com/p/469):Added: This appears to be a **distinct bug** from [#469](https://feedback.fluxer.com/p/469):Removed: - **[#791](https://feedback.fluxer.com/p/469)** describes the case where all non-privileged roles share the same position value (e.g. `1`), causing the strict comparison `UserMax > role_position(Role)` in `check_can_manage_roles` to evaluate `1 > 1 = false`. The proposed fix adds a tiebreaker by role ID for equal positions.Removed: - **This issue** is observed with **distinct** role positions: the role being assigned is several positions below both the bot's role and the target user's highest role. The tiebreaker fix from [#791](https://feedback.fluxer.com/p/469) does not apply here because there is no position equality involved — the failure depends on the **target user's** highest role, not on the assigned role.Added: - **[#469](https://feedback.fluxer.com/p/469)** describes the case where all non-privileged roles share the same position value (e.g. `1`), causing the strict comparison `UserMax > role_position(Role)` in `check_can_manage_roles` to evaluate `1 > 1 = false`. The proposed fix adds a tiebreaker by role ID for equal positions.Added: - **This issue** is observed with **distinct** role positions: the role being assigned is several positions below both the bot's role and the target user's highest role. The tiebreaker fix from [#469](https://feedback.fluxer.com/p/469) does not apply here because there is no position equality involved — the failure depends on the **target user's** highest role, not on the assigned role.Both fixes may be needed independently.## ChecksRemoved: - ☑ Searched for existing issues — [#791](https://feedback.fluxer.com/p/469) is related but addresses a different case (see above).Added: - ☑ Searched for existing issues — [#469](https://feedback.fluxer.com/p/469) is related but addresses a different case (see above).- ☑ This is not a security issue.- ☑ Steps and observations are sufficient to reproduce on any server with a comparable 3-tier role hierarchy.
Show

Role add/remove endpoints incorrectly enforce a target-user hierarchy check

Observed behaviour

The bug was originally observed via a reaction-role bot using @discordjs/rest. Across multiple reaction events on the same server:
  • Calls targeting users whose highest role is below the bot's highest role consistently returned 200 OK and the role was added.
  • Calls targeting users whose highest role is at or above the bot's highest role consistently returned:
HTTP 403
{ "code": "MISSING_PERMISSIONS", "message": "You don't have the permissions required to perform this action." }
The same response was returned by DELETE. Across the failing cases observed so far, no successful call has been seen — the failure appears consistent rather than intermittent, but this has not been formally tested with a controlled retry loop. Affected endpoints:
  • PUT /guilds/{guildId}/members/{userId}/roles/{roleId}
  • DELETE /guilds/{guildId}/members/{userId}/roles/{roleId}
Note: the cause-effect relationship between "target user's highest role position" and the 403 is inferred from observed correlation, not from a controlled experiment in which only the target user's roles are varied. The controlled test described in "Steps to reproduce" above would confirm this. Expected behaviour Both calls succeed. Discord's permission model for role add/remove operations only requires:
  • The bot has the MANAGE_ROLES permission.
  • The role being assigned is below the bot's highest role in the hierarchy.
The target user's own role hierarchy is not part of the check for add/remove role. It is only consulted for operations that modify the member directly (kick, ban, timeout, voice operations).

Reproduction steps

The behavior was discovered on a real server while debugging a reaction-role bot. The pattern below is the suggested controlled test to reproduce it cleanly; the original observation is described under "Actual behavior". Set up a server with the following role hierarchy (top to bottom):
1. Admin            (granted to user A)
2. Bot              (granted to the bot, MANAGE_ROLES enabled)
3. Member           (granted to user B)
4. Reactable        (the role to assign)
5. `@everyone`
Then, with the bot:
  1. Call PUT /guilds/{guildId}/members/{userIdOfB}/roles/{reactableRoleId}.
  2. Call PUT /guilds/{guildId}/members/{userIdOfA}/roles/{reactableRoleId}.
  3. Same pattern for DELETE on both users.
The only variable across (1) and (2) is the highest role of the target user; the role being assigned and the bot's permissions are identical.

Details

Summary

PUT /guilds/{guildId}/members/{userId}/roles/{roleId} and the corresponding DELETE endpoint return 403 MISSING_PERMISSIONS when the target user's highest role is at or above the bot's highest role — even when the role being assigned is far below both in the hierarchy. Per the Discord permission model these endpoints should only consider the position of the role being assigned versus the bot's highest role; the target user's hierarchy should not be consulted.

Area

Backend / API

Environment

  • API base URL: https://api.fluxer.app
  • Client tested: @discordjs/rest and direct fetch
  • Bot permissions: MANAGE_ROLES confirmed via permission bit inspection (268435456 set on the bot's role)
  • Hierarchy verified visually in the web UI and via GET /guilds/{id}/roles

Logs

Failed call (raw response body):
{
  "code": "MISSING_PERMISSIONS",
  "message": "You don't have the permissions required to perform this action."
}
Working calls confirm baseline functionality:
  • GET /guilds/{id}/roles → 200
  • GET /guilds/{id}/members/{userId} → 200
  • PATCH /guilds/{id}/members/{userId} with empty body → 200

Hypothesis (not verified — separated from observations above)

The 403 pattern is consistent with a hierarchy check of the form:
botMaxRolePosition > targetUserMaxRolePosition  // applied incorrectly
This kind of check is appropriate for kick/ban/timeout operations, where Discord requires the actor to outrank the target. It should not gate addRoleToMember / removeRoleFromMember, which are governed only by the relationship between the bot and the role being assigned.

Relationship to #469

This appears to be a distinct bug from #469:
  • #469 describes the case where all non-privileged roles share the same position value (e.g. 1), causing the strict comparison UserMax > role_position(Role) in check_can_manage_roles to evaluate 1 > 1 = false. The proposed fix adds a tiebreaker by role ID for equal positions.
  • This issue is observed with distinct role positions: the role being assigned is several positions below both the bot's role and the target user's highest role. The tiebreaker fix from #469 does not apply here because there is no position equality involved — the failure depends on the target user's highest role, not on the assigned role.
Both fixes may be needed independently.

Checks

  • ☑ Searched for existing issues — #469 is related but addresses a different case (see above).
  • ☑ This is not a security issue.
  • ☑ Steps and observations are sufficient to reproduce on any server with a comparable 3-tier role hierarchy.
Original by Rex
Show

Role add/remove endpoints incorrectly enforce a target-user hierarchy check

Observed behaviour

The bug was originally observed via a reaction-role bot using @discordjs/rest. Across multiple reaction events on the same server:
  • Calls targeting users whose highest role is below the bot's highest role consistently returned 200 OK and the role was added.
  • Calls targeting users whose highest role is at or above the bot's highest role consistently returned:
HTTP 403
{ "code": "MISSING_PERMISSIONS", "message": "You don't have the permissions required to perform this action." }
The same response was returned by DELETE. Across the failing cases observed so far, no successful call has been seen — the failure appears consistent rather than intermittent, but this has not been formally tested with a controlled retry loop. Affected endpoints:
  • PUT /guilds/{guildId}/members/{userId}/roles/{roleId}
  • DELETE /guilds/{guildId}/members/{userId}/roles/{roleId}
Note: the cause-effect relationship between "target user's highest role position" and the 403 is inferred from observed correlation, not from a controlled experiment in which only the target user's roles are varied. The controlled test described in "Steps to reproduce" above would confirm this. Expected behaviour Both calls succeed. Discord's permission model for role add/remove operations only requires:
  • The bot has the MANAGE_ROLES permission.
  • The role being assigned is below the bot's highest role in the hierarchy.
The target user's own role hierarchy is not part of the check for add/remove role. It is only consulted for operations that modify the member directly (kick, ban, timeout, voice operations).

Reproduction steps

The behavior was discovered on a real server while debugging a reaction-role bot. The pattern below is the suggested controlled test to reproduce it cleanly; the original observation is described under "Actual behavior". Set up a server with the following role hierarchy (top to bottom):
1. Admin            (granted to user A)
2. Bot              (granted to the bot, MANAGE_ROLES enabled)
3. Member           (granted to user B)
4. Reactable        (the role to assign)
5. @everyone
Then, with the bot:
  1. Call PUT /guilds/{guildId}/members/{userIdOfB}/roles/{reactableRoleId}.
  2. Call PUT /guilds/{guildId}/members/{userIdOfA}/roles/{reactableRoleId}.
  3. Same pattern for DELETE on both users.
The only variable across (1) and (2) is the highest role of the target user; the role being assigned and the bot's permissions are identical.

Details

Summary

PUT /guilds/{guildId}/members/{userId}/roles/{roleId} and the corresponding DELETE endpoint return 403 MISSING_PERMISSIONS when the target user's highest role is at or above the bot's highest role — even when the role being assigned is far below both in the hierarchy. Per the Discord permission model these endpoints should only consider the position of the role being assigned versus the bot's highest role; the target user's hierarchy should not be consulted.

Area

Backend / API

Environment

  • API base URL: https://api.fluxer.app
  • Client tested: @discordjs/rest and direct fetch
  • Bot permissions: MANAGE_ROLES confirmed via permission bit inspection (268435456 set on the bot's role)
  • Hierarchy verified visually in the web UI and via GET /guilds/{id}/roles

Logs

Failed call (raw response body):
{
  "code": "MISSING_PERMISSIONS",
  "message": "You don't have the permissions required to perform this action."
}
Working calls confirm baseline functionality:
  • GET /guilds/{id}/roles → 200
  • GET /guilds/{id}/members/{userId} → 200
  • PATCH /guilds/{id}/members/{userId} with empty body → 200

Hypothesis (not verified — separated from observations above)

The 403 pattern is consistent with a hierarchy check of the form:
botMaxRolePosition > targetUserMaxRolePosition  // applied incorrectly
This kind of check is appropriate for kick/ban/timeout operations, where Discord requires the actor to outrank the target. It should not gate addRoleToMember / removeRoleFromMember, which are governed only by the relationship between the bot and the role being assigned.

Relationship to #791

This appears to be a distinct bug from #791:
  • #791 describes the case where all non-privileged roles share the same position value (e.g. 1), causing the strict comparison UserMax > role_position(Role) in check_can_manage_roles to evaluate 1 > 1 = false. The proposed fix adds a tiebreaker by role ID for equal positions.
  • This issue is observed with distinct role positions: the role being assigned is several positions below both the bot's role and the target user's highest role. The tiebreaker fix from #791 does not apply here because there is no position equality involved — the failure depends on the target user's highest role, not on the assigned role.
Both fixes may be needed independently.

Checks

  • ☑ Searched for existing issues — #791 is related but addresses a different case (see above).
  • ☑ This is not a security issue.
  • ☑ Steps and observations are sufficient to reproduce on any server with a comparable 3-tier role hierarchy.