Jenkins has a build information disclosure vulnerability through Run Parameter
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
2.542 → fixed in 2.5510 → fixed in 2.541.2
Details
Jenkins 2.550 and earlier, LTS 2.541.1 and earlier accepts Run Parameter values that refer to builds the user submitting the build does not have access to, allowing attackers with Item/Build and Item/Configure permission to obtain information about the existence of jobs, the existence of builds, and if a specified build exists, its display name.
The fix
[SECURITY-3658]
core/src/main/java/hudson/model/RunParameterValue.java+14 −2
@@ -28,11 +28,13 @@import hudson.EnvVars;import java.util.Locale;import jenkins.model.Jenkins;+import jenkins.util.SystemProperties;import org.kohsuke.stapler.DataBoundConstructor;import org.kohsuke.stapler.export.Exported;public class RunParameterValue extends ParameterValue {+private static /* non-final for Script console */ boolean SKIP_EXISTENCE_CHECK = SystemProperties.getBoolean(RunParameterValue.class.getName() + ".SKIP_EXISTENCE_CHECK", false);private final String runId;@DataBoundConstructor@@ -49,9 +51,19 @@ public RunParameterValue(String name, String runId) {private static String check(String runId) {if (runId == null || runId.indexOf('#') == -1) {throw new IllegalArgumentException(runId);-} else {-return runId;}++if (!SKIP_EXISTENCE_CHECK) {+try {+if (Run.fromExternalizableId(runId) == null) {+throw new IllegalArgumentException(runId);+}+} catch (RuntimeException e) {+throw new IllegalArgumentException(runId);+}+}++return runId;}/**
test/src/test/java/hudson/model/RunParameterValueTest.java+11 −1
@@ -29,12 +29,22 @@import static org.junit.jupiter.api.Assertions.assertThrows;import org.junit.jupiter.api.Test;+import org.jvnet.hudson.test.JenkinsRule;+import org.jvnet.hudson.test.MockFolder;+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;+@WithJenkinsclass RunParameterValueTest {@SuppressWarnings("ResultOfObjectAllocationIgnored")@Test-void robustness() {+void robustness(JenkinsRule jenkinsRule) throws Exception {+// Prepare the job for the parameter value+final MockFolder folder = jenkinsRule.createFolder("folder");+final FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");+job.updateNextBuildNumber(57);+jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));+RunParameterValue rpv = new RunParameterValue("whatever", "folder/job#57");assertEquals("whatever", rpv.getName());assertEquals("folder/job", rpv.getJobName());
test/src/test/java/jenkins/security/Security3658Test.java+177 −0
@@ -0,0 +1,177 @@+package jenkins.security;++import static org.hamcrest.CoreMatchers.allOf;+import static org.hamcrest.CoreMatchers.containsString;+import static org.hamcrest.CoreMatchers.equalTo;+import static org.hamcrest.CoreMatchers.hasItem;+import static org.hamcrest.CoreMatchers.is;+import static org.hamcrest.MatcherAssert.assertThat;+import static org.hamcrest.Matchers.empty;+import static org.hamcrest.Matchers.instanceOf;+import static org.hamcrest.Matchers.not;+import static org.hamcrest.Matchers.nullValue;++import hudson.Launcher;+import hudson.model.AbstractBuild;+import hudson.model.BuildListener;+import hudson.model.FreeStyleBuild;+import hudson.model.FreeStyleProject;+import hudson.model.Item;+import hudson.model.ParameterValue;+import hudson.model.ParametersAction;+import hudson.model.ParametersDefinitionProperty;+import hudson.model.RunParameterDefinition;+import hudson.model.RunParameterValue;+import hudson.tasks.Builder;+import java.lang.reflect.Field;+import java.util.Map;+import jenkins.model.Jenkins;+import org.htmlunit.Page;+import org.htmlunit.WebResponse;+import org.htmlunit.html.HtmlFormUtil;+import org.htmlunit.html.HtmlPage;+import org.htmlunit.html.HtmlSelect;+import org.junit.jupiter.api.Test;+import org.jvnet.hudson.test.JenkinsRule;+import org.jvnet.hudson.test.MockAuthorizationStrategy;+import org.jvnet.hudson.test.junit.jupiter.WithJenkins;+import org.jvnet.hudson.test.recipes.LocalData;+import org.w3c.dom.Element;++@WithJenkins+public class Security3658Test {++@Test+void testNormalCase(JenkinsRule jenkinsRule) throws Exception {+final Jenkins j = jenkinsRule.jenkins;+final FreeStyleProject referencedProject = jenkinsRule.createFreeStyleProject();+final FreeStyleBuild build = jenkinsRule.buildAndAssertSuccess(referencedProject);+build.setDisplayName("Top Secret Build");+build.save();+final FreeStyleProject referencingProject = jenkinsRule.createFreeStyleProject();+referencingProject.addProperty(new ParametersDefinitionProperty(new RunParameterDefinition("the_run", referencedProject.getName(), null, null)));+referencingProject.getBuildersList().add(new EnvVarsPrinterBuilder());+referencingProject.save();+j.setSecurityRealm(jenkinsRule.createDummySecurityRealm());+j.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("alice"));++try (JenkinsRule.WebClient wc = jenkinsRule.createWebClient().login("alice").withThrowExceptionOnFailingStatusCode(false)) {+// Navigate to the build page+final HtmlPage page = wc.getPage(referencingProject, "build?delay=0sec");+// Submit and assert success+final Page submission = HtmlFormUtil.submit(page.getFormByName("parameters"));+final WebResponse response = submission.getWebResponse();+assertThat(response.getStatusCode(), equalTo(200));+}+jenkinsRule.waitUntilNoActivityUpTo(1_000);+// The build log contains the build name+assertThat(referencingProject.getBuildByNumber(1).getLog(1000), hasItem(containsString("the_run_NAME=Top Secret Build")));+}++@Test+void security3658(JenkinsRule jenkinsRule) throws Exception {+final Jenkins j = jenkinsRule.jenkins;+final FreeStyleProject referencedProject = jenkinsRule.createFreeStyleProject();+final FreeStyleBuild build = jenkinsRule.buildAndAssertSuccess(referencedProject);+build.setDisplayName("Top Secret Build");+build.save();+final FreeStyleProject referencingProject = jenkinsRule.createFreeStyleProject();+referencingProject.addProperty(new ParametersDefinitionProperty(new RunParameterDefinition("the_run", referencedProject.getName(), null, null)));+referencingProject.save();+j.setSecurityRealm(jenkinsRule.createDummySecurityRealm());+j.setAuthorizationStrategy(new MockAuthorizationStrategy()+.grant(Jenkins.ADMINISTER).everywhere().to("alice")+.grant(Jenkins.READ).everywhere().toEveryone()+.grant(Item.READ, Item.BUILD).onItems(referencingProject).to("bob"));++try (JenkinsRule.WebClient wc = jenkinsRule.createWebClient().login("bob").withThrowExceptionOnFailingStatusCode(false)) {+// Navigate to the build page and manipulate the DOM to specify an inaccessible build+final HtmlPage page = wc.getPage(referencingProject, "build?delay=0sec");+final Element option = page.createElement("option");+option.setTextContent(referencedProject.getName() + "#1");+page.getElementByName("runId").appendChild(option);+((HtmlSelect) page.getElementByName("runId")).setSelectedIndex(0);++// Submit and assert HTTP error+final Page submission = HtmlFormUtil.submit(page.getFormByName("parameters"));+final WebResponse response = submission.getWebResponse();+assertThat(response.getStatusCode(), equalTo(500));+assertThat(response.getContentAsString(), allOf(+containsString("java.lang.IllegalArgumentException: " + referencedProject.getName() + "#1"),+containsString("Caused: java.lang.IllegalArgumentException: Failed to instantiate class hudson.model.RunParameterValue from {\"name\":\"the_run\",\"runId\":\"" + referencedProject.getName() + "#1\"}")));+}+jenkinsRule.waitUntilNoActivityUpTo(1_000);+assertThat(referencingProject.getBuilds(), is(empty()));+}++@Test+void escapeHatch(JenkinsRule jenkinsRule) throws Exception {+final Jenkins j = jenkinsRule.jenkins;+final FreeStyleProject referencedProject = jenkinsRule.createFreeStyleProject();+final FreeStyleBuild build = jenkinsRule.buildAndAssertSuccess(referencedProject);+build.setDisplayName("Top Secret Build");+build.save();+final FreeStyleProject referencingProject = jenkinsRule.createFreeStyleProject();+referencingProject.addProperty(new ParametersDefinitionProperty(new RunParameterDefinition("the_run", referencedProject.getName(), null, null)));+referencingProject.getBuildersList().add(new EnvVarsPrinterBuilder());+referencingProject.save();+j.setSecurityRealm(jenkinsRule.createDummySecurityRealm());+j.setAuthorizationStrategy(new MockAuthorizationStrategy()+.grant(Jenkins.ADMINISTER).everywhere().to("alice")+.grant(Jenkins.READ).everywhere().toEveryone()+.grant(Item.READ, Item.BUILD).onItems(referencingProject).to("bob"));++final Field escapeHatch = RunParameterValue.class.getDeclaredField("SKIP_EXISTENCE_CHECK");+escapeHatch.setAccessible(true);+escapeHatch.set(null, true);+try {+try (JenkinsRule.WebClient wc = jenkinsRule.createWebClient().login("bob").withThrowExceptionOnFailingStatusCode(false)) {+// Navigate to the build page and manipulate the DOM to specify an inaccessible build+final HtmlPage page = wc.getPage(referencingProject, "build?delay=0sec");+final Element option = page.createElement("option");+option.setTextContent(referencedProject.getName() + "#1");+page.getElementByName("runId").appendChild(option);+((HtmlSelect) page.getElementByName("runId")).setSelectedIndex(0);++final Page submission = HtmlFormUtil.submit(page.getFormByName("parameters"));+final WebResponse response = submission.getWebResponse();+assertThat(response.getStatusCode(), not(equalTo(500)));+assertThat(response.getContentAsString(), allOf(+not(containsString("java.lang.IllegalArgumentException: " + referencedProject.getName() + "#1")),+not(containsString("Caused: java.lang.IllegalArgumentException: Failed to instantiate class hudson.model.RunParameterValue from {\"name\":\"the_run\",\"runId\":\"" + referencedProject.getName() + "#1\"}"))));+}+jenkinsRule.waitUntilNoActivityUpTo(1_000);+// The build log contains the build name+assertThat(referencingProject.getBuildByNumber(1).getLog(1000), hasItem(containsString("the_run_NAME=Top Secret Build")));+} finally {+escapeHatch.set(null, false);+}+}++@Test+@LocalData+void historicalRecords(JenkinsRule jenkinsRule) throws Exception {+// Ensure that we can still read old RunParameterValue records that reference non-existent builds+final ParameterValue parameter = jenkinsRule.jenkins.getItemByFullName("fs", FreeStyleProject.class).getBuildByNumber(1).getAction(ParametersAction.class).getParameter("copy");+assertThat(parameter, instanceOf(RunParameterValue.class));+final RunParameterValue runParameterValue = (RunParameterValue) parameter;+assertThat(runParameterValue.getRunId(), is("nonexistent#1"));+assertThat(runParameterValue.getJobName(), is("nonexistent"));+assertThat(runParameterValue.getNumber(), is("1"));+assertThat(runParameterValue.getRun(), is(nullValue()));+}++public static class EnvVarsPrinterBuilder extends Builder {+@Override+public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) {+try {+for (Map.Entry<String, String> e : build.getEnvironment(listener).entrySet()) {+listener.getLogger().println(e.getKey() + "=" + e.getValue());+}+} catch (Exception e) {+listener.getLogger().println("Failed to get environment: " + e);+}+return true;+}+}+}
test/src/test/resources/jenkins/security/Security3658Test/historicalRecords/jobs/fs/builds/1/build.xml+29 −0
@@ -0,0 +1,29 @@+<?xml version='1.1' encoding='UTF-8'?>+<build>+<actions>+<hudson.model.ParametersAction>+<safeParameters class="sorted-set"/>+<parameters>+<hudson.model.RunParameterValue>+<name>copy</name>+<runId>nonexistent#1</runId>+</hudson.model.RunParameterValue>+</parameters>+<parameterDefinitionNames>+<string>copy</string>+</parameterDefinitionNames>+</hudson.model.ParametersAction>+</actions>+<queueId>1</queueId>+<timestamp>1769414406434</timestamp>+<startTime>1769414406461</startTime>+<result>SUCCESS</result>+<duration>81</duration>+<charset>UTF-8</charset>+<keepLog>false</keepLog>+<builtOn></builtOn>+<scm class="hudson.scm.NullChangeLogParser"/>+<culprits class="java.util.Collections$UnmodifiableSet">+<c class="sorted-set"/>+</culprits>+</build>
test/src/test/resources/jenkins/security/Security3658Test/historicalRecords/jobs/fs/config.xml+17 −0
@@ -0,0 +1,17 @@+<?xml version='1.1' encoding='UTF-8'?>+<project>+<actions/>+<description></description>+<keepDependencies>false</keepDependencies>+<properties/>+<scm class="hudson.scm.NullSCM"/>+<canRoam>true</canRoam>+<disabled>false</disabled>+<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>+<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>+<triggers/>+<concurrentBuild>false</concurrentBuild>+<builders/>+<publishers/>+<buildWrappers/>+</project>
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2026-27100
- WEBhttps://github.com/jenkinsci/jenkins/commit/f92eadb5813f04ca27439455e2573c3171e93a45
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://github.com/jenkinsci/jenkins/releases/tag/jenkins-2.541.2
- WEBhttps://github.com/jenkinsci/jenkins/releases/tag/jenkins-2.551
- WEBhttps://www.jenkins.io/security/advisory/2026-02-18/#SECURITY-3658