fix(discord): check pairing store for component button auth
Component button interactions (approve/deny, slash confirm, model
picker, clarify) were not checking the pairing store for authorization.
Users approved via `hermes pairing approve` could send messages and use
slash commands (which go through the gateway authz_mixin), but button
clicks were rejected because `_component_check_auth` only checked
env-var allowlists (DISCORD_ALLOWED_USERS, GATEWAY_ALLOW_ALL_USERS,
etc.) and not the pairing store.
This was a regression from commit f6f363662 which intentionally made
component auth fail-closed when no allowlist is set (security fix for
GHSA-mc26-p6fw-7pp6), but did not account for pairing-based auth.
Fix: add a `PairingStore.is_approved("discord", uid)` check to
`_component_check_auth`, mirroring `authz_mixin._check_authorization`.
The pairing store check runs after all allowlist checks, preserving the
fail-closed behavior for non-paired, non-allowed users.
Fixes #50627
This commit is contained in:
parent
0957d77187
commit
7ff48a6291
2 changed files with 66 additions and 5 deletions
|
|
@ -5709,7 +5709,8 @@ def _component_check_auth(
|
|||
|
||||
Mirrors the gateway's external-surface authorization model: component
|
||||
button clicks must be explicitly authorized by a Discord user/role
|
||||
allowlist, a global user allowlist, or an explicit allow-all flag.
|
||||
allowlist, a global user allowlist, an explicit allow-all flag, or
|
||||
the pairing store (``hermes pairing approve``).
|
||||
|
||||
Behavior:
|
||||
|
||||
|
|
@ -5719,6 +5720,7 @@ def _component_check_auth(
|
|||
- role allowlist set + interaction.user has no resolvable
|
||||
``roles`` attribute (e.g. DM context with a role policy active)
|
||||
-> reject (fail closed)
|
||||
- user is approved in the pairing store -> allow
|
||||
- otherwise -> reject
|
||||
"""
|
||||
if os.getenv("DISCORD_ALLOW_ALL_USERS", "").strip().lower() in {"true", "1", "yes"}:
|
||||
|
|
@ -5740,11 +5742,13 @@ def _component_check_auth(
|
|||
if user is None:
|
||||
return False
|
||||
|
||||
# Resolve user ID once for both allowlist and pairing checks.
|
||||
try:
|
||||
uid = str(user.id)
|
||||
except AttributeError:
|
||||
uid = ""
|
||||
|
||||
if has_users:
|
||||
try:
|
||||
uid = str(user.id)
|
||||
except AttributeError:
|
||||
uid = ""
|
||||
if "*" in user_set or (uid and uid in user_set):
|
||||
return True
|
||||
|
||||
|
|
@ -5763,6 +5767,18 @@ def _component_check_auth(
|
|||
if user_role_ids & role_set:
|
||||
return True
|
||||
|
||||
# Check pairing store — mirrors ``authz_mixin._check_authorization``
|
||||
# so users approved via ``hermes pairing approve`` can interact with
|
||||
# component buttons even without DISCORD_ALLOWED_USERS set.
|
||||
if uid:
|
||||
try:
|
||||
from gateway.pairing import PairingStore
|
||||
store = PairingStore()
|
||||
if store.is_approved("discord", uid):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue