VYPR
Low severity3.8NVD Advisory· Published Feb 19, 2026· Updated Apr 15, 2026

CVE-2026-2733

CVE-2026-2733

Description

A flaw was identified in the Docker v2 authentication endpoint of Keycloak, where tokens continue to be issued even after a Docker registry client has been administratively disabled. This means that turning the client “Enabled” setting to OFF does not fully prevent access. As a result, previously valid credentials can still be used to obtain authentication tokens. This weakens administrative controls and could allow unintended access to container registry resources.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.keycloak:keycloak-servicesMaven
<= 26.5.3

Patches

1
743ac24081b2

Check client is enabled in the docker endpoint

https://github.com/keycloak/keycloakRicardo MartinFeb 19, 2026via ghsa
2 files changed · +43 9
  • services/src/main/java/org/keycloak/protocol/docker/DockerEndpoint.java+22 9 modified
    @@ -5,6 +5,7 @@
     import jakarta.ws.rs.core.Response;
     
     import org.keycloak.common.Profile;
    +import org.keycloak.events.Errors;
     import org.keycloak.events.EventBuilder;
     import org.keycloak.events.EventType;
     import org.keycloak.models.AuthenticationFlowModel;
    @@ -40,6 +41,7 @@ public class DockerEndpoint extends AuthorizationEndpointBase {
         public DockerEndpoint(KeycloakSession session, final EventBuilder event, final EventType login) {
             super(session, event);
             this.login = login;
    +        event.event(login);
         }
     
         @GET
    @@ -54,19 +56,11 @@ public Response build() {
                         "username is provided by Basic auth header.");
             }
             service = params.getFirst(DockerAuthV2Protocol.SERVICE_PARAM);
    -        if (service == null) {
    -            throw new ErrorResponseException("invalid_request", "service parameter must be provided", Response.Status.BAD_REQUEST);
    -        }
    -        client = realm.getClientByClientId(service);
    -        if (client == null) {
    -            logger.errorv("Failed to lookup client given by service={0} parameter for realm: {1}.", service, realm.getName());
    -            throw new ErrorResponseException("invalid_client", "Client specified by 'service' parameter does not exist", Response.Status.BAD_REQUEST);
    -        }
    -        session.getContext().setClient(client);
             scope = params.getFirst(DockerAuthV2Protocol.SCOPE_PARAM);
     
             checkSsl();
             checkRealm();
    +        checkService();
     
             final AuthorizationEndpointRequest authRequest = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, params, AuthorizationEndpointRequestParserProcessor.EndpointType.DOCKER_ENDPOINT);
             authenticationSession = createAuthenticationSession(client, authRequest.getState());
    @@ -94,6 +88,25 @@ private void updateAuthenticationSession() {
     
         }
     
    +    private void checkService() {
    +        if (service == null) {
    +            event.error(Errors.INVALID_REQUEST);
    +            throw new ErrorResponseException("invalid_request", "service parameter must be provided", Response.Status.BAD_REQUEST);
    +        }
    +        client = realm.getClientByClientId(service);
    +        if (client == null) {
    +            event.error(Errors.CLIENT_NOT_FOUND);
    +            logger.errorv("Failed to lookup client given by service={0} parameter for realm: {1}.", service, realm.getName());
    +            throw new ErrorResponseException("invalid_client", "Client specified by 'service' parameter does not exist", Response.Status.BAD_REQUEST);
    +        }
    +        if (!client.isEnabled()) {
    +            event.error(Errors.CLIENT_DISABLED);
    +            logger.errorv("The service {0} in realm {1} is disabled.", service, realm.getName());
    +            throw new ErrorResponseException("invalid_client", "Client specified by 'service' is disabled", Response.Status.BAD_REQUEST);
    +        }
    +        session.getContext().setClient(client);
    +    }
    +
         @Override
         protected AuthenticationFlowModel getAuthenticationFlow(AuthenticationSessionModel authSession) {
             return realm.getDockerAuthenticationFlow();
    
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/docker/DockerClientTest.java+21 0 modified
    @@ -8,13 +8,16 @@
     import java.util.Map;
     import java.util.Optional;
     
    +import org.keycloak.admin.client.resource.ClientResource;
     import org.keycloak.common.Profile;
     import org.keycloak.common.util.PemUtils;
     import org.keycloak.crypto.KeyStatus;
     import org.keycloak.models.Constants;
    +import org.keycloak.representations.idm.ClientRepresentation;
     import org.keycloak.representations.idm.KeysMetadataRepresentation;
     import org.keycloak.representations.idm.RealmRepresentation;
     import org.keycloak.testsuite.AbstractKeycloakTest;
    +import org.keycloak.testsuite.admin.ApiUtil;
     import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
     
     import org.junit.Assert;
    @@ -31,7 +34,9 @@
     import static org.keycloak.testsuite.util.WaitUtils.pause;
     
     import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.is;
    +import static org.hamcrest.Matchers.not;
     import static org.junit.Assume.assumeTrue;
     
     @EnableFeature(Profile.Feature.DOCKER)
    @@ -162,6 +167,22 @@ public void shouldPerformDockerAuthAgainstRegistry() throws Exception {
             result = dockerClientContainer.execInContainer("docker", "push", REGISTRY_HOSTNAME + ":" + REGISTRY_PORT + "/empty");
             printCommandResult(result);
             assertThat("Error pushing to registry", result.getExitCode(), is(0));
    +
    +        // logout
    +        result = dockerClientContainer.execInContainer("docker", "logout");
    +        printCommandResult(result);
    +        assertThat("Error performing logout", result.getExitCode(), is(0));
    +
    +        // disable and login should fail
    +        ClientResource client = ApiUtil.findClientByClientId(adminClient.realm(REALM_ID), CLIENT_ID);
    +        ClientRepresentation clientRep = client.toRepresentation();
    +        clientRep.setEnabled(Boolean.FALSE);
    +        client.update(clientRep);
    +
    +        result = dockerClientContainer.execInContainer("docker", "login", "-u", DOCKER_USER, "-p", DOCKER_USER_PASSWORD, REGISTRY_HOSTNAME + ":" + REGISTRY_PORT);
    +        printCommandResult(result);
    +        assertThat("Error performing login", result.getExitCode(), not(is(0)));
    +        assertThat("Service is not disabled", result.getStderr(), containsString("Client specified by 'service' is disabled"));
         }
     
         private void printCommandResult(Container.ExecResult result) {
    

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

8

News mentions

0

No linked articles in our index yet.