Security context
Medium· 4.3GHSA-mw82-xcg6-gx79 CVE-2026-53438CWE-862Published Jun 10, 2026

Jenkins: Missing permission check allows unauthorized cancellation of queue items

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 2.555.32.556 → fixed in 2.568

Details

A missing permission check in Jenkins 2.567 and earlier, LTS 2.555.2 and earlier allows attackers with Item/Cancel permission, but lacking Item/Read permission, to cancel queue items they do not have permission to view.

The fix

[SECURITY-3712]

Kevin-CB· May 29, 2026, 07:39 AM+911a154e1108f
core/src/main/java/hudson/model/Queue.java+4 0
@@ -132,6 +132,7 @@
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.CancelRequestHandlingException;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
@@ -2480,6 +2481,9 @@ public String getSearchUrl() {
@Deprecated
@RequirePOST
public HttpResponse doCancelQueue() {
+ if (!hasReadPermission(this, true)) {
+ throw new CancelRequestHandlingException();
+ }
if (hasCancelPermission()) {
Jenkins.get().getQueue().cancel(this);
}
test/src/test/java/hudson/model/QueueTest.java+1 1
@@ -1300,7 +1300,7 @@ private void checkCancelOperationUsingUrl(Function<Queue.Item, String> urlProvid
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Jenkins.READ, Item.READ, Item.CANCEL).everywhere().to("admin")
- .grant(Jenkins.READ).everywhere().to("user")
+ .grant(Jenkins.READ, Item.READ).everywhere().to("user")
);
// prevent execution to push stuff into the queue
test/src/test/java/jenkins/security/Security3712Test.java+86 0
@@ -0,0 +1,86 @@
+package jenkins.security;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import hudson.model.Cause;
+import hudson.model.FreeStyleProject;
+import hudson.model.Item;
+import hudson.model.Queue;
+import java.net.URI;
+import java.net.URL;
+import jenkins.model.Jenkins;
+import org.htmlunit.HttpMethod;
+import org.htmlunit.Page;
+import org.htmlunit.WebRequest;
+import org.junit.jupiter.api.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.MockAuthorizationStrategy;
+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
+
+@WithJenkins
+@Issue("SECURITY-3712")
+class Security3712Test {
+
+ @Test
+ void legacyCancelQueueEndpointEnforcesReadVisibility(JenkinsRule j) throws Exception {
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setCrumbIssuer(null);
+
+ final FreeStyleProject project = j.createFreeStyleProject("hidden-job");
+
+ j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
+ .grant(Jenkins.READ).everywhere().toEveryone()
+ .grant(Item.CANCEL).everywhere().to("attacker")
+ .grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ project.scheduleBuild2(10, new Cause.UserIdCause("admin"));
+
+ final Queue.Item[] items = j.jenkins.getQueue().getItems();
+ assertThat(items.length, is(1));
+ final long id = items[0].getId();
+
+ try (JenkinsRule.WebClient wc = j.createWebClient().login("attacker")) {
+ wc.setThrowExceptionOnFailingStatusCode(false);
+ wc.setRedirectEnabled(false);
+
+ final WebRequest req = new WebRequest(URI.create(j.jenkins.getRootUrl() + "queue/item/" + id + "/cancelQueue").toURL(), HttpMethod.POST);
+ final Page response = wc.getPage(req);
+
+ assertThat(response.getWebResponse().getStatusCode(), is(404));
+ assertThat(j.jenkins.getQueue().getItems().length, is(1));
+ }
+ }
+
+ @Test
+ void legacyCancelQueueEndpointAllowsCancelWithReadPermission(JenkinsRule j) throws Exception {
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setCrumbIssuer(null);
+
+ final FreeStyleProject project = j.createFreeStyleProject("allowed-job");
+
+ j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
+ .grant(Jenkins.READ).everywhere().toEveryone()
+ .grant(Item.CANCEL, Item.READ).onItems(project).to("user")
+ .grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ project.scheduleBuild2(10, new Cause.UserIdCause("admin"));
+
+ final Queue.Item[] items = j.jenkins.getQueue().getItems();
+ assertThat(items.length, is(1));
+ final long id = items[0].getId();
+
+ try (JenkinsRule.WebClient wc = j.createWebClient().login("user")) {
+ wc.setThrowExceptionOnFailingStatusCode(false);
+ wc.setRedirectEnabled(false);
+
+ final WebRequest req = new WebRequest(
+ new URL(j.jenkins.getRootUrl() + "queue/item/" + id + "/cancelQueue"), HttpMethod.POST);
+
+ final Page response = wc.getPage(req);
+ assertThat(response.getWebResponse().getStatusCode(), is(200));
+ assertThat(j.jenkins.getQueue().getItems().length, is(0));
+ }
+ }
+}

[SECURITY-3712]

Kevin-CB· Jun 2, 2026, 10:14 AM+911d22d091614
core/src/main/java/hudson/model/Queue.java+4 0
@@ -132,6 +132,7 @@
import org.kohsuke.accmod.restrictions.Beta;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.CancelRequestHandlingException;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
@@ -2491,6 +2492,9 @@ public String getSearchUrl() {
@Deprecated
@RequirePOST
public HttpResponse doCancelQueue() {
+ if (!hasReadPermission(this, true)) {
+ throw new CancelRequestHandlingException();
+ }
if (hasCancelPermission()) {
Jenkins.get().getQueue().cancel(this);
}
test/src/test/java/hudson/model/QueueTest.java+1 1
@@ -1292,7 +1292,7 @@ private void checkCancelOperationUsingUrl(Function<Queue.Item, String> urlProvid
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Jenkins.READ, Item.READ, Item.CANCEL).everywhere().to("admin")
- .grant(Jenkins.READ).everywhere().to("user")
+ .grant(Jenkins.READ, Item.READ).everywhere().to("user")
);
// prevent execution to push stuff into the queue
test/src/test/java/jenkins/security/Security3712Test.java+86 0
@@ -0,0 +1,86 @@
+package jenkins.security;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import hudson.model.Cause;
+import hudson.model.FreeStyleProject;
+import hudson.model.Item;
+import hudson.model.Queue;
+import java.net.URI;
+import java.net.URL;
+import jenkins.model.Jenkins;
+import org.htmlunit.HttpMethod;
+import org.htmlunit.Page;
+import org.htmlunit.WebRequest;
+import org.junit.jupiter.api.Test;
+import org.jvnet.hudson.test.Issue;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.MockAuthorizationStrategy;
+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
+
+@WithJenkins
+@Issue("SECURITY-3712")
+class Security3712Test {
+
+ @Test
+ void legacyCancelQueueEndpointEnforcesReadVisibility(JenkinsRule j) throws Exception {
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setCrumbIssuer(null);
+
+ final FreeStyleProject project = j.createFreeStyleProject("hidden-job");
+
+ j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
+ .grant(Jenkins.READ).everywhere().toEveryone()
+ .grant(Item.CANCEL).everywhere().to("attacker")
+ .grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ project.scheduleBuild2(10, new Cause.UserIdCause("admin"));
+
+ final Queue.Item[] items = j.jenkins.getQueue().getItems();
+ assertThat(items.length, is(1));
+ final long id = items[0].getId();
+
+ try (JenkinsRule.WebClient wc = j.createWebClient().login("attacker")) {
+ wc.setThrowExceptionOnFailingStatusCode(false);
+ wc.setRedirectEnabled(false);
+
+ final WebRequest req = new WebRequest(URI.create(j.jenkins.getRootUrl() + "queue/item/" + id + "/cancelQueue").toURL(), HttpMethod.POST);
+ final Page response = wc.getPage(req);
+
+ assertThat(response.getWebResponse().getStatusCode(), is(404));
+ assertThat(j.jenkins.getQueue().getItems().length, is(1));
+ }
+ }
+
+ @Test
+ void legacyCancelQueueEndpointAllowsCancelWithReadPermission(JenkinsRule j) throws Exception {
+ j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
+ j.jenkins.setCrumbIssuer(null);
+
+ final FreeStyleProject project = j.createFreeStyleProject("allowed-job");
+
+ j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
+ .grant(Jenkins.READ).everywhere().toEveryone()
+ .grant(Item.CANCEL, Item.READ).onItems(project).to("user")
+ .grant(Jenkins.ADMINISTER).everywhere().to("admin"));
+
+ project.scheduleBuild2(10, new Cause.UserIdCause("admin"));
+
+ final Queue.Item[] items = j.jenkins.getQueue().getItems();
+ assertThat(items.length, is(1));
+ final long id = items[0].getId();
+
+ try (JenkinsRule.WebClient wc = j.createWebClient().login("user")) {
+ wc.setThrowExceptionOnFailingStatusCode(false);
+ wc.setRedirectEnabled(false);
+
+ final WebRequest req = new WebRequest(
+ new URL(j.jenkins.getRootUrl() + "queue/item/" + id + "/cancelQueue"), HttpMethod.POST);
+
+ final Page response = wc.getPage(req);
+ assertThat(response.getWebResponse().getStatusCode(), is(200));
+ assertThat(j.jenkins.getQueue().getItems().length, is(0));
+ }
+ }
+}

References