{"repo":"NousResearch/hermes-agent","public_id":"11339","display_label":"Discord bot @everyone ping prevention fix","summary":"A bug was identified where the Discord bot could ping @everyone and roles because allowed_mentions was never configured on the client. Multiple fix attempts address this by defaulting allowed_mentions to block such pings.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":11479,"merged_at":"2026-04-17T11:08:43+00:00"},"resolution":{"state":"resolved","by":"pr-11479"},"freshness":{"state":"stale","last_verified_at":"2026-04-17T11:08:43+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":11339,"pr_number":11479,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-17T11:08:43+00:00"}],"members":[{"kind":"issue","number":11339,"title":"Discord bot can ping @everyone — `allowed_mentions` never set on the client","url":null,"state":"closed"},{"kind":"pr","number":11341,"title":"fix(discord): default allowed_mentions to block @everyone and role pings","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":11353,"title":"fix(discord): default allowed_mentions to block @everyone and role pings","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#11479","number":11479}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":11479,"title":"fix(discord): default allowed_mentions to block @everyone and role pings","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/config.py","gateway/platforms/discord.py","scripts/release.py","tests/gateway/test_discord_allowed_mentions.py","tests/gateway/test_discord_connect.py","website/docs/reference/environment-variables.md","website/docs/user-guide/messaging/discord.md"],"fix_hunks":[{"file":"gateway/config.py","hunks":["@@ -617,6 +617,20 @@ def load_gateway_config() -> GatewayConfig:\n                     if isinstance(ntc, list):\n                         ntc = \",\".join(str(v) for v in ntc)\n                     os.environ[\"DISCORD_NO_THREAD_CHANNELS\"] = str(ntc)\n+                # allow_mentions: granular control over what the bot can ping.\n+                # Safe defaults (no @everyone/roles) are applied in the adapter;\n+                # these YAML keys only override when set and let users opt back\n+                # into unsafe modes (e.g. roles=true) if they actually want it.\n+                allow_mentions_cfg = discord_cfg.get(\"allow_mentions\")\n+                if isinstance(allow_mentions_cfg, dict):\n+                    for yaml_key, env_key in (\n+                        (\"everyone\", \"DISCORD_ALLOW_MENTION_EVERYONE\"),\n+                        (\"roles\", \"DISCORD_ALLOW_MENTION_ROLES\"),\n+                        (\"users\", \"DISCORD_ALLOW_MENTION_USERS\"),\n+                        (\"replied_user\", \"DISCORD_ALLOW_MENTION_REPLIED_USER\"),\n+                    ):\n+                        if yaml_key in allow_mentions_cfg and not os.getenv(env_key):\n+                            os.environ[env_key] = str(allow_mentions_cfg[yaml_key]).lower()\n \n             # Telegram settings → env vars (env vars take precedence)\n             telegram_cfg = yaml_cfg.get(\"telegram\", {})"]},{"file":"gateway/platforms/discord.py","hunks":["@@ -80,6 +80,41 @@ def check_discord_requirements() -> bool:\n     return DISCORD_AVAILABLE\n \n \n+def _build_allowed_mentions():\n+    \"\"\"Build Discord ``AllowedMentions`` with safe defaults, overridable via env.\n+\n+    Discord bots default to parsing ``@everyone``, ``@here``, role pings, and\n+    user pings when ``allowed_mentions`` is unset on the client — any LLM\n+    output or echoed user content that contains ``@everyone`` would therefore\n+    ping the whole server. We explicitly deny ``@everyone`` and role pings\n+    by default and keep user / replied-user pings enabled so normal\n+    conversation still works.\n+\n+    Override via environment variables (or ``discord.allow_mentions.*`` in\n+    config.yaml):\n+\n+        DISCORD_ALLOW_MENTION_EVERYONE      default false  — @everyone + @here\n+        DISCORD_ALLOW_MENTION_ROLES         default false  — @role pings\n+        DISCORD_ALLOW_MENTION_USERS         default true   — @user pings\n+        DISCORD_ALLOW_MENTION_REPLIED_USER  default true   — reply-ping author\n+    \"\"\"\n+    if not DISCORD_AVAILABLE:\n+        return None\n+\n+    def _b(name: str, default: bool) -> bool:\n+        raw = os.getenv(name, \"\").strip().lower()\n+        if not raw:\n+            return default\n+        return raw in (\"true\", \"1\", \"yes\", \"on\")\n+\n+    return discord.AllowedMentions(\n+        everyone=_b(\"DISCORD_ALLOW_MENTION_EVERYONE\", False),\n+        roles=_b(\"DISCORD_ALLOW_MENTION_ROLES\", False),\n+        users=_b(\"DISCORD_ALLOW_MENTION_USERS\", True),\n+        replied_user=_b(\"DISCORD_ALLOW_MENTION_REPLIED_USER\", True),\n+    )\n+\n+\n class VoiceReceiver:\n     \"\"\"Captures and decodes voice audio from a Discord voice channel.\n ","@@ -556,10 +591,15 @@ async def connect(self) -> bool:\n             if proxy_url:\n                 logger.info(\"[%s] Using proxy for Discord: %s\", self.name, proxy_url)\n \n-            # Create bot — proxy= for HTTP, connector= for SOCKS\n+            # Create bot — proxy= for HTTP, connector= for SOCKS.\n+            # allowed_mentions is set with safe defaults (no @everyone/roles)\n+            # so LLM output or echoed user content can't ping the whole\n+            # server; override per DISCORD_ALLOW_MENTION_* env vars or the\n+            # discord.allow_mentions.* block in config.yaml.\n             self._client = commands.Bot(\n                 command_prefix=\"!\",  # Not really used, we handle raw messages\n                 intents=intents,\n+                allowed_mentions=_build_allowed_mentions(),\n                 **proxy_kwargs_for_bot(proxy_url),\n             )\n             adapter_self = self  # capture for closure"]},{"file":"scripts/release.py","hunks":["@@ -230,6 +230,7 @@\n     \"[redacted:email]\": \"ZaynJarvis\",\n     \"[redacted:email]\": \"ZaynJarvis\",\n     \"[redacted:email]\": \"malaiwah\",\n+    \"[redacted:email]\": \"malaiwah\",\n     \"[redacted:email]\": \"v1k22\",\n }\n "]},{"file":"tests/gateway/test_discord_allowed_mentions.py","hunks":["@@ -0,0 +1,155 @@\n+\"\"\"Tests for the Discord ``allowed_mentions`` safe-default helper.\n+\n+Ensures the bot defaults to blocking ``@everyone`` / ``@here`` / role pings\n+so an LLM response (or echoed user content) can't spam a whole server —\n+and that the four ``DISCORD_ALLOW_MENTION_*`` env vars correctly opt back\n+in when an operator explicitly wants a different policy.\n+\"\"\"\n+\n+import sys\n+from types import SimpleNamespace\n+from unittest.mock import MagicMock\n+\n+import pytest\n+\n+\n+class _FakeAllowedMentions:\n+    \"\"\"Stand-in for ``discord.AllowedMentions`` that exposes the same four\n+    boolean flags as real attributes so the test can assert on them.\n+    \"\"\"\n+\n+    def __init__(self, *, everyone=True, roles=True, users=True, replied_user=True):\n+        self.everyone = everyone\n+        self.roles = roles\n+        self.users = users\n+        self.replied_user = replied_user\n+\n+    def __repr__(self) -> str:  # pragma: no cover - debug helper\n+        return (\n+            f\"AllowedMentions(everyone={self.everyone}, roles={self.roles}, \"\n+            f\"users={self.users}, replied_user={self.replied_user})\"\n+        )\n+\n+\n+def _ensure_discord_mock():\n+    \"\"\"Install (or augment) a mock ``discord`` module.\n+\n+    Other test modules in this directory stub ``discord`` via\n+    ``sys.modules.setdefault`` — whichever test file imports first wins and\n+    our full module is then silently dropped. We therefore ALWAYS force\n+    ``AllowedMentions`` onto whatever is currently in ``sys.modules[\"discord\"]``;\n+    that's the only attribute this test file actually needs real behavior from.\n+    \"\"\"\n+    if \"discord\" in sys.modules and hasattr(sys.modules[\"discord\"], \"__file__\"):\n+        sys.modules[\"discord\"].AllowedMentions = _FakeAllowedMentions\n+        return\n+\n+    if sys.modules.get(\"discord\") is None:\n+        discord_mod = MagicMock()\n+        discord_mod.Intents.default.return_value = MagicMock()\n+        discord_mod.Client = MagicMock\n+        discord_mod.File = MagicMock\n+        discord_mod.DMChannel = type(\"DMChannel\", (), {})\n+        discord_mod.Thread = type(\"Thread\", (), {})\n+        discord_mod.ForumChannel = type(\"ForumChannel\", (), {})\n+        discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)\n+        discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)\n+        discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)\n+        discord_mod.Interaction = object\n+        discord_mod.Embed = MagicMock\n+        discord_mod.app_commands = SimpleNamespace(\n+            describe=lambda **kwargs: (lambda fn: fn),\n+            choices=lambda **kwargs: (lambda fn: fn),\n+            Choice=lambda **kwargs: SimpleNamespace(**kwargs),\n+        )\n+        discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)\n+\n+        ext_mod = MagicMock()\n+        commands_mod = MagicMock()\n+        commands_mod.Bot = MagicMock\n+        ext_mod.commands = commands_mod\n+\n+        sys.modules[\"discord\"] = discord_mod\n+        sys.modules.setdefault(\"discord.ext\", ext_mod)\n+        sys.modules.setdefault(\"discord.ext.commands\", commands_mod)\n+\n+    # Whether we just installed the mock OR the mock was already installed\n+    # by another test's _ensure_discord_mock, force the AllowedMentions\n+    # stand-in onto it — _build_allowed_mentions() reads this attribute.\n+    sys.modules[\"discord\"].AllowedMentions = _FakeAllowedMentions\n+\n+\n+_ensure_discord_mock()\n+\n+from gateway.platforms.discord import _build_allowed_mentions  # noqa: E402\n+\n+\n+# The four DISCORD_ALLOW_MENTION_* env vars that _build_allowed_mentions reads.\n+# Cleared before each test so env leakage from other tests never masks a regression.\n+_ENV_VARS = (\n+    \"DISCORD_ALLOW_MENTION_EVERYONE\",\n+    \"DISCORD_ALLOW_MENTION_ROLES\",\n+    \"DISCORD_ALLOW_MENTION_USERS\",\n+    \"DISCORD_ALLOW_MENTION_REPLIED_USER\",\n+)\n+\n+\n[redacted:email](autouse=True)\n+def _clear_allowed_mention_env(monkeypatch):\n+    for name in _ENV_VARS:\n+        monkeypatch.delenv(name, raising=False)\n+\n+\n+def test_safe_defaults_block_everyone_and_roles():\n+    am = _build_allowed_mentions()\n+    assert am.everyone is False, \"default must NOT allow @everyone/@here pings\"\n+    assert am.roles is False, \"default must NOT allow role pings\"\n+    assert am.users is True, \"default must allow user pings so replies work\"\n+    assert am.replied_user is True, \"default must allow reply-reference pings\"\n+\n+\n+def test_env_var_opts_back_into_everyone(monkeypatch):\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_EVERYONE\", \"true\")\n+    am = _build_allowed_mentions()\n+    assert am.everyone is True\n+    # other defaults unaffected\n+    assert am.roles is False\n+    assert am.users is True\n+    assert am.replied_user is True\n+\n+\n+def test_env_var_can_disable_users(monkeypatch):\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_USERS\", \"false\")\n+    am = _build_allowed_mentions()\n+    assert am.users is False\n+    # safe defaults elsewhere remain\n+    assert am.everyone is False\n+    assert am.roles is False\n+    assert am.replied_user is True\n+\n+\n[redacted:email](\"raw, expected\", [\n+    (\"true\", True), (\"True\", True), (\"TRUE\", True),\n+    (\"1\", True), (\"yes\", True), (\"YES\", True), (\"on\", True),\n+    (\"false\", False), (\"False\", False), (\"0\", False),\n+    (\"no\", False), (\"off\", False),\n+    (\"\", False),                 # empty falls back to default (False for everyone)\n+    (\"garbage\", False),          # unknown falls back to default\n+    (\" true \", True),            # whitespace tolerated\n+])\n+def test_everyone_boolean_parsing(monkeypatch, raw, expected):\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_EVERYONE\", raw)\n+    am = _build_allowed_mentions()\n+    assert am.everyone is expected\n+\n+\n+def test_all_four_knobs_together(monkeypatch):\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_EVERYONE\", \"true\")\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_ROLES\", \"true\")\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_USERS\", \"false\")\n+    monkeypatch.setenv(\"DISCORD_ALLOW_MENTION_REPLIED_USER\", \"false\")\n+    am = _build_allowed_mentions()\n+    assert am.everyone is True\n+    assert am.roles is True\n+    assert am.users is False\n+    assert am.replied_user is False"]},{"file":"tests/gateway/test_discord_connect.py","hunks":["@@ -8,37 +8,60 @@\n from gateway.config import PlatformConfig\n \n \n+class _FakeAllowedMentions:\n+    \"\"\"Stand-in for ``discord.AllowedMentions`` — exposes the same four\n+    boolean flags as real attributes so tests can assert on safe defaults.\n+    \"\"\"\n+\n+    def __init__(self, *, everyone=True, roles=True, users=True, replied_user=True):\n+        self.everyone = everyone\n+        self.roles = roles\n+        self.users = users\n+        self.replied_user = replied_user\n+\n+\n def _ensure_discord_mock():\n+    \"\"\"Install (or augment) a mock ``discord`` module.\n+\n+    Always force ``AllowedMentions`` onto whatever is in ``sys.modules`` —\n+    other test files also stub the module via ``setdefault``, and we need\n+    ``_build_allowed_mentions()``'s return value to have real attribute\n+    access regardless of which file loaded first.\n+    \"\"\"\n     if \"discord\" in sys.modules and hasattr(sys.modules[\"discord\"], \"__file__\"):\n+        sys.modules[\"discord\"].AllowedMentions = _FakeAllowedMentions\n         return\n \n-    discord_mod = MagicMock()\n-    discord_mod.Intents.default.return_value = MagicMock()\n-    discord_mod.Client = MagicMock\n-    discord_mod.File = MagicMock\n-    discord_mod.DMChannel = type(\"DMChannel\", (), {})\n-    discord_mod.Thread = type(\"Thread\", (), {})\n-    discord_mod.ForumChannel = type(\"ForumChannel\", (), {})\n-    discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)\n-    discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)\n-    discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)\n-    discord_mod.Interaction = object\n-    discord_mod.Embed = MagicMock\n-    discord_mod.app_commands = SimpleNamespace(\n-        describe=lambda **kwargs: (lambda fn: fn),\n-        choices=lambda **kwargs: (lambda fn: fn),\n-        Choice=lambda **kwargs: SimpleNamespace(**kwargs),\n-    )\n-    discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)\n-\n-    ext_mod = MagicMock()\n-    commands_mod = MagicMock()\n-    commands_mod.Bot = MagicMock\n-    ext_mod.commands = commands_mod\n-\n-    sys.modules.setdefault(\"discord\", discord_mod)\n-    sys.modules.setdefault(\"discord.ext\", ext_mod)\n-    sys.modules.setdefault(\"discord.ext.commands\", commands_mod)\n+    if sys.modules.get(\"discord\") is None:\n+        discord_mod = MagicMock()\n+        discord_mod.Intents.default.return_value = MagicMock()\n+        discord_mod.Client = MagicMock\n+        discord_mod.File = MagicMock\n+        discord_mod.DMChannel = type(\"DMChannel\", (), {})\n+        discord_mod.Thread = type(\"Thread\", (), {})\n+        discord_mod.ForumChannel = type(\"ForumChannel\", (), {})\n+        discord_mod.ui = SimpleNamespace(View=object, button=lambda *a, **k: (lambda fn: fn), Button=object)\n+        discord_mod.ButtonStyle = SimpleNamespace(success=1, primary=2, danger=3, green=1, blurple=2, red=3, grey=4, secondary=5)\n+        discord_mod.Color = SimpleNamespace(orange=lambda: 1, green=lambda: 2, blue=lambda: 3, red=lambda: 4)\n+        discord_mod.Interaction = object\n+        discord_mod.Embed = MagicMock\n+        discord_mod.app_commands = SimpleNamespace(\n+            describe=lambda **kwargs: (lambda fn: fn),\n+            choices=lambda **kwargs: (lambda fn: fn),\n+            Choice=lambda **kwargs: SimpleNamespace(**kwargs),\n+        )\n+        discord_mod.opus = SimpleNamespace(is_loaded=lambda: True)\n+\n+        ext_mod = MagicMock()\n+        commands_mod = MagicMock()\n+        commands_mod.Bot = MagicMock\n+        ext_mod.commands = commands_mod\n+\n+        sys.modules[\"discord\"] = discord_mod\n+        sys.modules.setdefault(\"discord.ext\", ext_mod)\n+        sys.modules.setdefault(\"discord.ext.commands\", commands_mod)\n+\n+    sys.modules[\"discord\"].AllowedMentions = _FakeAllowedMentions\n \n \n _ensure_discord_mock()","@@ -56,8 +79,9 @@ def command(self, *args, **kwargs):\n \n \n class FakeBot:\n-    def __init__(self, *, intents, proxy=None):\n+    def __init__(self, *, intents, proxy=None, allowed_mentions=None, **_):\n         self.intents = intents\n+        self.allowed_mentions = allowed_mentions\n         self.user = SimpleNamespace(id=999, name=\"Hermes\")\n         self._events = {}\n         self.tree = FakeTree()","@@ -115,8 +139,8 @@ async def test_connect_only_requests_members_intent_when_needed(monkeypatch, all\n \n     created = {}\n \n-    def fake_bot_factory(*, command_prefix, intents, proxy=None):\n-        created[\"bot\"] = FakeBot(intents=intents)\n+    def fake_bot_factory(*, command_prefix, intents, proxy=None, allowed_mentions=None, **_):\n+        created[\"bot\"] = FakeBot(intents=intents, allowed_mentions=allowed_mentions)\n         return created[\"bot\"]\n \n     monkeypatch.setattr(discord_platform.commands, \"Bot\", fake_bot_factory)","@@ -126,6 +150,13 @@ def fake_bot_factory(*, command_prefix, intents, proxy=None):\n \n     assert ok is True\n     assert created[\"bot\"].intents.members is expected_members_intent\n+    # Safe-default AllowedMentions must be applied on every connect so the\n+    # bot cannot @everyone from LLM output.  Granular overrides live in the\n+    # dedicated test_discord_allowed_mentions.py module.\n+    am = created[\"bot\"].allowed_mentions\n+    assert am is not None, \"connect() must pass an AllowedMentions to commands.Bot\"\n+    assert am.everyone is False\n+    assert am.roles is False\n \n     await adapter.disconnect()\n ","@@ -144,7 +175,11 @@ async def test_connect_releases_token_lock_on_timeout(monkeypatch):\n     monkeypatch.setattr(\n         discord_platform.commands,\n         \"Bot\",\n-        lambda **kwargs: FakeBot(intents=kwargs[\"intents\"], proxy=kwargs.get(\"proxy\")),\n+        lambda **kwargs: FakeBot(\n+            intents=kwargs[\"intents\"],\n+            proxy=kwargs.get(\"proxy\"),\n+            allowed_mentions=kwargs.get(\"allowed_mentions\"),\n+        ),\n     )\n \n     async def fake_wait_for(awaitable, timeout):","@@ -172,7 +207,7 @@ async def test_connect_does_not_wait_for_slash_sync(monkeypatch):\n \n     created = {}\n \n-    def fake_bot_factory(*, command_prefix, intents, proxy=None):\n+    def fake_bot_factory(*, command_prefix, intents, proxy=None, allowed_mentions=None, **_):\n         bot = SlowSyncBot(intents=intents, proxy=proxy)\n         created[\"bot\"] = bot\n         return bot"]},{"file":"website/docs/reference/environment-variables.md","hunks":["@@ -196,6 +196,10 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI\n | `DISCORD_IGNORED_CHANNELS` | Comma-separated channel IDs where the bot never responds |\n | `DISCORD_NO_THREAD_CHANNELS` | Comma-separated channel IDs where bot responds without auto-threading |\n | `DISCORD_REPLY_TO_MODE` | Reply-reference behavior: `off`, `first` (default), or `all` |\n+| `DISCORD_ALLOW_MENTION_EVERYONE` | Allow the bot to ping `@everyone`/`@here` (default: `false`). See [Mention Control](../user-guide/messaging/discord.md#mention-control). |\n+| `DISCORD_ALLOW_MENTION_ROLES` | Allow the bot to ping `@role` mentions (default: `false`). |\n+| `DISCORD_ALLOW_MENTION_USERS` | Allow the bot to ping individual `@user` mentions (default: `true`). |\n+| `DISCORD_ALLOW_MENTION_REPLIED_USER` | Ping the author when replying to their message (default: `true`). |\n | `SLACK_BOT_TOKEN` | Slack bot token (`xoxb-...`) |\n | `SLACK_APP_TOKEN` | Slack app-level token (`xapp-...`, required for Socket Mode) |\n | `SLACK_ALLOWED_USERS` | Comma-separated Slack user IDs |"]},{"file":"website/docs/user-guide/messaging/discord.md","hunks":["@@ -283,6 +283,10 @@ Discord behavior is controlled through two files: **`~/.hermes/.env`** for crede\n | `DISCORD_IGNORED_CHANNELS` | No | — | Comma-separated channel IDs where the bot **never** responds, even when `@mentioned`. Takes priority over all other channel settings. |\n | `DISCORD_NO_THREAD_CHANNELS` | No | — | Comma-separated channel IDs where the bot responds directly in the channel instead of creating a thread. Only relevant when `DISCORD_AUTO_THREAD` is `true`. |\n | `DISCORD_REPLY_TO_MODE` | No | `\"first\"` | Controls reply-reference behavior: `\"off\"` — never reply to the original message, `\"first\"` — reply-reference on the first message chunk only (default), `\"all\"` — reply-reference on every chunk. |\n+| `DISCORD_ALLOW_MENTION_EVERYONE` | No | `false` | When `false` (default), the bot cannot ping `@everyone` or `@here` even if its response contains those tokens. Set to `true` to opt back in. See [Mention Control](#mention-control) below. |\n+| `DISCORD_ALLOW_MENTION_ROLES` | No | `false` | When `false` (default), the bot cannot ping `@role` mentions. Set to `true` to allow. |\n+| `DISCORD_ALLOW_MENTION_USERS` | No | `true` | When `true` (default), the bot can ping individual users by ID. |\n+| `DISCORD_ALLOW_MENTION_REPLIED_USER` | No | `true` | When `true` (default), replying to a message pings the original author. |\n \n ### Config File (`config.yaml`)\n ","@@ -298,6 +302,11 @@ discord:\n   ignored_channels: []            # Channel IDs where bot never responds\n   no_thread_channels: []          # Channel IDs where bot responds without threading\n   channel_prompts: {}             # Per-channel ephemeral system prompts\n+  allow_mentions:                 # What the bot is allowed to ping (safe defaults)\n+    everyone: false               # @everyone / @here pings (default: false)\n+    roles: false                  # @role pings (default: false)\n+    users: true                   # @user pings (default: true)\n+    replied_user: true            # reply-reference pings the author (default: true)\n \n # Session isolation (applies to all gateway platforms, not just Discord)\n group_sessions_per_user: true     # Isolate sessions per user in shared channels","@@ -552,6 +561,34 @@ If you intentionally want a shared room conversation, leave it off — just expe\n Always set `DISCORD_ALLOWED_USERS` to restrict who can interact with the bot. Without it, the gateway denies all users by default as a safety measure. Only add User IDs of people you trust — authorized users have full access to the agent's capabilities, including tool use and system access.\n :::\n \n+### Mention Control\n+\n+By default, Hermes blocks the bot from pinging `@everyone`, `@here`, and role mentions, even if its reply contains those tokens. This prevents a poorly-worded prompt or echoed user content from spamming a whole server. Individual `@user` pings and reply-reference pings (the little \"replying to…\" chip) stay enabled so normal conversation still works.\n+\n+You can relax these defaults via either env vars or `config.yaml`:\n+\n+```yaml\n+# ~/.hermes/config.yaml\n+discord:\n+  allow_mentions:\n+    everyone: false      # allow the bot to ping @everyone / @here\n+    roles: false         # allow the bot to ping @role mentions\n+    users: true          # allow the bot to ping individual @users\n+    replied_user: true   # ping the author when replying to their message\n+```\n+\n+```bash\n+# ~/.hermes/.env — env vars win over config.yaml\n+DISCORD_ALLOW_MENTION_EVERYONE=false\n+DISCORD_ALLOW_MENTION_ROLES=false\n+DISCORD_ALLOW_MENTION_USERS=true\n+DISCORD_ALLOW_MENTION_REPLIED_USER=true\n+```\n+\n+:::tip\n+Leave `everyone` and `roles` at `false` unless you know exactly why you need them. It is very easy for an LLM to produce the string `@everyone` inside a normal-looking response; without this protection, that would notify every member of your server.\n+:::\n+\n For more information on securing your Hermes Agent deployment, see the [Security Guide](../security.md).\n \n \n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}