VYPR
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.

PackageAffected versionsPatched versions
github.com/gravitl/netmakerGo
< 1.5.01.5.0

Affected products

1

Patches

1
5309aa70d464

NM-258: fix host authrize func, check for token validity

https://github.com/gravitl/netmakerabhishek9686Feb 23, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.