Skip to content

Back to the catalog

Missing authentication on MCP HTTP endpoint allows unauthenticated privileged tool calls
github.comadvisory

Missing authentication on MCP HTTP endpoint allows unauthenticated privileged tool calls

GHSA advisory for jovancoding/network-ai disclosing that its MCP HTTP transport (bin/mcp-server.ts, lib/mcp-transport-sse.ts) accepts JSON-RPC tools/call requests with zero authentication, session, or origin checks, and binds to 0.0.0.0 by default. The PoC shows an unauthenticated attacker can enumerate 22 registered tools and invoke privileged ones - config_get/config_set to read and mutate live orchestrator configuration, agent_list/agent_spawn/agent_stop, token_create/token_revoke, budget_set_ceiling, and blackboard_write/delete - with curl requests against a local Docker deployment. Remediation recommended enforcing bearer-token auth in _handlePost() before dispatch, defaulting to 127.0.0.1 binding, and gating state-mutating tools behind authorization checks.

TL;DR Unauthenticated MCP HTTP endpoint in jovancoding/network-ai lets anyone with network access invoke privileged orchestrator tools such as config mutation, agent control, and token minting.
Document

What it is

GHSA-fj4g-2p96-q6m3 for jovancoding/network-ai: the MCP HTTP transport dispatches JSON-RPC tools/call requests straight to the orchestrator's tool registry with no authentication, session, origin, or token check, while binding to 0.0.0.0 by default. Anyone with network reach can enumerate and invoke privileged management tools.

How it works

The advisory pins the failure to specific lines:

  • bin/mcp-server.ts:75 — binds 0.0.0.0 by default.
  • lib/mcp-transport-sse.ts:379 — _handlePost() parses the JSON-RPC body and calls this._bridge.handleRPC(rpc) with no auth check.
  • lib/mcp-transport-sse.ts:155 — handleRPC() dispatches tools/call directly to provider.call(toolName, toolArgs).
  • lib/mcp-tools-control.ts:231 — config_set mutates runtime config in place via this._config[key] = parsed.

The exposed endpoints are /tools (inventory) and /mcp (JSON-RPC). config_get at lib/mcp-tools-control.ts:80 leaks live runtime config including auditLogPath, trustConfigPath, grantTokenTTL, and blackboardPath.

Preconditions / Affected

Any deployment reachable on a routable interface — the default 0.0.0.0 bind plus the missing auth gate is the whole exposure. PoC was run against a local Docker build bound to localhost:13001.

What to do

The advisory's own PoC doubles as a verification check. List tools:

curl http://localhost:13001/tools

Read and mutate live config unauthenticated:

curl http://localhost:13001/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"config_get","arguments":{}}}'

curl http://localhost:13001/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"config_set","arguments":{"key":"defaultTimeout","value":"12345"}}}'

A 200 with "applied": true confirms the mutation persisted on the next read.

Remediation, in order: enforce a shared-secret/bearer token inside _handlePost() before handleRPC(); default bind to 127.0.0.1 and require explicit opt-in (warning on startup) for non-loopback binding without auth; as defense in depth, gate the state-mutating tools (config_set, agent_spawn, agent_stop, token_create, token_revoke, budget_set_ceiling, fsm_transition, blackboard_write, blackboard_delete) behind a verified caller identity.

Caveats

Testing was local-Docker only; no third-party deployment was tested, and the local build required a minor Dockerfile fix (the code path under test was unmodified). The agent_list PoC returned an empty array because the test environment had no agents — not because a control blocked it.

Technologies named here

Techniques named here