PraisonAI DiscordApproval accepts unrelated channel messages as dangerous-tool approvals
praisonai.bots.DiscordApproval approves a pending dangerous tool call when it
sees any later non-bot message in the configured Discord channel whose text is
classified as approval, such as yes.
The decision is not bound to:
As a result, any user who can post in the configured approval channel can
approve a pending high-risk tool call by sending yes after the approval
message appears. The same local PoV also shows that the Slack and Telegram
messaging approval backends have no explicit approver allowlist parameter, but
the primary report-grade issue is the Discord backend's unthreaded channel
cross-talk: the approving message does not need to be a reply or otherwise
request-bound.
MervinPraison/PraisonAIpippraisonaisrc/praisonai/praisonai/bots/_discord_approval.pysrc/praisonai/praisonai/bots/_slack_approval.pysrc/praisonai/praisonai/bots/_telegram_approval.py4.6.58origin/main validated:
1ad58ca02975ff1398efeda694ea2ab78f20cf3eorigin/main tag validated: v4.6.58Suggested affected range:
pip:praisonai >= 4.5.2, <= 4.6.58
Representative local sweep:
4.5.0 Discord approval backend not present
4.5.2 vulnerable
4.5.128 vulnerable
4.6.9 vulnerable
4.6.10 vulnerable
4.6.56 vulnerable
4.6.57 vulnerable
4.6.58 vulnerable
DiscordApproval.request_approval() posts an approval message to the configured
channel and records the returned message id.
_poll_for_response() then polls channel history with:
f"/channels/{channel_id}/messages?after={message_id}&limit=10"
For each later non-bot message, it reads content, classifies the text, and
returns ApprovalDecision(approved=True) when the text is approve/yes.
There is no check that the message is a Discord reply to the approval message,
belongs to a request-specific thread, came from an intended approver, or
contains a request-specific approval token.
Important source evidence from origin/main:
_discord_approval.py lines 57-73: constructor accepts token,
channel_id, timeout, and poll_interval; no approver allowlist._discord_approval.py line 239: polls all messages after the approval
message in the configured channel._discord_approval.py lines 252-265: skips bot messages, classifies the
remaining text, and approves when the keyword is approve.The Slack and Telegram backends are better request-scoped than Discord:
conversations.replies for the approval message thread.However, both still lack an explicit approver identity parameter. They are included in the PoV and suggested fix because the authorization model should be consistent across all messaging approval backends.
Run against the latest local checkout:
python3 poc/pov_prai_cand_029_messaging_approval_channel_member_bypass.py \
--repo ../../artifacts/repos/praisonai-v4.6.58 \
--json
The PoV is local-only. It mocks Slack, Telegram, and Discord API helpers in-process and does not contact those services.
For Discord, the mock sequence is:
DiscordApproval posts a critical execute_command approval request to
D_APPROVAL_CHANNEL.D_INTRUDER with content yes.DiscordApproval returns approved=True and approver="D_INTRUDER".Observed output from evidence/pov-v4.6.58.json:
{
"approval_from_unconfigured_channel_participant": {
"discord": true,
"slack": true,
"telegram": true
},
"backends": [
{
"backend": "discord",
"configured_channel_id": "D_APPROVAL_CHANNEL",
"decision_approved": true,
"decision_approver": "D_INTRUDER",
"decision_reason": "Approved via Discord by intruder",
"intruder_user": "D_INTRUDER"
}
],
"no_explicit_approver_allowlist_parameters": true,
"vulnerable": true
}
The command in the approval request is a harmless local sentinel:
touch /tmp/prai-cand-029. The PoV stops at the approval decision; it does not
execute the tool.
This report does not claim that a deliberately private approval channel with only trusted approvers is unsafe by itself. The narrower issue is that the Discord backend treats an unrelated later channel message as the approval decision for a specific dangerous tool request.
PraisonAI's approval documentation describes approval as a safety control that
pauses before dangerous tools and asks a human or channel to allow or deny the
specific request. A random later yes in the channel is not evidence that an
intended approver reviewed that request.
The existing Slack and Telegram implementations already show request-binding patterns that Discord lacks:
message_id.The Discord backend should provide at least the same request binding and should also support explicit approver identity checks for deployments where channel membership is broader than approval authority.
If an application uses DiscordApproval for dangerous tools such as shell
commands, file writes, deletes, deployments, or other privileged operations, a
low-privileged Discord user with write access to the configured approval channel
can approve pending dangerous tool executions.
This can lead to code execution, file modification, deployment changes, or data access with the privileges of the PraisonAI process, depending on which tools the agent exposes behind approval.
The attacker does not need the LLM API key, shell access, repository access, or PraisonAI process access. They only need to be able to post an approval-looking message in the configured approval channel after the approval prompt appears.
Suggested severity: High.
Rationale:
AV: the attacker interacts through a networked Discord channel.AC: sending yes after an approval prompt is enough.PR: the attacker needs permission to post in the configured approval
channel, but no approver-specific permission.UI: no separate victim interaction is needed after the prompt exists.S: the vulnerable approval backend and approved tool run in the PraisonAI
application's security scope.C/I/A: approved dangerous tools can disclose, modify, or destroy data
depending on the configured agent tools.Recommended fixes:
message_reference.message_id matches the approval message.approver_user_ids or allowed_approvers.ApprovalDecision(approved=True).yes in the channel is ignored;Why this VPI (explainable, experimental)
VPI breakdown
| Impact | 88.00 |
| Exploitation signal(No additional exploitation signal) | ×1.00 |
| VPI | 88.00 |
VPI formula vpi-v1