Skip to content

fix(security): close role escalation, music SSRF, and permission gaps - #33

Merged
raigon-pawa merged 1 commit into
mainfrom
fix/security-hardening
Jul 2, 2026
Merged

fix(security): close role escalation, music SSRF, and permission gaps#33
raigon-pawa merged 1 commit into
mainfrom
fix/security-hardening

Conversation

@raigon-pawa

Copy link
Copy Markdown
Owner

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 add and /levelrole set accepted 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_denial guard 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 (_ELEVATED mask). The button callback and on_level_up re-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_permissions is only a UI default — server admins can override it per-command in Integration settings. moderation and /prefix already double-checked in code; /selfrole, /levelrole, /automod, /logging, and /welcome did not.

Fix: explicit has_permissions checks 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_mentions to 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) pins PyNaCl<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), global allowed_mentions blocks @everyone/@roles, owner commands properly gated, AI cog has no tool use (prompt-injection blast radius ≈ nil), Docker already hardened.

Testing

  • 21 → 23 tests: new SSRF-guard tests (internal IPs, link-local metadata address, direct file links rejected; YouTube/SoundCloud accepted).
  • End-to-end check: _resolve("http://192.168.1.1/internal.mp3") raises before any fetch.
  • ruff + ruff format + full suite green.

🤖 Generated with Claude Code

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>
@raigon-pawa
raigon-pawa merged commit 9fb91b1 into main Jul 2, 2026
2 checks passed
@raigon-pawa
raigon-pawa deleted the fix/security-hardening branch July 2, 2026 19:08
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.

1 participant