mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4444] [Bug]: Unauthenticated Mooncake HTTP metadata server allows segment descriptor poisoning and KV transfer redirection #1122
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#1122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @yyymk on GitHub (Oct 1, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4444
Bug Report
Summary
The HTTP metadata server shipped in the mooncake wheel serves GET/PUT/DELETE on
/metadatawithout any authentication. A network peer can read every engine's segment descriptor (absolute buffer addresses and data ports), overwrite a victim engine's descriptor to redirect live KV transfers to an attacker-controlled listener while the sender reports success, rewriterpc_metaentries by bypassing the duplicate-key check with DELETE, delete cluster metadata to break new transfers, and grow the server's memory without bound. Runtime-confirmed with themooncake.http_metadata_servercomponent of mooncake-transfer-engine 0.3.11.post1; the same unauthenticated handler still ships in the 0.3.13 wheel and on the main branch asmooncake-transfer-engine/example/http-metadata-server-python/bootstrap_server.py. No fix available. Suggested severity: High, CVSS 8.8.Details
mooncake/http_metadata_server.py(started viapython -m mooncake.http_metadata_server, default bind 0.0.0.0:8080) is the documented HTTP backend forTransferEnginemetadata, used as the etcd alternative via a conn string likehttp://host:28080/metadata(HTTPStoragePluginin src/transfer_metadata_plugin.cpp).The handlers are plain key/value operations with no authentication and no authorization:
GET /metadata?key=mooncake/ram/<name>returns the engine's segment descriptor, includingbuffers[].addr(absolute process addresses),tcp_data_port, and RDMA rkeys.PUToverwrites any segment descriptor. The only integrity check in the file is a duplicate-key rejection forrpc_metakeys - andDELETEremoves the key first, soDELETE+PUTrewritesmooncake/rpc_meta/<name>(the handshake RPC location) without restriction.DELETEremoves any key. Request bodies are read fully into memory and the store dict has no size or key-count limit (aiohttp's defaultclient_max_sizeof 1 MB rejects one huge body, many smaller keys bypass it).The data plane trusts these values.
TcpTransport::startTransferconnects to the IP fromrpc_metaplustcp_data_portfrom the segment descriptor, with no validation that the descriptor is authentic. Aftertcp_data_portis poisoned, a normaltransfer_sync_writesends the payload to the attacker's listener. The sender still reports success: the client session completes once the payload is written to the socket and the server side acknowledges nothing.PoC
Environment: Linux x86_64, Python 3.11,
pip install mooncake-transfer-engine==0.3.11.post1on two shells of one host (use the host IP 192.168.105.123 below). Terminal 1 starts the server:Terminal 2 runs the receiving victim:
Terminal 3 is the attacker listener:
Sender, run as a fresh process each round (the segment descriptor is cached per process):
Control round: run
sender.pyonce; it printstransfer returned 0and victim A's buffer now starts withSECRET_KV_.Attack round:
Observed: the sender prints
transfer returned 0, the listener prints 88 bytes consisting of the 24-byte protocol header (including the target absolute address) followed by the 64-byteSECRET_KV_...payload, and victim A's buffer is unchanged - the payload was exfiltrated with neither endpoint reporting an error.rpc_meta duplicate-check bypass:
Resource exhaustion:
PUTa 900 KB body to 2800 distinct keys; the server process RSS grew from 39 MB to about 2.5 GB.Impact
This is a missing-authorization issue (CWE-306) on the cluster metadata control plane. The attacker needs no credentials, only reachability of the metadata server port, which is a fixed, operator-chosen port bound on all interfaces by default.
Confidentiality: KV cache data (user prompts and context) is exfiltrated to an attacker-controlled listener while both endpoints believe the transfer succeeded; segment descriptors also leak absolute process addresses and rkeys. Integrity: cluster metadata and transfer destinations are attacker-controlled; combined with the rpc_meta rewrite, handshakes can be redirected to an attacker host. Availability: deleting
mooncake/ram/*keys makes every new transfer fail with a metadata error, and the unbounded store allows OOM of the metadata server. Affected: deployments using the HTTP metadata backend (the documented etcd alternative); the component ships in the wheel through 0.3.13.post1 and as an unauthenticated example on the current main branch. Suggested CVSS:3.1 vector AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (8.8). No patched version yet.Before submitting...
@github-actions[bot] commented on GitHub (Oct 1, 2026):
Thanks for opening this issue, @yyymk!
133664083A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@he-yufeng commented on GitHub (Oct 4, 2026):
Confirmed on current main, same as the other two reports in this batch:
python/mooncake/http_metadata_server.pystill serves GET/PUT/DELETE with no authentication and an unbounded store, and the descriptor-poisoning path throughtcp_data_portworks exactly as the PoC describes, since the data plane trusts whatever the metadata backend returns.I am preparing a fix with this shape:
MC_METADATA_HTTP_TOKENset (sameMC_env family asMC_REDIS_PASSWORD), every/metadatarequest must carryAuthorization: Bearer <token>or it gets a 401. Unset keeps today's behavior so existing trusted-network deployments do not break, but the server logs a loud startup warning when it binds a non-loopback address without a token.HTTPStoragePluginreads the same env and attaches the header on GET/PUT/DELETE, so engine traffic authenticates transparently with no config change beyond the env var.MC_METADATA_MAX_KEYS/MC_METADATA_MAX_TOTAL_BYTEScaps, with over-budget writes rejected (507) instead of growing RSS without limit. That closes the OOM half of the report.bootstrap_server.pygets a startup warning pointing operators at the wheel server for anything beyond local testing, and the deployment docs get a paragraph recommending the token for any reachable bind.What this deliberately does not do: cryptographic validation of segment descriptors on the data plane. Signed descriptors are a protocol-level change and belong in a separate design discussion; this PR closes the unauthenticated write path that makes poisoning trivial.
Will link the PR here.
@he-yufeng commented on GitHub (Oct 4, 2026):
PR up: #4472. Auth token, bounded store, client-side header, docs, and the example warning, all as described above.