Devtron Attributes API Unauthorized Access Leading to API Token Signing Key Leakage
Description
Devtron is an open source tool integration platform for Kubernetes. In version 2.0.0 and prior, a vulnerability exists in Devtron's Attributes API interface, allowing any authenticated user (including low-privileged CI/CD Developers) to obtain the global API Token signing key by accessing the /orchestrator/attributes?key=apiTokenSecret endpoint. After obtaining the key, attackers can forge JWT tokens for arbitrary user identities offline, thereby gaining complete control over the Devtron platform and laterally moving to the underlying Kubernetes cluster. This issue has been patched via commit d2b0d26.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Devtron's Attributes API exposes the global JWT signing key to any authenticated user, enabling full platform compromise and Kubernetes cluster takeover.
Vulnerability
The vulnerability resides in Devtron's Attributes API, specifically the GetAttributesByKey handler (CVE-2026-25538). The RBAC authorization check was commented out, allowing any authenticated user—including low-privileged CI/CD Developers—to retrieve the global apiTokenSecret by requesting the endpoint /orchestrator/attributes?key=apiTokenSecret [1][3].
Exploitation
An attacker can simply authenticate and issue a GET request to the above endpoint to obtain the HMAC-SHA256 signing key used for all API tokens. With the key in hand, the attacker can forge JWT tokens offline, impersonating any user identity without further authentication [1].
Impact
Successful exploitation grants the attacker full administrative control over the Devtron platform. Because Devtron manages Kubernetes clusters, this access can be leveraged for lateral movement, potentially compromising the entire underlying Kubernetes infrastructure [1][3].
Mitigation
The issue has been patched via commit d2b0d26, which re-adds authorization checks and blocks access to internal-only attributes [4]. Users are strongly advised to update to a version containing this fix [1].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/devtron-labs/devtronGo | <= 2.0.0 | — |
Affected products
2- devtron-labs/devtronv5Range: <= 2.0.0
Patches
1d2b0d260d858fix: prevent exposure of internal-only attributes in API responses and requests
2 files changed · +67 −6
api/restHandler/AttributesRestHandlder.go+62 −6 modified@@ -19,14 +19,16 @@ package restHandler import ( "encoding/json" "errors" - "github.com/devtron-labs/devtron/pkg/attributes/bean" + "fmt" "net/http" "strconv" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/pkg/attributes" + "github.com/devtron-labs/devtron/pkg/attributes/bean" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" "github.com/devtron-labs/devtron/pkg/auth/user" + "github.com/devtron-labs/devtron/util/sliceUtil" "github.com/gorilla/mux" "go.uber.org/zap" ) @@ -57,6 +59,20 @@ func NewAttributesRestHandlerImpl(logger *zap.SugaredLogger, enforcer casbin.Enf } return userAuthHandler } + +// isInternalOnlyKey checks if the given key is internal-only and should not be exposed +func (handler AttributesRestHandlerImpl) isInternalOnlyKey(key string) bool { + return bean.InternalOnlyKeys[key] +} + +// filterInternalAttributes removes internal-only attributes from the list +func (handler AttributesRestHandlerImpl) filterInternalAttributes(attributes []*bean.AttributesDto) []*bean.AttributesDto { + filtered := make([]*bean.AttributesDto, 0, len(attributes)) + return sliceUtil.Filter(filtered, attributes, func(attr *bean.AttributesDto) bool { + return !handler.isInternalOnlyKey(attr.Key) + }) +} + func (handler AttributesRestHandlerImpl) AddAttributes(w http.ResponseWriter, r *http.Request) { userId, err := handler.userService.GetLoggedInUser(r) if userId == 0 || err != nil { @@ -78,6 +94,13 @@ func (handler AttributesRestHandlerImpl) AddAttributes(w http.ResponseWriter, r return } + // Check if the key is internal-only (not allowed to be created via API) + if handler.isInternalOnlyKey(dto.Key) { + handler.logger.Warnw("attempt to create internal-only attribute", "key", dto.Key, "userId", userId) + common.WriteJsonResp(w, fmt.Errorf("forbidden: cannot create attribute with key: %q", dto.Key), nil, http.StatusForbidden) + return + } + handler.logger.Infow("request payload, AddAttributes", "payload", dto) resp, err := handler.attributesService.AddAttributes(&dto) if err != nil { @@ -105,6 +128,14 @@ func (handler AttributesRestHandlerImpl) UpdateAttributes(w http.ResponseWriter, } token := r.Header.Get("token") + + // Check if the key is internal-only (not allowed to be created via API) + if handler.isInternalOnlyKey(dto.Key) { + handler.logger.Warnw("attempt to create internal-only attribute", "key", dto.Key, "userId", userId) + common.WriteJsonResp(w, fmt.Errorf("forbidden: cannot edit attribute with key: %q", dto.Key), nil, http.StatusForbidden) + return + } + if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return @@ -145,6 +176,14 @@ func (handler AttributesRestHandlerImpl) GetAttributesById(w http.ResponseWriter common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return } + + // Filter out internal-only attributes + if res != nil && handler.isInternalOnlyKey(res.Key) { + handler.logger.Warnw("attempt to read internal-only attribute", "key", res.Key, "userId", userId) + common.WriteJsonResp(w, fmt.Errorf("forbidden: cannot read attribute with key: %q", res.Key), nil, http.StatusForbidden) + return + } + common.WriteJsonResp(w, nil, res, http.StatusOK) } @@ -167,7 +206,10 @@ func (handler AttributesRestHandlerImpl) GetAttributesActiveList(w http.Response common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return } - common.WriteJsonResp(w, nil, res, http.StatusOK) + + // Filter out internal-only attributes from the list + filteredRes := handler.filterInternalAttributes(res) + common.WriteJsonResp(w, nil, filteredRes, http.StatusOK) } func (handler AttributesRestHandlerImpl) GetAttributesByKey(w http.ResponseWriter, r *http.Request) { @@ -185,9 +227,17 @@ func (handler AttributesRestHandlerImpl) GetAttributesByKey(w http.ResponseWrite vars := mux.Vars(r) key := vars["key"] + + // Check if the key is internal-only (not allowed to be read via API) + if handler.isInternalOnlyKey(key) { + handler.logger.Warnw("attempt to read internal-only attribute by key", "key", key, "userId", userId) + common.WriteJsonResp(w, fmt.Errorf("forbidden: cannot read attribute with key: %q", key), nil, http.StatusForbidden) + return + } + res, err := handler.attributesService.GetByKey(key) if err != nil { - handler.logger.Errorw("service err, GetAttributesById", "err", err) + handler.logger.Errorw("service err, GetAttributesByKey", "key", key, "err", err) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return } @@ -204,21 +254,27 @@ func (handler AttributesRestHandlerImpl) AddDeploymentEnforcementConfig(w http.R var dto bean.AttributesDto err = decoder.Decode(&dto) if err != nil { - handler.logger.Errorw("request err, AddAttributes", "err", err, "payload", dto) + handler.logger.Errorw("request err, AddDeploymentEnforcementConfig", "err", err, "payload", dto) common.WriteJsonResp(w, err, nil, http.StatusBadRequest) return } + // Check if the key is enforce deployment type config + if dto.Key != bean.ENFORCE_DEPLOYMENT_TYPE_CONFIG { + common.WriteJsonResp(w, fmt.Errorf("invalid key: %q", dto.Key), nil, http.StatusBadRequest) + return + } + token := r.Header.Get("token") if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*"); !ok { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } - handler.logger.Infow("request payload, AddAttributes", "payload", dto) + handler.logger.Infow("request payload, AddDeploymentEnforcementConfig", "payload", dto) resp, err := handler.attributesService.AddDeploymentEnforcementConfig(&dto) if err != nil { - handler.logger.Errorw("service err, AddAttributes", "err", err, "payload", dto) + handler.logger.Errorw("service err, AddDeploymentEnforcementConfig", "err", err, "payload", dto) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return }
pkg/attributes/bean/bean.go+5 −0 modified@@ -24,6 +24,11 @@ const ( UserPreferencesResourcesKey = "resources" ) +// InternalOnlyKeys are the internal attribute keys - cannot be read or written via API +var InternalOnlyKeys = map[string]bool{ + API_SECRET_KEY: true, +} + type AttributesDto struct { Id int `json:"id"` Key string `json:"key,omitempty"`
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-8wpc-j9q9-j5m2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-25538ghsaADVISORY
- github.com/devtron-labs/devtron/commit/d2b0d260d858ab1354b73a8f50f7f078ca62706fghsax_refsource_MISCWEB
- github.com/devtron-labs/devtron/security/advisories/GHSA-8wpc-j9q9-j5m2ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.