Moderate severityNVD Advisory· Published Oct 19, 2022· Updated May 8, 2025
CVE-2022-43410
CVE-2022-43410
Description
Jenkins Mercurial Plugin 1251.va_b_121f184902 and earlier provides information about which jobs were triggered or scheduled for polling through its webhook endpoint, including jobs the user has no permission to access.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:mercurialMaven | < 1260.vdfb_723cdcc81 | 1260.vdfb_723cdcc81 |
Affected products
1- Range: unspecified
Patches
1dfb723cdcc81[SECURITY-2831]
2 files changed · +187 −4
src/main/java/hudson/plugins/mercurial/MercurialStatus.java+15 −4 modified@@ -33,6 +33,7 @@ import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; +import org.springframework.security.core.Authentication; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_OK; @@ -108,20 +109,22 @@ public HttpResponse doNotifyCommit(@QueryParameter(required=true) final String u String origin = SCMEvent.originOf(Stapler.getCurrentRequest()); // run in high privilege to see all the projects anonymous users don't see. // this is safe because we only initiate polling. + // But we shouldn't disclose the item names to users that is not supposed to see them + final Authentication origAuth = Jenkins.getAuthentication2(); try (ACLContext context = ACL.as(ACL.SYSTEM)) { if (StringUtils.isNotBlank(branch) && StringUtils.isNotBlank(changesetId)) { SCMHeadEvent.fireNow(new MercurialSCMHeadEvent( SCMEvent.Type.UPDATED, new MercurialCommitPayload(new URI(url), branch, changesetId), origin)); return HttpResponses.ok(); } - return handleNotifyCommit(origin, new URI(url)); + return handleNotifyCommit(origin, new URI(url), origAuth); } catch ( URISyntaxException ex ) { throw HttpResponses.error(SC_BAD_REQUEST, ex); } } - private HttpResponse handleNotifyCommit(String origin, URI url) throws ServletException, IOException { + private HttpResponse handleNotifyCommit(String origin, URI url, final Authentication origAuth) throws ServletException, IOException { final List<Item> projects = Lists.newArrayList(); boolean scmFound = false, urlFound = false; @@ -203,11 +206,19 @@ public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object nod rsp.setStatus(SC_OK); rsp.setContentType("text/plain"); for (Item p : projects) { - rsp.addHeader("Triggered", p.getAbsoluteUrl()); + if (p.hasPermission2(origAuth, Item.READ)) { + rsp.addHeader("Triggered", p.getAbsoluteUrl()); + } else { + rsp.addHeader("Triggered", "Something"); + } } PrintWriter w = rsp.getWriter(); for (Item p : projects) { - w.println("Scheduled polling of " + p.getFullName()); + if (p.hasPermission2(origAuth, Item.READ)) { + w.println("Scheduled polling of " + p.getFullName()); + } else { + w.println("Scheduled polling of a job"); + } } if (msg!=null) w.println(msg);
src/test/java/hudson/plugins/mercurial/MercurialStatusPermissionTest.java+172 −0 added@@ -0,0 +1,172 @@ +package hudson.plugins.mercurial; + +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.util.NameValuePair; +import hudson.FilePath; +import hudson.model.FreeStyleProject; +import hudson.model.Item; +import hudson.model.Slave; +import hudson.triggers.SCMTrigger; +import jenkins.model.Jenkins; +import org.hamcrest.Matcher; +import org.jenkinsci.test.acceptance.docker.DockerClassRule; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; +import org.jvnet.hudson.test.SleepBuilder; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; +import static org.hamcrest.collection.ArrayMatching.hasItemInArray; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.StringContains.containsString; +import static org.hamcrest.core.StringEndsWith.endsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class MercurialStatusPermissionTest { + + @Rule public JenkinsRule j = new JenkinsRule(); + @Rule public MercurialRule m = new MercurialRule(j); + @ClassRule + public static DockerClassRule<MercurialContainer> docker = new DockerClassRule<>(MercurialContainer.class); + + private final String[] names = new String[]{"one1", "two2", "three3"}; + private final String carls = names[1]; + private String source; + + @Before + public void setup() throws Exception { + m.hg("version"); // test environment needs to be able to run Mercurial + MercurialContainer container = docker.create(); + Slave slave = container.createSlave(j); + m.withNode(slave); + MercurialInstallation inst = container.createInstallation(j, MercurialContainer.Version.HG5, false, false, false, "", slave); + assertNotNull(inst); + m.withInstallation(inst); + FilePath sampleRepo = slave.getRootPath().child("sampleRepo"); + sampleRepo.mkdirs(); + m.hg(sampleRepo, "init"); + sampleRepo.child("a").write("a", "UTF-8"); + m.hg(sampleRepo, "commit", "--addremove", "--message=a-file"); + + source = "ssh://test@" + container.ipBound(22) + ":" + container.port(22) + "/" + sampleRepo; + + FreeStyleProject p = j.createFreeStyleProject(names[0]); + p.setScm(new MercurialSCM( + inst.getName(), + source, + null, null, null, null, false)); + p.addTrigger(new SCMTrigger("")); + p.getBuildersList().add(new SleepBuilder(1000)); + p = j.createFreeStyleProject(names[1]); + FreeStyleProject carls = p; + p.setScm(new MercurialSCM( + inst.getName(), + source, + null, null, null, null, false)); + p.addTrigger(new SCMTrigger("")); + p.getBuildersList().add(new SleepBuilder(1000)); + p = j.createFreeStyleProject(names[2]); + p.setScm(new MercurialSCM( + inst.getName(), + source, + null, null, null, null, false)); + p.addTrigger(new SCMTrigger("")); + p.getBuildersList().add(new SleepBuilder(1000)); + + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() + .grant(Item.READ).everywhere().to("bob") + .grant(Item.READ).onItems(carls).to("carl") + .grant(Jenkins.ADMINISTER).everywhere().to("alice") + .grant(Jenkins.READ).onRoot().toAuthenticated()); + } + + @Test + public void testTriggeredWithAnonymous() throws Exception { + final Page page = j.createWebClient().goTo("mercurial/notifyCommit?url=" + source, "text/plain"); + final WebResponse response = page.getWebResponse(); + assertEquals(200, response.getStatusCode()); + final List<NameValuePair> headers = response.getResponseHeaders(); + final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList()); + assertEquals("No headers!", 3, triggered.size()); + List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList()); + final String content = response.getContentAsString(); + assertThat(content, containsString("Scheduled polling of a job")); + assertThat(headerValues.toArray(new String[headerValues.size()]), hasItemInArray(containsString("Something"))); + + List<Matcher<? super String>> testHeaders = new ArrayList<>(); + for (String name : names) { + assertThat(content, not(containsString(name))); + testHeaders.add(not(endsWith(name + "/"))); + } + + assertThat(headerValues, containsInAnyOrder(testHeaders)); + } + + @Test + public void testTriggeredWithAllReadable() throws Exception { + final Page page = j.createWebClient().login("bob").goTo("mercurial/notifyCommit?url=" + source, "text/plain"); + final WebResponse response = page.getWebResponse(); + assertEquals(200, response.getStatusCode()); + final List<NameValuePair> headers = response.getResponseHeaders(); + final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList()); + assertEquals("No headers!", 3, triggered.size()); + List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList()); + final String content = response.getContentAsString(); + assertThat(content, containsString("Scheduled polling of")); + assertThat(content, not(containsString("Scheduled polling of a job"))); + assertThat(headerValues.toArray(new String[headerValues.size()]), not(hasItemInArray(containsString("Something")))); + + List<Matcher<? super String>> testHeaders = new ArrayList<>(); + for (String name : names) { + assertThat(content, containsString(name)); + testHeaders.add(endsWith(name + "/")); + } + + assertThat(headerValues, containsInAnyOrder(testHeaders)); + } + + @Test + public void testTriggeredWithOneReadable() throws Exception { + final Page page = j.createWebClient().login("carl").goTo("mercurial/notifyCommit?url=" + source, "text/plain"); + final WebResponse response = page.getWebResponse(); + assertEquals(200, response.getStatusCode()); + final List<NameValuePair> headers = response.getResponseHeaders(); + final List<NameValuePair> triggered = headers.stream().filter(nvp -> nvp.getName().equals("Triggered")).collect(Collectors.toList()); + assertEquals("No headers!", 3, triggered.size()); + List<String> headerValues = triggered.stream().map(NameValuePair::getValue).collect(Collectors.toList()); + final String content = response.getContentAsString(); + assertThat(content, containsString("Scheduled polling of")); + assertThat(content, containsString("Scheduled polling of a job")); + assertThat(headerValues.toArray(new String[headerValues.size()]), hasItemInArray(containsString("Something"))); + + List<Matcher<? super String>> testHeaders = new ArrayList<>(); + for (String name : names) { + if (name.equals(carls)) { + assertThat(content, containsString(name)); + testHeaders.add(endsWith(name + "/")); + } else { + assertThat(content, not(containsString(name))); + testHeaders.add(not(endsWith(name + "/"))); + } + } + + assertThat(headerValues, containsInAnyOrder(testHeaders)); + } +}
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
5- github.com/advisories/GHSA-j7pg-863g-22p6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-43410ghsaADVISORY
- www.openwall.com/lists/oss-security/2022/10/19/3ghsamailing-listWEB
- github.com/jenkinsci/mercurial-plugin/commit/dfb723cdcc815875cdf63abd32e314ced5e95ac9ghsaWEB
- www.jenkins.io/security/advisory/2022-10-19/ghsaWEB
News mentions
0No linked articles in our index yet.