CVE-2026-42300
Description
DevGuard provides vulnerability management for the full software supply chain. Prior to 1.2.2, the SessionMiddleware accepts a client-supplied X-Admin-Token HTTP request header and uses its raw string value as the authenticated userID when no Kratos session cookie is present. An unauthenticated attacker who knows or can guess a target user's Kratos identity UUID can issue requests as that user. Where the target user is an organisation admin or owner, this gives the attacker full control over that organisation's DevGuard resources. This vulnerability is fixed in 1.2.2.
Affected products
1- Range: < 1.2.2
Patches
16f38310bf93bremoves admin token implementation
6 files changed · +12 −109
accesscontrol/external_entity_provider_rbac.go+1 −19 modified@@ -10,19 +10,17 @@ import ( type externalEntityProviderRBAC struct { thirdPartyIntegration shared.IntegrationAggregate externalEntityProviderID string - adminToken *string ctx shared.Context rootAccessControl shared.AccessControl } var _ shared.AccessControl = (*externalEntityProviderRBAC)(nil) -func NewExternalEntityProviderRBAC(ctx shared.Context, rootAccessControl shared.AccessControl, thirdPartyIntegration shared.IntegrationAggregate, externalEntityProviderID string, adminToken *string) *externalEntityProviderRBAC { +func NewExternalEntityProviderRBAC(ctx shared.Context, rootAccessControl shared.AccessControl, thirdPartyIntegration shared.IntegrationAggregate, externalEntityProviderID string) *externalEntityProviderRBAC { return &externalEntityProviderRBAC{ thirdPartyIntegration: thirdPartyIntegration, externalEntityProviderID: externalEntityProviderID, - adminToken: adminToken, ctx: ctx, rootAccessControl: rootAccessControl, } @@ -41,9 +39,6 @@ func (e *externalEntityProviderRBAC) RevokeAllRolesInAssetForUser(ctx context.Co } func (e *externalEntityProviderRBAC) HasAccess(ctx context.Context, userID string) (bool, error) { - if e.adminToken != nil && userID == *e.adminToken { - return true, nil - } return e.thirdPartyIntegration.HasAccessToExternalEntityProvider(e.ctx, e.externalEntityProviderID) } @@ -104,13 +99,6 @@ func (e *externalEntityProviderRBAC) AllowRole(ctx context.Context, role shared. } func (e *externalEntityProviderRBAC) IsAllowed(ctx context.Context, userID string, object shared.Object, action shared.Action) (bool, error) { - if e.adminToken != nil && userID == *e.adminToken { - if action == shared.ActionRead { - return true, nil - } - return false, nil - } - // ALLOW ORG read access for all users - this is pretty much the same as HasAccess. if object == shared.ObjectOrganization && action == shared.ActionRead { return true, nil @@ -123,9 +111,6 @@ func (e *externalEntityProviderRBAC) IsAllowedInProject(ctx context.Context, pro if project.ExternalEntityProviderID == nil || project.ExternalEntityID == nil { return false, nil } - if e.adminToken != nil && user == *e.adminToken && action == shared.ActionRead { - return true, nil - } return e.rootAccessControl.IsAllowedInProject(ctx, project, user, object, action) } @@ -173,9 +158,6 @@ func (e *externalEntityProviderRBAC) IsAllowedInAsset(ctx context.Context, asset if asset.ExternalEntityProviderID == nil || asset.ExternalEntityID == nil { return false, nil } - if e.adminToken != nil && user == *e.adminToken && action == shared.ActionRead { - return true, nil - } return e.rootAccessControl.IsAllowedInAsset(ctx, asset, user, object, action) }
accesscontrol/external_entity_provider_rbac_test.go+6 −36 modified@@ -7,19 +7,16 @@ import ( "github.com/l3montree-dev/devguard/mocks" "github.com/l3montree-dev/devguard/shared" - "github.com/l3montree-dev/devguard/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestIsAllowed(t *testing.T) { - type testCase struct { name string userID string object shared.Object action shared.Action - adminToken *string mockResult bool mockErr error expectedResult bool @@ -32,41 +29,36 @@ func TestIsAllowed(t *testing.T) { userID: "admin-token", object: shared.ObjectProject, action: shared.ActionRead, - adminToken: utils.Ptr("admin-token"), expectedResult: true, }, { name: "all users can read organization", userID: "user1", object: shared.ObjectOrganization, action: shared.ActionRead, - adminToken: utils.Ptr("admin-token"), expectedResult: true, }, { - name: "error from rootAccessControl", - userID: "user5", - object: shared.ObjectProject, - action: shared.ActionRead, - adminToken: utils.Ptr("admin-token"), - mockErr: errors.New("some error"), - expectErr: true, + name: "error from rootAccessControl", + userID: "user5", + object: shared.ObjectProject, + action: shared.ActionRead, + mockErr: errors.New("some error"), + expectErr: true, }, { name: "admin token can not create", userID: "admin-token", object: shared.ObjectProject, action: shared.ActionCreate, - adminToken: utils.Ptr("admin-token"), expectedResult: false, }, { name: "admin token cannot delete", userID: "admin-token", object: shared.ObjectProject, action: shared.ActionDelete, - adminToken: utils.Ptr("admin-token"), expectedResult: false, }, } @@ -87,7 +79,6 @@ func TestIsAllowed(t *testing.T) { rootAccessControl, thirdpartyIntegrationMock, "external-entity-provider-id", - tc.adminToken, ) result, err := rbac.IsAllowed(context.Background(), tc.userID, tc.object, tc.action) @@ -112,7 +103,6 @@ func TestHasAccess(t *testing.T) { rootAccessControl, thirdpartyIntegrationMock, "external-entity-provider-id", - utils.Ptr("admin-token"), ) hasAccess, err := rbac.HasAccess(context.Background(), "admin-token") @@ -131,30 +121,10 @@ func TestHasAccess(t *testing.T) { rootAccessControl, thirdpartyIntegrationMock, "external-entity-provider-id", - nil, ) hasAccess, err := rbac.HasAccess(context.Background(), "user1") assert.NoError(t, err) assert.True(t, hasAccess) }) - - t.Run("if no admin token is provided, the third party integration should be called (false)", func(t *testing.T) { - ctx := mocks.NewContext(t) - rootAccessControl := mocks.NewAccessControl(t) - thirdpartyIntegrationMock := mocks.NewIntegrationAggregate(t) - thirdpartyIntegrationMock.On("HasAccessToExternalEntityProvider", ctx, "external-entity-provider-id").Return(false, nil) - - rbac := NewExternalEntityProviderRBAC( - ctx, - rootAccessControl, - thirdpartyIntegrationMock, - "external-entity-provider-id", - nil, - ) - - hasAccess, err := rbac.HasAccess(context.Background(), "user1") - assert.NoError(t, err) - assert.False(t, hasAccess) - }) }
integrations/gitlabint/gitlab_oauth2.go+4 −18 modified@@ -31,7 +31,6 @@ type GitlabOauth2Config struct { DevGuardBotUserID int // the user id of the devguard bot user, used to create issues DevGuardBotUserAccessToken string // the access token of the devguard bot user, used to create issues - AdminToken *string } func (c *GitlabOauth2Config) GetProviderID() string { @@ -47,9 +46,8 @@ type gitlabEnvConfig struct { appID string appSecret string scopes string - botUserID int // the user id of the devguard bot user, used to create issues - botUserAccessToken string // the access token of the devguard bot user, used to create issues - adminToken *string // the admin token for the gitlab instance, used to create issues + botUserID int // the user id of the devguard bot user, used to create issues + botUserAccessToken string // the access token of the devguard bot user, used to create issues } type gitlabOauth2Client struct { @@ -101,12 +99,6 @@ func parseGitlabEnvs() map[string]gitlabEnvConfig { conf.botUserID = intValue case "botuseraccesstoken": conf.botUserAccessToken = value - case "admintoken": - if value == "" { - conf.adminToken = nil - } else { - conf.adminToken = &value - } } urls[name] = conf @@ -134,17 +126,12 @@ func parseGitlabEnvs() map[string]gitlabEnvConfig { if conf.botUserAccessToken == "" { slog.Warn(fmt.Sprintf("GITLAB_%s_BOTUSERACCESSTOKEN is not set", strings.ToUpper(name))) } - if conf.adminToken == nil { - slog.Warn(fmt.Sprintf("GITLAB_%s_ADMINTOKEN is not set", strings.ToUpper(name))) - } else { - slog.Info(fmt.Sprintf("GITLAB_%s_ADMINTOKEN is set", strings.ToUpper(name))) - } } return urls } -func NewGitLabOauth2Config(id, gitlabBaseURL, gitlabOauth2ClientID, gitlabOauth2ClientSecret, gitlabOauth2Scopes string, botUserID int, botUserAccessToken string, adminToken *string, gitlabOauth2TokenRepository shared.GitLabOauth2TokenRepository) *GitlabOauth2Config { +func NewGitLabOauth2Config(id, gitlabBaseURL, gitlabOauth2ClientID, gitlabOauth2ClientSecret, gitlabOauth2Scopes string, botUserID int, botUserAccessToken string, gitlabOauth2TokenRepository shared.GitLabOauth2TokenRepository) *GitlabOauth2Config { frontendURL := os.Getenv("FRONTEND_URL") if frontendURL == "" { @@ -161,7 +148,6 @@ func NewGitLabOauth2Config(id, gitlabBaseURL, gitlabOauth2ClientID, gitlabOauth2 ProviderID: id, DevGuardBotUserID: botUserID, DevGuardBotUserAccessToken: botUserAccessToken, - AdminToken: adminToken, Oauth2Conf: &oauth2.Config{ ClientID: gitlabOauth2ClientID, ClientSecret: gitlabOauth2ClientSecret, @@ -244,7 +230,7 @@ func NewGitLabOauth2Integrations(db shared.DB, gitlabOauth2TokenRepository share envs := parseGitlabEnvs() gitlabIntegrations := make(map[string]*GitlabOauth2Config) for id, env := range envs { - gitlabIntegration := NewGitLabOauth2Config(id, env.baseURL, env.appID, env.appSecret, env.scopes, env.botUserID, env.botUserAccessToken, env.adminToken, gitlabOauth2TokenRepository) + gitlabIntegration := NewGitLabOauth2Config(id, env.baseURL, env.appID, env.appSecret, env.scopes, env.botUserID, env.botUserAccessToken, gitlabOauth2TokenRepository) gitlabIntegrations[id] = gitlabIntegration slog.Info("gitlab oauth2 integration created", "id", id, "baseURL", env.baseURL, "appID", env.appID) }
middlewares/access_control_middlewares.go+1 −7 modified@@ -204,13 +204,7 @@ func MultiOrganizationMiddlewareRBAC(rbacProvider shared.RBACProvider, organizat domainRBAC := rbacProvider.GetDomainRBAC(org.ID.String()) if org.IsExternalEntity() { // check if there is an admin token defined - conf, ok := oauth2Config[*org.ExternalEntityProviderID] - if !ok { - slog.Error("no oauth2 config found for external entity provider", "provider", *org.ExternalEntityProviderID) - return ctx.JSON(500, map[string]string{"error": "no oauth2 config found for external entity provider"}) - } - - domainRBAC = accesscontrol.NewExternalEntityProviderRBAC(ctx, rbacProvider.GetDomainRBAC(org.ID.String()), shared.GetThirdPartyIntegration(ctx), *org.ExternalEntityProviderID, conf.AdminToken) + domainRBAC = accesscontrol.NewExternalEntityProviderRBAC(ctx, rbacProvider.GetDomainRBAC(org.ID.String()), shared.GetThirdPartyIntegration(ctx), *org.ExternalEntityProviderID) } // check if the user is allowed to access the organization
middlewares/session_middleware.go+0 −6 modified@@ -61,8 +61,6 @@ func SessionMiddleware(oryAPIClient shared.PublicClient, verifier shared.Verifie var scopes string var err error - adminTokenHeader := ctx.Request().Header.Get("X-Admin-Token") - if oryKratosSessionCookie != nil { userID, err = cookieAuth(ctx.Request().Context(), oryAPIClient, oryKratosSessionCookie.String()) if err != nil { @@ -77,10 +75,6 @@ func SessionMiddleware(oryAPIClient shared.PublicClient, verifier shared.Verifie scopesArray := strings.Fields(scopes) ctx.Set("session", accesscontrol.NewSession(userID, scopesArray)) return next(ctx) - } else if adminTokenHeader != "" { - slog.Warn("admin token header is set, using it to create session") - ctx.Set("session", accesscontrol.NewSession(adminTokenHeader, []string{})) - return next(ctx) } else { userID, scopes, err = verifier.VerifyRequestSignature(ctx.Request().Context(), ctx.Request()) if err != nil {
middlewares/session_middleware_test.go+0 −23 modified@@ -122,27 +122,4 @@ func TestSessionMiddleware(t *testing.T) { _ = handler(c) assert.True(t, called) }) - - t.Run("should set the session using admin token header", func(t *testing.T) { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set("X-Admin-Token", "admin_token_value") - rec := httptest.NewRecorder() - c := e.NewContext(req, rec) - - mw := SessionMiddleware(nil, nil) - - var called bool - handler := mw(func(ctx echo.Context) error { - called = true - sess := shared.GetSession(ctx) - - assert.Equal(t, "admin_token_value", sess.GetUserID()) - assert.ElementsMatch(t, []string{}, sess.GetScopes()) - return nil - }) - - _ = handler(c) - assert.True(t, called) - }) }
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
4News mentions
0No linked articles in our index yet.