Back to Blog
critical SEVERITY4 min read

Discord Bot Auto-Role CWE-269: Privilege Escalation via role.position

A Discord.js bot's auto-role feature allowed privilege escalation where any user with ManageRoles permission could add Administrator roles to automatic assignment lists, regardless of their own role hierarchy. The vulnerability existed because role.position validation checked only the bot's permissions, not the invoking user's role hierarchy relative to the target role.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published October 4, 2026•Reviewed October 4, 2026

Answer Summary

The auto-role management subcommands (setup, enable, list) in a Discord.js moderation bot; an attacker with ManageRoles permission but lower role position could add Administrator or higher-ranked roles to automatic assignment lists, which were then automatically granted to new server members; fixed by adding a role.position hierarchy check comparing the invoking user's highest role against the target role; CWE-269 (Improper Privilege Management).

Vulnerability at a Glance

cweCWE-269
fixAdded role.position comparison between interaction.member.roles.highest and target role
riskPrivilege escalation allowing lower-ranked moderators to grant administrative access to new members
languageJavaScript
root causeMissing role hierarchy validation for the invoking user, only checking bot permissions
vulnerabilityImproper Privilege Management

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see linked PR
Ecosystem N/A (Discord.js bot application)
CVE / GHSA not assigned
CWE CWE-269 (Improper Privilege Management)

The Vulnerability Explained

Discord's permission system relies on role hierarchy: users can only manage roles positioned below their highest assigned role. A moderator with a "Moderator" role cannot assign or modify an "Administrator" role positioned above it, even if they hold the ManageRoles permission.

The auto-role management feature in this Discord.js bot broke this fundamental invariant. When processing subcommands like setup, enable, or list, the code validated that the bot could manage the target role—but never checked if the invoking user had sufficient hierarchy to do so.

The vulnerable validation looked like this:

// Bot permission check only—no user hierarchy validation
if (!interaction.guild.members.me.permissions.has(PermissionFlagsBits.ManageRoles)) {
    return interaction.editReply({
        content: lang?.role?.botNoPermission || "Bot không có quyền quản lý role.",
    });
}

This pattern appears sufficient at first glance. The bot verifies it can manage roles, proceeds with the operation, and Discord's API will enforce its own restrictions. But Discord's API restrictions apply to the bot's identity, not the user's intent. The bot was correctly permissioned; the user was over-empowered.

The Attack Scenario

Consider a typical server hierarchy:

  1. Server Owner
  2. Administrator (full server access)
  3. Moderator (kick, ban, manage messages)
  4. Member

A user with the "Moderator" role has ManageRoles permission. In normal Discord usage, they cannot touch the "Administrator" role. But with this vulnerability:

/role setup role:@Administrator channel:#welcome

The command succeeds. The Administrator role is now automatically assigned to every new member joining the server. The moderator has effectively granted administrative access to the entire server population—without ever holding that role themselves.

The escalation is persistent and systemic: new members automatically receive elevated privileges, and the original attacker needs no ongoing access to maintain the compromise.

The Fix

The remediation adds explicit role hierarchy validation before any auto-role configuration:

// Kiểm tra người dùng có quyền quản lý role này không (tránh leo thang đặc quyền qua auto role)
if (role.position >= interaction.member.roles.highest.position && interaction.guild.ownerId !== interaction.user.id) {
    return interaction.editReply({
        content:
            lang?.role?.userRoleTooLow || "Bạn không thể quản lý role này vì role của bạn thấp hơn hoặc bằng role này.",
    });
}

This single check enforces Discord's native hierarchy semantics:

Before After
Bot checks its own ManageRoles permission Bot checks its permission + user's role hierarchy
Any ManageRoles holder configures any auto-role User must hold a role strictly higher than target
Server owner subject to same checks Server owner explicitly exempted

The >= comparison is critical. Discord's role system uses strictly higher position for management rights—a user with position 5 cannot manage position 5 or above. The exemption for interaction.guild.ownerId preserves Discord's guarantee that server owners retain full control regardless of role assignments.

Key Takeaways

  • Permission bits are insufficient for hierarchy-sensitive operations: ManageRoles in PermissionsBitField doesn't encode positional information. Always validate role.position against GuildMember.roles.highest.position for role management commands.

  • Bot-centric validation creates user-level vulnerabilities: When a bot acts on behalf of users, it must enforce the user's restrictions, not merely its own. The bot's permissions are a prerequisite, not a substitute for authorization checks.

  • Auto-assignment features amplify privilege escalation impact: A single compromised auto-role configuration affects all future server members. The persistence makes this vulnerability more severe than transient role grants.

  • Discord.js interaction objects expose hierarchy data: interaction.member.roles.highest provides the necessary comparison point—no additional API calls required.

How Orbis AppSec Detected This

Source: The role option in Discord slash command interactions, accepting user-specified role IDs.

Sink: Database persistence via database.ZiGuild.findOne() and subsequent ZiGuild document updates that configure automatic role assignment.

Missing control: No validation of interaction.member.roles.highest.position against the target role.position before persisting auto-role configuration.

CWE: CWE-269 (Improper Privilege Management)

Fix: Added hierarchy check using role.position >= interaction.member.roles.highest.position with explicit server owner exemption.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates how bot-mediated role management can subvert Discord's built-in hierarchy protections. The fix restores the platform's security model by enforcing that users can only configure auto-roles they could manually assign. For Discord.js developers, the pattern is clear: always validate roles.highest.position before acting on user-specified roles, regardless of what permission bits appear sufficient.

Prevention and further reading

Frequently Asked Questions

Does the fix prevent server owners from managing any role?

No. The check explicitly exempts the guild owner via `interaction.guild.ownerId !== interaction.user.id`, preserving their full role management capabilities.

What specific Discord.js API property prevents the privilege escalation?

The fix uses `interaction.member.roles.highest.position` to compare against `role.position`, ensuring users can only manage roles strictly below their highest assigned role.

Is the vulnerability exploitable through the list or enable subcommands, or only setup?

All three subcommands (setup, enable, list) were affected because they share the same role validation logic that only verified bot permissions, not user hierarchy.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #226

Related Articles

high

CVE-2026-54290: hono cors() Reflects Any Origin With Credentials

The `hono` CORS middleware, as resolved in this service at 4.12.8, reflected the caller's `Origin` header back in `Access-Control-Allow-Origin` while also emitting `Access-Control-Allow-Credentials: true` whenever the `origin` option was left at its `'*'` default. That combination makes any website a trusted origin for credentialed cross-origin reads. The dependency range was raised from `^4.7.1` to `^4.13.5`, moving the installed copy from 4.12.8 to 4.13.5.

critical

POST /api/generate Lacks Authentication, Allowing Unauthenticated

A resume generation endpoint in a Node.js backend accepted requests from any caller with network access, allowing attackers to consume OpenAI API quota without restriction. The vulnerability stemmed from missing authentication middleware on a cost-bearing endpoint. The fix adds mandatory API key validation via HTTP headers before processing any generation requests.

critical

SchedulePush.disableReminder Missing Authorization Check in push.js

The `disableReminder` method in the `SchedulePush` class allowed any user to disable push notification reminders for arbitrary user IDs by manipulating the `e.user_id` event parameter. The fix adds a `checkFriend()` authorization gate that verifies the requesting user has a valid friendship relationship with the bot before modifying subscription state.

critical

How Broken Object-Level Authorization happens in Express.js and how to fix it

A critical authorization flaw in `src/v1/routes/index.js` allowed any authenticated API key holder to access arbitrary budgets by manipulating the `budgetSyncId` URL parameter. The fix introduces an environment-based allowlist that validates budget access before processing requests.

high

How Missing Rate Limiting Enables Denial of Service Attacks in Node.js and How to Fix It

The k-skill-proxy server exposed multiple public API endpoints (`/health`, `/v1/vworld/search`, `/v1/fine-dust/report`, `/v1/assembly/bills`) without consistent rate limiting middleware, leaving them vulnerable to denial-of-service attacks. A `buildRateLimiter` function existed but wasn't applied to all endpoints. This fix ensures rate limiting is enforced on all public endpoints, preventing resource exhaustion attacks.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.