Security context
High· 8.0GHSA-93qh-vwrm-c5pw CVE-2026-53441CWE-79Published Jun 10, 2026

Jenkins: Stored XSS vulnerability in node offline cause description

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

2.483 → fixed in 2.568

Details

Jenkins 2.483 through 2.567 (both inclusive), LTS 2.492.1 through 2.555.2 (both inclusive) does not escape the user-provided description of a generic offline cause that could be set through the `POST config.xml` API, resulting in a stored cross-site scripting (XSS) vulnerability exploitable by attackers with Agent/Configure permission.

The fix

[SECURITY-3731]

Daniel Beck· Jun 2, 2026, 10:14 AM+50620041e8090
core/src/main/java/hudson/slaves/OfflineCause.java+0 5
@@ -203,11 +203,6 @@ public String getComputerIconAltText() {
public String getIcon() {
return "symbol-person";
}
-
- @Override
- public String toString() {
- return Util.escape(super.toString());
- }
}
public static class ByCLI extends UserCause {
core/src/main/resources/hudson/slaves/OfflineCause/cause.jelly+1 1
@@ -29,7 +29,7 @@ THE SOFTWARE.
<i:formatDate value="${it.time}" type="both" dateStyle="medium" timeStyle="medium"/>
</div>
<div class="message">
- <j:out value="${it}" />
+ ${it}
</div>
</div>
</j:jelly>
test/src/test/java/jenkins/security/Security3669Test.java+49 0
@@ -15,6 +15,7 @@
import org.htmlunit.html.HtmlFormUtil;
import org.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;
+import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
import org.jvnet.hudson.test.junit.jupiter.WithLocalData;
@@ -104,6 +105,54 @@ void postConfigXmlWithLocalizable(JenkinsRule jenkinsRule) throws Exception {
}
}
+ @Test
+ @Issue("SECURITY-3731")
+ void postConfigXmlWithNonUserCause(JenkinsRule jenkinsRule) throws Exception {
+ final DumbSlave agent = jenkinsRule.createOnlineSlave();
+
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
+ "<slave>\n" +
+ " <temporaryOfflineCause class=\"hudson.slaves.OfflineCause$SimpleOfflineCause\">\n" +
+ " <description>\n" +
+ " <holder>\n" +
+ " <owner>hudson.slaves.Messages</owner>\n" +
+ " </holder>\n" +
+ " <key>SlaveComputer.DisconnectedBy</key>\n" +
+ " <args>\n" +
+ " <string>admin</string>\n" +
+ " <string>&lt;img src=x onerror=alert(1)&gt;</string>\n" +
+ " </args>\n" +
+ " </description>\n" +
+ " </temporaryOfflineCause>\n" +
+ " <name>" + agent.getNodeName() + "</name>\n" +
+ " <description></description>\n" +
+ " <remoteFS>/tmp/foo</remoteFS>\n" +
+ " <numExecutors>1</numExecutors>\n" +
+ " <mode>NORMAL</mode>\n" +
+ " <retentionStrategy class=\"hudson.slaves.RetentionStrategy$Always\"/>\n" +
+ " <launcher class=\"hudson.slaves.JNLPLauncher\">\n" +
+ " <workDirSettings>\n" +
+ " <disabled>false</disabled>\n" +
+ " <internalDir>remoting</internalDir>\n" +
+ " <failIfWorkDirIsMissing>false</failIfWorkDirIsMissing>\n" +
+ " </workDirSettings>\n" +
+ " <webSocket>false</webSocket>\n" +
+ " </launcher>\n" +
+ " <label></label>\n" +
+ " <nodeProperties/>\n" +
+ "</slave>";
+ agent.toComputer().updateByXml(new ByteArrayInputStream(xml.getBytes()));
+
+ try (JenkinsRule.WebClient webClient = jenkinsRule.createWebClient()) {
+ final HtmlPage nodePage = webClient.getPage(agent);
+ assertThat(
+ nodePage.getWebResponse().getContentAsString(),
+ allOf(
+ not(containsString("<img src=x onerror=alert(1)>")),
+ containsString("&lt;img src=x onerror=alert(1)>")));
+ }
+ }
+
@Test
@WithLocalData
void dataFromDisk(JenkinsRule jenkinsRule) throws Exception {

References