Skip to content

POST /api/mcp accepts any 64-hex Bearer — full tool session with no on-chain check, sessions pin ip_active_cap until DELETE #707

Description

@dun999

Bug

https://relayer.memory.walrus.xyz/api/mcp (Streamable HTTP) is advertised as OAuth-protected. Unauthenticated requests get 401 plus WWW-Authenticate: Bearer resource_metadata=....

If Authorization is Bearer plus any 64-character hex string, and X-MemWal-Account-Id is any 0x + 64 hex chars, the relayer treats the caller as a legacy delegate-key client and the sidecar opens a real MCP session.

Nothing checks that the key is a registered delegate, that the account object exists, or that an OAuth token was issued. The session is given the full tool set (memwal_remember, memwal_remember_bulk, memwal_analyze, memwal_restore, memwal_recall, memwal_health).

Streamable HTTP sessions stay in memory until DELETE /api/mcp or a sidecar restart. There is no idle timeout. Each leftover session holds an ip_active_cap slot (default 16 per IP). Repeating initialize without DELETE eventually returns 429 with MCP rate limit: ip_active_cap and blocks further MCP use from that IP, including real clients.

Writes/recalls still fail later on the signed relayer API (401) because the key is not on-chain. The MCP layer itself has already accepted the session, listed write tools, and served memwal_health.

Relevant code:

  • Relayer classifies a 64-hex Bearer as a legacy delegate and skips OAuth (services/server/src/mcp_proxy.rs, is_legacy_delegate_bearer / classify_and_resolve).
  • That path stamps full read+write scope (apply_internal_headers when identity is None).
  • Sidecar resolveAuth only checks header shape (services/server/scripts/mcp/auth.ts).
  • Streamable sessions are stored until DELETE (services/server/scripts/mcp/index.ts); no idle reap.

How to reproduce

Against production (or any relayer with MCP + OAuth enabled):

KEY=$(python3 -c 'print("11"*32)')
ACCT="0x$(python3 -c 'print("22"*32)')'

# 1) initialize — expect HTTP 200 and an mcp-session-id
curl -sS -D - -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
  -H "Authorization: Bearer $KEY" \
  -H "X-MemWal-Account-Id: $ACCT" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"repro","version":"0"}}}'

Copy mcp-session-id from the response headers, then:

SID='<mcp-session-id from step 1>'

# 2) tools/list — expect all six memwal_* tools, including writes
curl -sS -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
  -H "Authorization: Bearer $KEY" \
  -H "X-MemWal-Account-Id: $ACCT" \
  -H "mcp-session-id: $SID" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

# 3) memwal_health — expect a successful tool result (relayer /health is public)
curl -sS -X POST "https://relayer.memory.walrus.xyz/api/mcp" \
  -H "Authorization: Bearer $KEY" \
  -H "X-MemWal-Account-Id: $ACCT" \
  -H "mcp-session-id: $SID" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"memwal_health","arguments":{}}}'

To see the cap:

# 4) Repeat step 1 ~16 times without DELETE.
# Later initialize calls return:
#   HTTP 429
#   {"jsonrpc":"2.0","error":{"code":-32000,"message":"MCP rate limit: ip_active_cap. Try again in 30s."},"id":null}

Cleanup for any sessions you opened:

curl -sS -X DELETE "https://relayer.memory.walrus.xyz/api/mcp" \
  -H "Authorization: Bearer $KEY" \
  -H "X-MemWal-Account-Id: $ACCT" \
  -H "mcp-session-id: $SID"

Observed on https://relayer.memory.walrus.xyz/api/mcp (relayer version 0.1.0, serverInfo.version 0.0.1).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions