# Patch Analysis — CVE-2026-59092 (JuiceFS DefaultServeMux pprof exposure)

## Fix under analysis

- **Repository**: `github.com/juicedata/juicefs`
- **Fix commit**: `a46979cdd4082217081ee99b931ddc53d038e47a`
- **PR**: #7214 — *"cmd: use a dedicated ServeMux to avoid exposing pprof/metrics"*
- **Parent (vulnerable) commit**: `f60a90fc0ad52d2bb1f44f38a04d55044fc91d50`
- **Files changed by the fix** (3 source files + 3 test files):
  - `cmd/mount.go` — `exposeMetrics()`
  - `pkg/fs/http.go` — `StartHTTPServer()` (WebDAV)
  - `pkg/sync/cluster.go` — `startManager()` (sync cluster manager)
  - `cmd/mount_test.go`, `pkg/fs/http_test.go`, `pkg/sync/cluster_test.go` — regression tests asserting `/debug/pprof/cmdline` returns 404.

## What the fix changes

The fix replaces the shared `http.DefaultServeMux` with a **dedicated `http.NewServeMux()`** in exactly three HTTP-serving sites, and passes that dedicated mux as the handler instead of `nil`:

1. **`cmd/mount.go` — `exposeMetrics()`** (metrics port, used by `mount`, `gateway`, `sync`, `mdtest`):
   ```go
   mux := http.NewServeMux()
   mux.Handle("/metrics", promhttp.HandlerFor(...))
   ...
   if err := http.Serve(ln, mux); err != nil { ... }   // was: http.Serve(ln, nil)
   ```

2. **`pkg/fs/http.go` — `StartHTTPServer()`** (WebDAV port, `http.ListenAndServe` / `ListenAndServeTLS`):
   ```go
   func newWebdavHandler(...) http.Handler {
       ...
       mux := http.NewServeMux()
       mux.Handle("/", h)
       return mux
   }
   func StartHTTPServer(...) {
       handler := newWebdavHandler(...)
       err = http.ListenAndServeTLS(config.Addr, ..., handler)   // was: nil
       err = http.ListenAndServe(config.Addr, handler)           // was: nil
   }
   ```

3. **`pkg/sync/cluster.go` — `startManager()`** (sync cluster manager `/fetch` + `/stats`):
   ```go
   mux := http.NewServeMux()
   mux.HandleFunc("/fetch", ...)
   mux.HandleFunc("/stats", ...)
   go func() { _ = http.Serve(l, mux) }()                       // was: http.Serve(l, nil)
   ```

Because these three servers no longer serve `DefaultServeMux`, the pprof handlers (registered on `DefaultServeMux` by the blank import `_ "net/http/pprof"` in `cmd/mount.go`) are no longer reachable on the **metrics**, **WebDAV**, or **sync-cluster** ports. The fix's own regression tests confirm `/debug/pprof/cmdline` returns HTTP 404 on the metrics/cluster ports after the fix.

## Fix assumptions

The fix assumes that **every remotely-reachable HTTP server in JuiceFS that previously served `DefaultServeMux`** is one of the three sites above. Implicitly it assumes:

- The only places that call `http.Serve(..., nil)`, `http.ListenAndServe(..., nil)`, or `http.ListenAndServeTLS(..., nil)` are `exposeMetrics`, `StartHTTPServer`, and `startManager`.
- Any *other* `nil`-handler HTTP server is intentionally localhost-only and therefore not part of the remote exposure surface.

## What the fix does NOT cover (the gap)

A whole-codebase scan of the **fixed** commit for `http.(Serve|ListenAndServe|ListenAndServeTLS)(..., nil)` and for `http.Handle/HandleFunc` shows the fix left **two `nil`-handler servers untouched**, both serving `DefaultServeMux`:

1. **`cmd/main.go:336` — the debug agent** (started for *every* subcommand unless `--no-agent`):
   ```go
   if !c.Bool("no-agent") {
       go debugAgentOnce.Do(func() {
           for port := 6060; port < 6100; port++ {
               debugAgent = fmt.Sprintf("127.0.0.1:%d", port)     // cmd/main.go:334
               _ = http.ListenAndServe(debugAgent, nil)          // cmd/main.go:336  <-- DefaultServeMux
           }
       })
   }
   ```

2. **`sdk/java/libjfs/main.go:573` — the Java SDK native debug agent** (gated by `jConf.Debug || JUICEFS_DEBUG`):
   ```go
   _ = http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), nil)  // <-- DefaultServeMux
   ```

`git show <fix> -- cmd/main.go` returns an **empty diff**, proving the fix did not touch the debug agent at all. Both sites still import `_ "net/http/pprof"` (via `cmd/main.go:22` and `sdk/java/libjfs/main.go:54`), so the pprof handlers — including `/debug/pprof/cmdline` — are registered on `DefaultServeMux` and served by these `nil`-handler listeners.

## Behavior before vs. after the fix

| HTTP surface | Bind address | Before fix | After fix |
|---|---|---|---|
| Metrics port (`exposeMetrics`) | operator-configurable, `0.0.0.0:9567` in repro | pprof exposed (HTTP 200, leaks cmdline) | **404** (dedicated mux) ✓ |
| WebDAV port (`StartHTTPServer`) | operator-configurable | pprof exposed | pprof not exposed ✓ |
| Sync cluster manager (`startManager`) | ephemeral | pprof exposed | pprof not exposed ✓ |
| **Debug agent (`cmd/main.go:336`)** | **hardcoded `127.0.0.1:6060+`** | pprof exposed, leaks cmdline | **still exposed, still leaks cmdline** ✗ |
| **Java SDK debug agent (`sdk/java/libjfs/main.go:573`)** | **hardcoded `127.0.0.1:6060+`** | pprof exposed, leaks cmdline | **still exposed, still leaks cmdline** ✗ |

## Is the fix complete?

**For the remotely-reachable surface claimed by the CVE: yes.** All three operator-bindable servers (metrics / WebDAV / sync cluster) now use isolated muxes, so a remote unauthenticated attacker can no longer reach pprof on those ports. This was verified at runtime: the fixed binary returns HTTP 404 for `/debug/pprof/cmdline` on the metrics port while `/metrics` still returns 200 (no regression).

**For the *same root-cause mechanism* on the remaining `DefaultServeMux` consumers: no.** The debug agent and the Java SDK debug agent still serve `DefaultServeMux` and still expose `/debug/pprof/cmdline` (with the identical credential-leak impact) on the fixed commit. The only thing preventing this from being a *remote* bypass is that both are hardcoded to `127.0.0.1` and there is no flag that rebinds them to `0.0.0.0` (the only related flag is `--no-agent`, which *disables* the agent). This is therefore a **local-hardening / fix-coverage gap** rather than a remote bypass of the original CVE.

## Target threat model / security policy

- The repository has **no `SECURITY.md`**. `README.md` links only to external data-encryption docs (`juicefs.com/docs/community/security/encrypt`); there is no documented threat model covering debug ports or pprof.
- The debug agent is an **intentional** debugging feature: the `--no-agent` flag's usage string is *"disable pprof (:6060) agent"*, documenting its existence and purpose. Its localhost binding is a deliberate scope limitation.
- The original CVE's claimed surface is **unauthenticated *remote* attackers** via the operator-bindable metrics port. The debug agent does **not** cross that remote trust boundary (it is localhost-only). It does, however, cross a **local user-to-user / process-to-process** trust boundary on shared hosts: a co-located unprivileged user (or an SSRF from a co-located web service) can read `127.0.0.1:6060/debug/pprof/cmdline` and steal the JuiceFS operator's metadata-engine credentials from a *fixed* deployment that was not started with `--no-agent`.

## Variant conclusion

The fix is **surgical and correct for the remote surface**, but **incomplete for the same root cause**: the `nil`-handler debug agents in `cmd/main.go:336` and `sdk/java/libjfs/main.go:573` were not converted to dedicated muxes and still expose pprof (including the credential-leaking `/debug/pprof/cmdline`) on the fixed commit — reachable on localhost (and thus to co-located local users / SSRF), disableable only via `--no-agent`. A complete fix should isolate these muxes as well (or register pprof on a dedicated mux that is never served by the debug agent's listener).
