Jenkins: Missing permission check allows unauthorized cancellation of queue items
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]
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@RequirePOSTpublic 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> urlProvidr.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]
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@RequirePOSTpublic 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> urlProvidr.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
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2026-53438
- WEBhttps://github.com/jenkinsci/jenkins/commit/a154e1108f3170dc036ecbe6b2a89f164ff1554e
- WEBhttps://github.com/jenkinsci/jenkins/commit/d22d0916148a430dd308760641539d06c0d8a184
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://www.jenkins.io/security/advisory/2026-06-10/#SECURITY-3712