VYPR
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.

PackageAffected versionsPatched versions
org.jenkins-ci.plugins:mercurialMaven
< 1260.vdfb_723cdcc811260.vdfb_723cdcc81

Affected products

1

Patches

1
dfb723cdcc81

[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

News mentions

0

No linked articles in our index yet.