gateway: Implement suppress_system_messages for WhatsApp/Discord (fix internal message leakage) - #24365
gateway: Implement suppress_system_messages for WhatsApp/Discord (fix internal message leakage)#24365pcmarcon wants to merge 1 commit into
Conversation
… internal message leakage)
- Add suppress_system_messages support to _deliver_platform_notice()
- Suppress session reset notifications for customer-facing platforms
- Auto-deny dangerous commands silently (logged to gateway.log)
- Add documentation for suppress_system_messages configuration
Fixes: Internal system messages leaking to customers in production
|
Bug: escaped newlines in approval prompt message In the non-suppressed path (lines near the approval prompt), the f-strings changed from # Before (correct):
f"Dangerous command requires approval:\n"
f"```\n{cmd_preview}\n```\n"
# After (bug — produces literal backslash-n text):
f"Dangerous command requires approval:\\n"
f"```\\n{cmd_preview}\\n```\\n"
This appears to be an unintended change — the |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real customer-facing gateway concern. The underlying behavior is still present on current main: gateway/run.py:11411-11418 emits the home-channel notice, gateway/slash_commands.py:325-327 returns reset banners, and gateway/run.py:18542-18569 sends fallback approval prompts.
Problems
gateway/run.py:15139-15147logs an “auto-denied” command and returns, but never callsresolve_gateway_approval(..., "deny"). The agent therefore remains blocked;gateway/slash_commands.py:4384-4425andtests/gateway/test_approve_deny_commands.py:425-516show resolution is required.gateway/run.py:8237-8253only suppresses the reset banner whensession_infois truthy; the header still leaks through the empty-session-info path.gateway/run.py:7891-7909uses broad phrase matching ("pronto","enviei", etc.) and can discard substantive WhatsApp replies after tool calls.gateway/run.py:15150-15152changes newlines to literal\\n, matching the formatting regression reported in the existing review comment.
Suggested changes
- Rework this against current
GatewayConfigdelivery and approval boundaries, resolve suppressed approvals explicitly asdeny, and add focused notice/reset/approval regression tests.
Automated hermes-sweeper review.
| _has_tool_calls = bool(agent_result.get("messages") and any( | ||
| msg.get("tool_calls") for msg in agent_result.get("messages", []) | ||
| )) | ||
| _is_narration = any(phrase in response.lower() for phrase in [ |
There was a problem hiding this comment.
This phrase list is not a reliable narration classifier: a substantive customer response after a tool call can legitimately say pronto, enviei, or fiz, and would be dropped. Please use structural system-message classification rather than response wording.
| @@ -8182,6 +8235,20 @@ async def _handle_reset_command(self, event: MessageEvent) -> Union[str, Ephemer | |||
| _tip_line = "" | |||
|
|
|||
| if session_info: | |||
There was a problem hiding this comment.
Suppression only runs when session_info is truthy. _format_session_info() can fall back to an empty string, and that path still returns the reset header below. Apply the policy before this conditional so every reset notification is covered.
| _cfg = _load_gateway_config() or {} | ||
| _platform_cfg = (_cfg.get("display") or {}).get("platforms") or {} | ||
| _plat_cfg = _platform_cfg.get(source.platform.value) or {} | ||
| if _plat_cfg.get("suppress_system_messages", False): |
There was a problem hiding this comment.
Returning here does not auto-deny the pending command; it only stops notification delivery. Resolve the registered gateway approval with a deny result so the blocked agent thread receives a definitive denial instead of waiting for timeout.
| f"⚠️ **Dangerous command requires approval:**\n" | ||
| f"```\n{cmd_preview}\n```\n" | ||
| f"Reason: {desc}\n\n" | ||
| f"⚠️ **Dangerous command requires approval:**\\n" |
There was a problem hiding this comment.
This emits literal \\n characters instead of line breaks on the normal fallback path. Restore single \n escapes; this is unrelated to suppression and was also identified in the existing review comment.
Restart lifecycle replies are deterministic gateway-internal output, but the adapter sinks treated them as ordinary text and defaulted them back to the origin chat. When /restart was triggered from a group, the immediate command reply and post-restart notification could therefore expose operational status to the shared audience. Add a PrivateReply wrapper and adapter sink helper that prefer a real send_private_notice implementation for shared-audience sources, while refusing to reuse the default public fallback for confidential text. /restart now persists the requester user id for the restarted process, marks restart status as private, and sends only a neutral public fallback when private delivery is not available. Related NousResearch#48060 Related NousResearch#24365 Co-authored-by: Aldo <17973757+aldoeliacim@users.noreply.github.com> Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
gateway: Implement suppress_system_messages for WhatsApp/Discord (fix internal message leakage)
🐛 Problem
The configuration
suppress_system_messages: trueinconfig.yamlwas not fully implemented ingateway/run.py. Internal system messages continued leaking to end customers in customer-facing deployments (WhatsApp, Discord, etc.).Messages that were leaking:
Configuration warnings
gateway/run.py:7440via_deliver_platform_notice()Session reset notifications
gateway/run.py:8165(useslocales/en.yaml:222)Dangerous command approval prompts
gateway/run.py:15103Assistant narration (already fixed in previous commit)
gateway/run.py:7867-7893Current workaround (unsustainable):
Deployments required manual patches in 3+ locations of
gateway/run.py:✅ Solution
Implemented native
suppress_system_messagessupport for all customer-facing platforms (WhatsApp, Discord, Slack, Telegram).Changes made:
1.
_deliver_platform_notice()— Suppress configuration warningsFile:
gateway/run.py(line ~5600)2. Session reset — Suppress reset notifications
File:
gateway/run.py(line ~8221)3. Dangerous command approval — Silent auto-deny
File:
gateway/run.py(line ~15129)📖 Configuration
Example config.yaml for customer-facing deployment:
Recommended setup:
suppress_system_messages: truesuppress_system_messages: falseThis allows customers to see only business-related messages while admins receive full system notifications for debugging.
📝 Logging Behavior
All suppressed messages are logged to
gateway.log:To view suppressed messages in real-time:
hermes logs --follow --level DEBUG --grep "Suppressed"🧪 Testing
Manual testing steps:
Configure WhatsApp with suppress_system_messages: true
Test "No home channel" suppression:
Test session reset suppression:
/newcommand from WhatsAppTest dangerous command suppression:
rm -rf /tmp) from WhatsAppVerify admin channel still receives messages:
suppress_system_messages: false📚 Documentation
New documentation file created:
website/docs/user-guide/messaging/suppress-system-messages.mdThis guide covers:
🔒 Security Considerations
Risks:
Mitigations:
gateway.logsuppress_system_messages: falsefor monitoring🎯 Impact
📸 Screenshots
Before (message leakage):
After (clean customer experience):
📋 Checklist
gateway/run.pywebsite/docs/🚀 Deployment Notes
After merging, deployments should:
suppress_system_messages: trueto customer-facing platform configsgateway.logfor suppressed messages during initial rolloutFixes issue: Internal system messages leaking to customers in production deployments
Reported by: Tchê Gourmet (WhatsApp deployment)
Type: Bug fix + Feature enhancement