{"repro_id":"REPRO-2026-00220","version":7,"title":"JuiceFS through 1.3.1 exposes debug/metrics endpoints via shared http.DefaultServeMux, enabling authentication bypass and leakage of sensitive metadata connection strings, with potential DoS via profiling handlers.","repro_type":"security","status":"published","severity":"high","cvss_score":7.0,"description":"JuiceFS through 1.3.1 contains an authentication bypass in its HTTP debug/metrics handler registration. Because handlers are registered on the shared `http.DefaultServeMux`, unauthenticated remote attackers can access sensitive `/debug/pprof/*` and metrics endpoints. The `/debug/pprof/cmdline` endpoint can leak process command-line arguments that include metadata engine connection strings with database credentials, enabling full read/write access to filesystem metadata. Other pprof handlers leak internal state and profiling handlers can be abused for denial-of-service.","root_cause":"# Root Cause Analysis: CVE-2026-59092\n\n## Summary\n\nJuiceFS through 1.3.1 exposes debug and metrics HTTP endpoints via the shared `http.DefaultServeMux`, enabling unauthenticated remote attackers to access sensitive `/debug/pprof/*` handlers. The `/debug/pprof/cmdline` endpoint leaks the full process command line, which includes metadata engine connection strings containing database credentials (e.g., Redis passwords), granting full read/write access to filesystem metadata. Other pprof handlers (`/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`) leak internal runtime state and can be abused for denial-of-service via CPU profiling.\n\n## Impact\n\n- **Package/component affected**: `cmd/mount.go` (`exposeMetrics` function), `pkg/fs/http.go` (`StartHTTPServer` for WebDAV), `pkg/sync/cluster.go` (`startManager`)\n- **Affected versions**: JuiceFS through 1.3.1 (fixed in commit `a46979cdd4082217081ee99b931ddc53d038e47a`)\n- **Risk level**: High (CVSS 7.7) — unauthenticated credential disclosure leading to metadata engine compromise and potential DoS\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact**: Authentication bypass; disclosure of metadata engine connection strings with DB credentials via `/debug/pprof/cmdline`; access to internal state via other pprof handlers; potential DoS via profiling endpoints\n- **Reproduced impact from this run**: \n  - Unauthenticated access to `/debug/pprof/cmdline` returns HTTP 200 with full command line including Redis password `s3cr3tPass` from the metadata URL `redis://:s3cr3tPass@127.0.0.1:6379/1`\n  - All pprof endpoints (`/debug/pprof/`, `/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`) return HTTP 200 without authentication\n  - Fixed version returns HTTP 404 for all pprof endpoints while `/metrics` still works\n- **Parity**: `full` — the authentication bypass and credential leakage are fully demonstrated through the real product (JuiceFS gateway) via the remote HTTP API surface\n- **Not demonstrated**: Full metadata compromise (using the leaked credentials to access the Redis metadata engine) and DoS via profiling — these are downstream consequences of the credential leak, not separate reproduction targets\n\n## Root Cause\n\nThe `exposeMetrics()` function in `cmd/mount.go` calls `http.Handle(\"/metrics\", ...)` which registers the metrics handler on the shared `http.DefaultServeMux`. It then starts the HTTP server with `http.Serve(ln, nil)`, where the `nil` handler defaults to `http.DefaultServeMux`.\n\nSince the `net/http/pprof` package is imported via `_ \"net/http/pprof\"` in multiple files (`cmd/main.go`, `cmd/mount.go`, `cmd/gateway.go`, `cmd/format.go`, `cmd/sync.go`), its `init()` function registers pprof handlers (`/debug/pprof/cmdline`, `/debug/pprof/heap`, `/debug/pprof/goroutine`, `/debug/pprof/profile`, etc.) on `http.DefaultServeMux` at program startup. As a result, all pprof endpoints are served without authentication alongside the metrics endpoint on whatever address the metrics server binds to (default `127.0.0.1:9567`, but configurable to `0.0.0.0:9567` for remote access).\n\nThe same pattern affects:\n- `pkg/fs/http.go` `StartHTTPServer()` — WebDAV server uses `http.ListenAndServe(addr, nil)`\n- `pkg/sync/cluster.go` `startManager()` — sync manager uses `http.Serve(l, nil)`\n\n**Fix commit**: `a46979cdd4082217081ee99b931ddc53d038e47a` (\"cmd: use a dedicated ServeMux to avoid exposing pprof/metrics\")\n\nThe fix creates a dedicated `http.NewServeMux()` for each HTTP server and passes it to `http.Serve(ln, mux)` instead of `nil`, isolating the application handlers from the pprof handlers registered on `DefaultServeMux`.\n\n### Vulnerable code (`cmd/mount.go`, commit `f60a90fc`):\n```go\nhttp.Handle(\"/metrics\", promhttp.HandlerFor(...))  // registers on DefaultServeMux\n// ...\nhttp.Serve(ln, nil)  // nil → uses DefaultServeMux → exposes pprof\n```\n\n### Fixed code (`cmd/mount.go`, commit `a46979cd`):\n```go\nmux := http.NewServeMux()                           // dedicated mux\nmux.Handle(\"/metrics\", promhttp.HandlerFor(...))    // registers on dedicated mux\n// ...\nhttp.Serve(ln, mux)  // uses dedicated mux → no pprof exposure\n```\n\n## Reproduction Steps\n\n1. **Reference**: `bundle/repro/reproduction_steps.sh`\n2. **What the script does**:\n   - Installs Go 1.25.0 and Redis if not already available\n   - Clones the JuiceFS repository (or reuses the project cache)\n   - Builds the vulnerable binary at commit `f60a90fc` (parent of the fix)\n   - Builds the fixed binary at commit `a46979cd`\n   - Starts Redis with password `s3cr3tPass` to simulate a credential-bearing metadata engine\n   - Formats a JuiceFS volume using `redis://:s3cr3tPass@127.0.0.1:6379/1` as the metadata URL\n   - Starts the JuiceFS S3 gateway with `--metrics 0.0.0.0:9567` (remotely accessible metrics port)\n   - Sends unauthenticated HTTP GET to `/debug/pprof/cmdline` on the metrics port\n   - Verifies the response contains the Redis password (vulnerable version)\n   - Repeats with the fixed binary and verifies HTTP 404 (no pprof exposure)\n3. **Expected evidence of reproduction**:\n   - Vulnerable version: HTTP 200 for `/debug/pprof/cmdline` with response body containing `redis://:s3cr3tPass@127.0.0.1:6379/1`\n   - Fixed version: HTTP 404 for `/debug/pprof/cmdline`\n   - Both versions: HTTP 200 for `/metrics` (metrics endpoint still functional after fix)\n\n## Evidence\n\n### Log files\n- `bundle/logs/gateway-vuln.log` — vulnerable gateway startup and metrics listening log\n- `bundle/logs/gateway-fixed.log` — fixed gateway startup and metrics listening log\n- `bundle/logs/redis.log` — Redis server log\n- `bundle/logs/format.log` — JuiceFS volume format log\n\n### Response artifacts\n- `bundle/repro/artifacts/vuln-cmdline-response.txt` — raw response from vulnerable `/debug/pprof/cmdline`\n- `bundle/repro/artifacts/fixed-cmdline-response.txt` — raw response from fixed `/debug/pprof/cmdline`\n- `bundle/repro/artifacts/vuln-metrics-response.txt` — Prometheus metrics from vulnerable version\n\n### Key excerpts\n\n**Vulnerable `/debug/pprof/cmdline` response (HTTP 200):**\n```\n/data/pruva/project-cache/.../juicefs-vuln\ngateway\nredis://:s3cr3tPass@127.0.0.1:6379/1\nlocalhost:9000\n--metrics\n0.0.0.0:9567\n--no-banner\n```\n\n**Fixed `/debug/pprof/cmdline` response (HTTP 404):**\n```\n404 page not found\n```\n\n**Vulnerable pprof endpoints (all HTTP 200 without auth):**\n```\n/debug/pprof/: HTTP 200\n/debug/pprof/heap: HTTP 200\n/debug/pprof/goroutine: HTTP 200\n/debug/pprof/profile: HTTP 200\n/metrics: HTTP 200\n```\n\n**Fixed pprof endpoints (all HTTP 404):**\n```\n/debug/pprof/: HTTP 404\n/debug/pprof/heap: HTTP 404\n/debug/pprof/goroutine: HTTP 404\n/debug/pprof/profile: HTTP 404\n/metrics: HTTP 200\n```\n\n### Environment details\n- Go version: go1.25.0 linux/amd64\n- Redis version: 8.0.5\n- JuiceFS vulnerable commit: `f60a90fc0ad52d2bb1f44f38a04d55044fc91d50`\n- JuiceFS fixed commit: `a46979cdd4082217081ee99b931ddc53d038e47a`\n- Architecture: x86_64\n\n## Recommendations / Next Steps\n\n1. **Upgrade**: Update to a JuiceFS version containing commit `a46979cd` or later\n2. **Network restriction**: Bind the metrics port to localhost (`127.0.0.1:9567`) rather than `0.0.0.0:9567` unless remote monitoring is explicitly required\n3. **Debug agent**: Use the `--no-agent` flag to disable the debug agent on port 6060, which still uses `http.ListenAndServe(debugAgent, nil)` with `DefaultServeMux` even after the fix (though it binds to localhost only)\n4. **Firewall**: Restrict network access to the metrics and debug ports using firewall rules\n5. **Credential rotation**: If credentials were exposed via this vulnerability, rotate all metadata engine passwords immediately\n\n## Additional Notes\n\n- **Idempotency**: The script was run twice consecutively, both times confirming the vulnerability with identical results (exit code 0)\n- **Scope**: The fix addresses `exposeMetrics` (mount/gateway/sync/mdtest), `StartHTTPServer` (WebDAV), and `startManager` (sync cluster). The debug agent in `cmd/main.go` line 336 (`http.ListenAndServe(debugAgent, nil)`) remains unchanged but is bound to `127.0.0.1` only, limiting it to local access\n- **Negative control**: The fixed version binary was built from the exact fix commit and demonstrates that pprof endpoints return 404 while `/metrics` continues to function, proving the fix is surgical and does not break metrics collection\n","cve_id":"CVE-2026-59092","cwe_id":"CWE-489 Active Debug Code","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59092","package":{"name":"JuiceFS (juicedata/juicefs)","ecosystem":"go","affected_versions":"JuiceFS <= 1.3.1","fixed_version":"commit a46979cdd4082217081ee99b931ddc53d038e47a"},"reproduced_at":"2026-07-03T15:53:57.609649+00:00","duration_secs":886.0,"tool_calls":158,"handoffs":2,"total_cost_usd":1.91877709,"agent_costs":{"hypothesis_generator":0.0113656,"judge":0.01422675,"repro":0.81922758,"support":0.044896620000000005,"vuln_variant":1.02906054},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.0113656},"judge":{"gpt-5.4-mini":0.01422675},"repro":{"accounts/fireworks/routers/glm-5p2-fast":0.81922758},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.044896620000000005},"vuln_variant":{"accounts/fireworks/routers/glm-5p2-fast":1.02906054}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-03T15:53:58.364047+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":15752,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8382,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":20021,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":11928,"category":"analysis"},{"path":"bundle/ticket.md","filename":"ticket.md","size":2513,"category":"ticket"},{"path":"bundle/ticket.json","filename":"ticket.json","size":3603,"category":"other"},{"path":"bundle/AGENTS.repro.md","filename":"AGENTS.repro.md","size":192,"category":"documentation"},{"path":"bundle/logs/cmdline-vuln-response.txt","filename":"cmdline-vuln-response.txt","size":170,"category":"other"},{"path":"bundle/logs/redis.log","filename":"redis.log","size":2944,"category":"log"},{"path":"bundle/logs/format.log","filename":"format.log","size":1135,"category":"log"},{"path":"bundle/logs/gateway-vuln.log","filename":"gateway-vuln.log","size":1686,"category":"log"},{"path":"bundle/logs/gateway-fixed.log","filename":"gateway-fixed.log","size":1686,"category":"log"},{"path":"bundle/repro/artifacts/vuln-cmdline-response.txt","filename":"vuln-cmdline-response.txt","size":170,"category":"other"},{"path":"bundle/repro/artifacts/vuln-metrics-response.txt","filename":"vuln-metrics-response.txt","size":77115,"category":"other"},{"path":"bundle/repro/artifacts/fixed-cmdline-response.txt","filename":"fixed-cmdline-response.txt","size":19,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":710,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":748,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_version.txt","filename":"fixed_version.txt","size":346,"category":"other"},{"path":"bundle/logs/vuln_variant/redis.log","filename":"redis.log","size":10301,"category":"log"},{"path":"bundle/logs/vuln_variant/format.log","filename":"format.log","size":785,"category":"log"},{"path":"bundle/logs/vuln_variant/gateway-vuln.log","filename":"gateway-vuln.log","size":1689,"category":"log"},{"path":"bundle/logs/vuln_variant/vuln-debugagent-cmdline.txt","filename":"vuln-debugagent-cmdline.txt","size":170,"category":"other"},{"path":"bundle/logs/vuln_variant/vuln-debugagent-cmdline-pretty.txt","filename":"vuln-debugagent-cmdline-pretty.txt","size":239,"category":"other"},{"path":"bundle/logs/vuln_variant/gateway-fixed.log","filename":"gateway-fixed.log","size":1689,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed-debugagent-cmdline.txt","filename":"fixed-debugagent-cmdline.txt","size":171,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed-debugagent-cmdline-pretty.txt","filename":"fixed-debugagent-cmdline-pretty.txt","size":240,"category":"other"},{"path":"bundle/logs/vuln_variant/gateway-fixed-noagent.log","filename":"gateway-fixed-noagent.log","size":1690,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-1.log","filename":"variant-run-1.log","size":4174,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-2.log","filename":"variant-run-2.log","size":4171,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-3.log","filename":"variant-run-3.log","size":871,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-4.log","filename":"variant-run-4.log","size":4171,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-5.log","filename":"variant-run-5.log","size":4171,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-6.log","filename":"variant-run-6.log","size":4171,"category":"log"},{"path":"bundle/logs/vuln_variant/variant-run-7.log","filename":"variant-run-7.log","size":4171,"category":"log"},{"path":"bundle/vuln_variant/variant_runtime_result.json","filename":"variant_runtime_result.json","size":457,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7695,"category":"documentation"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":4221,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":2360,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":1511,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":2499,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":2349,"category":"other"}]}