GitHub Advisoryadvisory · Go
GHSA-6vgg-xhvh-38ff nebula-mesh: POST /api/v1/hosts/{id}/mobile-bundle response lacks Cache-Control: no-store
LOW
Official page ↗
- Ecosystem
- Go
- Packages
- github.com/juev/nebula-mesh
- Severity
- Low
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-525
- Reviewed
- 2026-06-12
Affected packages
• Go github.com/juev/nebula-mesh: < 0.3.2 → fixed in 0.3.2
Description
`internal/api/mobile_bundle.go:62-66` sets only `Content-Type: application/yaml`. The Web-UI sibling at `internal/web/handlers.go:1316-1321` sets `Cache-Control: no-store`, `Pragma: no-cache`, `Expires: 0`, `X-Content-Type-Options: nosniff` — and has a test asserting it. The API path was missed.
Affected
All released versions up to v0.3.0.
Threat model
The endpoint returns a freshly minted X25519 private key inline. Without `no-store`, any intermediary proxy or CDN that caches `200 OK` YAML responses retains the private key for its cache TTL. Same applies to browser disk cache for direct API hits. Combined with the cross-tenant authz advisory (critical), even a corrected authz layer would still leak via cache after fix.
Suggested fix
Copy the four headers from the Web sibling:
```go
w.Header().Set("Content-Type", "application/yaml; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("X-Content-Type-Options", "nosniff")
```
Mirrors `internal/web/handlers.go:1316-1321`. Add a parallel test to the existing web-side coverage.
Suggested patch
Verified locally: `go vet`, `go test -race -count=1 ./...`, `golangci-lint v2.12` all clean.
```diff
diff --git a/internal/api/mobile_bundle.go b/internal/api/mobile_bundle.go
index fc09da0..73152eb 100644
--- a/internal/api/mobile_bundle.go
+++ b/internal/api/mobile_bundle.go
@@ -58,8 +58,15 @@ func (s *Server) handleMobileBundle(w http.ResponseWriter, r *http.Request) {
return
}
- // Return YAML bundle with proper content-type
+ // Return YAML bundle with proper content-type. The bundle inlines a
+ // freshly-minted X25519 private key, so suppress every layer of cache
+ // between server and operator (intermediate proxies/CDNs, browser disk
+ // cache). Mirrors the Web-UI sibling at internal/web/handlers.go.
w.Header().Set("Content-Type", "application/yaml; charset=utf-8")
+ w.Header().Set("Cache-Control", "no-store")
+ w.Header().Set("Pragma", "no-cache")
+ w.Header().Set("Expires", "0")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(bundle); err != nil {
s.logger.Error("write mobile bundle response", "error", err)
diff --git a/internal/api/mobile_bundle_test.go b/internal/api/mobile_bundle_test.go
index dcb8cd9..da08b01 100644
--- a/internal/api/mobile_bundle_test.go
+++ b/internal/api/mobile_bundle_test.go
@@ -52,6 +52,19 @@ func TestHandleMobileBundle_Success(t *testing.T) {
t.Errorf("Content-Type = %q, want 'application/yaml; charset=utf-8'", ct)
}
+ // Bundle inlines a private key — every cache between server and operator
+ // must drop the response. Mirrors the Web-UI sibling's headers.
+ for header, want := range map[string]string{
+ "Cache-Control": "no-store",
+ "Pragma": "no-cache",
+ "Expires": "0",
+ "X-Content-Type-Options": "nosniff",
+ } {
+ if got := w.Header().Get(header); got != want {
+ t.Errorf("%s = %q, want %q", header, got, want)
+ }
+ }
+
// Verify body is valid YAML with expected keys
var yamlData map[string]interface{}
if err := yaml.Unmarshal(w.Body.Bytes(), &yamlData); err != nil {
```
Related
Weakness · CWE (1)
- CWE-525 Use of Web Browser Cache Containing Sensitive Information weakness