High severity8.2NVD Advisory· Published Apr 28, 2026· Updated Apr 28, 2026
CVE-2026-38651
CVE-2026-38651
Description
Authentication Bypass vulnerability exists in Netmaker versions prior to 1.5.0. The VerifyHostToken function in logic/jwts.go fails to validate the JWT signature when verifying host tokens. An attacker can forge a JWT signed with any arbitrary key and use it to impersonate any host in the network, gaining access to sensitive information
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/gravitl/netmakerGo | < 1.5.0 | 1.5.0 |
Affected products
1Patches
15309aa70d464NM-258: fix host authrize func, check for token validity
5 files changed · +33 −117
controllers/hosts.go+5 −5 modified@@ -36,7 +36,7 @@ func hostHandlers(r *mux.Router) { Methods(http.MethodPost) r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))). Methods(http.MethodPut) - r.HandleFunc("/api/hosts/{hostid}", Authorize(true, false, "all", http.HandlerFunc(deleteHost))). + r.HandleFunc("/api/hosts/{hostid}", AuthorizeHost(http.HandlerFunc(deleteHost))). Methods(http.MethodDelete) r.HandleFunc("/api/hosts/{hostid}/upgrade", logic.SecurityCheck(true, http.HandlerFunc(upgradeHost))). Methods(http.MethodPut) @@ -45,13 +45,13 @@ func hostHandlers(r *mux.Router) { r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(deleteHostFromNetwork))). Methods(http.MethodDelete) r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost) - r.HandleFunc("/api/v1/host", Authorize(true, false, "host", http.HandlerFunc(pull))). + r.HandleFunc("/api/v1/host", AuthorizeHost(http.HandlerFunc(pull))). Methods(http.MethodGet) - r.HandleFunc("/api/v1/host/{hostid}/signalpeer", Authorize(true, false, "host", http.HandlerFunc(signalPeer))). + r.HandleFunc("/api/v1/host/{hostid}/signalpeer", AuthorizeHost(http.HandlerFunc(signalPeer))). Methods(http.MethodPost) - r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))). + r.HandleFunc("/api/v1/fallback/host/{hostid}", AuthorizeHost(http.HandlerFunc(hostUpdateFallback))). Methods(http.MethodPut) - r.HandleFunc("/api/v1/host/{hostid}/peer_info", Authorize(true, false, "host", http.HandlerFunc(getHostPeerInfo))). + r.HandleFunc("/api/v1/host/{hostid}/peer_info", AuthorizeHost(http.HandlerFunc(getHostPeerInfo))). Methods(http.MethodGet) r.HandleFunc("/api/v1/pending_hosts", logic.SecurityCheck(true, http.HandlerFunc(getPendingHosts))). Methods(http.MethodGet)
controllers/node.go+19 −103 modified@@ -24,9 +24,9 @@ func nodeHandlers(r *mux.Router) { r.HandleFunc("/api/nodes", logic.SecurityCheck(true, http.HandlerFunc(getAllNodes))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet) - r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet) + r.HandleFunc("/api/nodes/{network}/{nodeid}", AuthorizeHost(http.HandlerFunc(getNode))).Methods(http.MethodGet) r.HandleFunc("/api/nodes/{network}/{nodeid}", logic.SecurityCheck(true, http.HandlerFunc(updateNode))).Methods(http.MethodPut) - r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) + r.HandleFunc("/api/nodes/{network}/{nodeid}", AuthorizeHost(http.HandlerFunc(deleteNode))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost) r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", logic.SecurityCheck(true, http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete) r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost) @@ -140,116 +140,32 @@ func authenticate(response http.ResponseWriter, request *http.Request) { // even if it's technically ok // This is kind of a poor man's RBAC. There's probably a better/smarter way. // TODO: Consider better RBAC implementations -func Authorize( - hostAllowed, networkCheck bool, - authNetwork string, +func AuthorizeHost( next http.Handler, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var errorResponse = models.ErrorResponse{ - Code: http.StatusForbidden, Message: logic.Forbidden_Msg, - } - - var params = mux.Vars(r) - - networkexists, _ := logic.NetworkExists(params["network"]) - //check that the request is for a valid network - //if (networkCheck && !networkexists) || err != nil { - if networkCheck && !networkexists { - logic.ReturnErrorResponse(w, r, errorResponse) - return - } else { - w.Header().Set("Content-Type", "application/json") - //get the auth token - bearerToken := r.Header.Get("Authorization") + w.Header().Set("Content-Type", "application/json") - var tokenSplit = strings.Split(bearerToken, " ") + //get the auth token + bearerToken := r.Header.Get("Authorization") - //I put this in in case the user doesn't put in a token at all (in which case it's empty) - //There's probably a smarter way of handling this. - var authToken = "928rt238tghgwe@TY@$Y@#WQAEGB2FC#@HG#@$Hddd" + var tokenSplit = strings.Split(bearerToken, " ") + var authToken = "" - if len(tokenSplit) > 1 { - authToken = tokenSplit[1] - } else { - logic.ReturnErrorResponse(w, r, errorResponse) - return - } - // check if host instead of user - if hostAllowed { - // TODO --- should ensure that node is only operating on itself - if hostID, _, _, err := logic.VerifyHostToken(authToken); err == nil { - r.Header.Set(hostIDHeader, hostID) - // this indicates request is from a node - // used for failover - if a getNode comes from node, this will trigger a metrics wipe - next.ServeHTTP(w, r) - return - } - } - - var isAuthorized = false - var nodeID = "" - username, issuperadmin, isadmin, errN := logic.VerifyUserToken(authToken) - if errN != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(errN, logic.Unauthorized_Msg)) - return - } + if len(tokenSplit) < 2 { + logic.ReturnErrorResponse(w, r, logic.FormatError(logic.Unauthorized_Err, logic.Unauthorized_Msg)) + return + } else { + authToken = tokenSplit[1] + } - isnetadmin := issuperadmin || isadmin - if issuperadmin || isadmin { - nodeID = "mastermac" - isAuthorized = true - r.Header.Set("ismasterkey", "yes") - } - //The mastermac (login with masterkey from config) can do everything!! May be dangerous. - if nodeID == "mastermac" { - isAuthorized = true - r.Header.Set("ismasterkey", "yes") - //for everyone else, there's poor man's RBAC. The "cases" are defined in the routes in the handlers - //So each route defines which access network should be allowed to access it - } else { - switch authNetwork { - case "all": - isAuthorized = true - case "nodes": - isAuthorized = (nodeID != "") || isnetadmin - case "network": - if isnetadmin { - isAuthorized = true - } else { - node, err := logic.GetNodeByID(nodeID) - if err != nil { - logic.ReturnErrorResponse(w, r, errorResponse) - return - } - isAuthorized = (node.Network == params["network"]) - } - case "node": - if isnetadmin { - isAuthorized = true - } else { - isAuthorized = (nodeID == params["netid"]) - } - case "host": - case "user": - isAuthorized = true - default: - isAuthorized = false - } - } - if !isAuthorized { - logic.ReturnErrorResponse(w, r, errorResponse) - return - } else { - //If authorized, this function passes along it's request and output to the appropriate route function. - if username == "" { - username = "(user not found)" - } - r.Header.Set("user", username) - next.ServeHTTP(w, r) - } + if hostID, _, _, err := logic.VerifyHostToken(authToken); err == nil { + r.Header.Set(hostIDHeader, hostID) + next.ServeHTTP(w, r) + return } + logic.ReturnErrorResponse(w, r, logic.FormatError(logic.Unauthorized_Err, logic.Unauthorized_Msg)) } }
logic/jwts.go+2 −2 modified@@ -254,14 +254,14 @@ func VerifyHostToken(tokenString string) (hostID string, mac string, network str // this may be a stupid way of serving up a master key // TODO: look into a different method. Encryption? if tokenString == servercfg.GetMasterKey() && servercfg.GetMasterKey() != "" { - return "mastermac", "", "", nil + return MasterUser, "", "", nil } token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { return jwtSecretKey, nil }) - if token != nil { + if token != nil && token.Valid { return claims.ID, claims.MacAddress, claims.Network, nil } return "", "", "", err
pro/controllers/auto_relay.go+4 −4 modified@@ -22,19 +22,19 @@ import ( // AutoRelayHandlers - handlers for AutoRelay func AutoRelayHandlers(r *mux.Router) { - r.HandleFunc("/api/v1/node/{nodeid}/auto_relay", controller.Authorize(true, false, "host", http.HandlerFunc(getAutoRelayGws))). + r.HandleFunc("/api/v1/node/{nodeid}/auto_relay", controller.AuthorizeHost(http.HandlerFunc(getAutoRelayGws))). Methods(http.MethodGet) r.HandleFunc("/api/v1/node/{nodeid}/auto_relay", logic.SecurityCheck(true, http.HandlerFunc(setAutoRelay))). Methods(http.MethodPost) r.HandleFunc("/api/v1/node/{nodeid}/auto_relay", logic.SecurityCheck(true, http.HandlerFunc(unsetAutoRelay))). Methods(http.MethodDelete) r.HandleFunc("/api/v1/node/{network}/auto_relay/reset", logic.SecurityCheck(true, http.HandlerFunc(resetAutoRelayGw))). Methods(http.MethodPost) - r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_me", controller.Authorize(true, false, "host", http.HandlerFunc(autoRelayME))). + r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_me", controller.AuthorizeHost(http.HandlerFunc(autoRelayME))). Methods(http.MethodPost) - r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_me", controller.Authorize(true, false, "host", http.HandlerFunc(autoRelayMEUpdate))). + r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_me", controller.AuthorizeHost(http.HandlerFunc(autoRelayMEUpdate))). Methods(http.MethodPut) - r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_check", controller.Authorize(true, false, "host", http.HandlerFunc(checkautoRelayCtx))). + r.HandleFunc("/api/v1/node/{nodeid}/auto_relay_check", controller.AuthorizeHost(http.HandlerFunc(checkautoRelayCtx))). Methods(http.MethodGet) }
pro/controllers/failover.go+3 −3 modified@@ -22,17 +22,17 @@ import ( // FailOverHandlers - handlers for FailOver func FailOverHandlers(r *mux.Router) { - r.HandleFunc("/api/v1/node/{nodeid}/failover", controller.Authorize(true, false, "host", http.HandlerFunc(getfailOver))). + r.HandleFunc("/api/v1/node/{nodeid}/failover", controller.AuthorizeHost(http.HandlerFunc(getfailOver))). Methods(http.MethodGet) r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(createfailOver))). Methods(http.MethodPost) r.HandleFunc("/api/v1/node/{nodeid}/failover", logic.SecurityCheck(true, http.HandlerFunc(deletefailOver))). Methods(http.MethodDelete) r.HandleFunc("/api/v1/node/{network}/failover/reset", logic.SecurityCheck(true, http.HandlerFunc(resetFailOver))). Methods(http.MethodPost) - r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.Authorize(true, false, "host", http.HandlerFunc(failOverME))). + r.HandleFunc("/api/v1/node/{nodeid}/failover_me", controller.AuthorizeHost(http.HandlerFunc(failOverME))). Methods(http.MethodPost) - r.HandleFunc("/api/v1/node/{nodeid}/failover_check", controller.Authorize(true, false, "host", http.HandlerFunc(checkfailOverCtx))). + r.HandleFunc("/api/v1/node/{nodeid}/failover_check", controller.AuthorizeHost(http.HandlerFunc(checkfailOverCtx))). Methods(http.MethodGet) }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-qpv2-rwc8-c993ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-38651ghsaADVISORY
- github.com/gravitl/netmaker/commit/5309aa70d464ef565911369714d661a61481a79bnvdWEB
- www.zyenra.com/advisories/netmaker-jwt-verification-bypassnvdWEB
- www.zyenra.com/advisories/netmaker-jwt-verification-bypass/nvd
- www.zyenra.com/blog/netmaker-jwt-verification-bypassnvd
News mentions
0No linked articles in our index yet.