fix(security): close role escalation, music SSRF, and permission gaps - #33
Merged
Conversation
Findings from a full security audit, all fixed: - roles: /selfrole add and /levelrole set validate the role before storing it — must be below BOTH the acting admin's and the bot's top role, not managed or @everyone, and carry no moderation/management permissions (_ELEVATED mask). The panel button and on_level_up re-validate at grant time in case the role changed after being configured. - music: /music play rejects URLs that only yt-dlp's Generic extractor matches, BEFORE any network I/O — the Generic fallthrough let users make the bot fetch arbitrary URLs, including LAN/internal addresses, from the host (SSRF). - roles/automod/logging/welcome subcommands now enforce has_permissions in code; default_permissions is only a UI default that admins can override per-command. - study: delivered reminders ping only the reminder's owner (user-controlled text is replayed by the bot and could smuggle mentions), text capped at 500 chars, and pending reminders capped at 25 per user. - workflows: least-privilege permissions (CI contents:read, deploy none). Dependency audit (pip-audit): PyNaCl 1.5.0 / CVE-2025-69277 is known but upstream-blocked — discord.py 2.7.1 pins PyNaCl<1.6; documented in CHANGELOG. Tests: 21 → 23 (new SSRF-guard tests, incl. internal/link-local addresses). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Full security audit of the codebase (permission enforcement, injection points, the role system, the music resolver, mention handling, workflows, dependencies). Five findings, all fixed here; one dependency advisory documented as upstream-blocked.
1. Role privilege escalation (high)
/selfrole addand/levelrole setaccepted any role — including roles above the acting admin's own, or roles with Administrator/Manage Server — and the panel button would then grant them to anyone who clicked.Fix: a
_grantable_denialguard rejects roles that are ≥ the acting admin's top role, above the bot's top role, managed/integration roles, @everyone, and any role with moderation/management permissions (_ELEVATEDmask). The button callback andon_level_upre-validate at grant time, so a role edited after being configured can't sneak through.2. SSRF via
/music play(high on a self-hosted box)Any URL not matching a known site fell through to yt-dlp's Generic extractor, which fetches the URL directly — so
/music play http://192.168.1.1/...made the bot fetch arbitrary LAN/internal URLs from the NAS.Fix: pre-flight extractor matching rejects Generic-only URLs before any network I/O (checking after extraction would be too late). Search text is unaffected; known-site links are unaffected. Friendly error for direct links.
3. Missing server-side permission checks (medium)
default_permissionsis only a UI default — server admins can override it per-command in Integration settings.moderationand/prefixalready double-checked in code;/selfrole,/levelrole,/automod,/logging, and/welcomedid not.Fix: explicit
has_permissionschecks on every subcommand (existing error handlers already reply cleanly).4. Reminder ping abuse (low)
Reminder text is user-controlled and replayed by the bot later, with user pings allowed — targeted ping-spam by proxy. Also unlimited pending reminders (DB growth + spam-at-fire).
Fix: delivery restricts
allowed_mentionsto the reminder's owner only; text capped at 500 chars; 25 pending reminders per user.5. Workflow token scope (hardening)
CI and deploy workflows now declare least-privilege
permissions(CI:contents: read; deploy:{}— it never uses the token).Dependency audit
pip-audit: PyNaCl 1.5.0 → CVE-2025-69277 (fixed in 1.6.2), but discord.py 2.7.1 (latest) pinsPyNaCl<1.6, so the fix can't be installed without overriding the library's constraint. Documented in the CHANGELOG; bump when discord.py relaxes the pin. No other known vulns in the dependency set.Verified clean
Parameterized SQL throughout (no injection), no dangerous sinks (
eval/subprocess/pickle), globalallowed_mentionsblocks @everyone/@roles, owner commands properly gated, AI cog has no tool use (prompt-injection blast radius ≈ nil), Docker already hardened.Testing
_resolve("http://192.168.1.1/internal.mp3")raises before any fetch.ruff+ruff format+ full suite green.🤖 Generated with Claude Code