CVE-2019-1003093
Description
The Jenkins Nomad Plugin lacks a permission check in NomadCloud.DescriptorImpl#doTestConnection, allowing Overall/Read users to connect to attacker-specified servers.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
The Jenkins Nomad Plugin lacks a permission check in NomadCloud.DescriptorImpl#doTestConnection, allowing Overall/Read users to connect to attacker-specified servers.
Vulnerability
A missing permission check in the NomadCloud.DescriptorImpl#doTestConnection form validation method of the Jenkins Nomad Plugin allows attackers with Overall/Read permission to initiate a connection to an attacker-specified server. The vulnerability affects versions prior to the fix. [1][3]
Exploitation
An attacker needs only Overall/Read permission on the Jenkins instance, which is the default for many users. The attacker can craft a request to the doTestConnection endpoint, specifying a server under their control, and the plugin will attempt to connect to that server. [1][3]
Impact
Successful exploitation allows the attacker to use the Jenkins controller to initiate a network connection to an attacker-specified server. This could be used for network reconnaissance, SSRF (server-side request forgery), or to exfiltrate data by connecting to an external server. The attacker does not need any additional privileges. [1][3]
Mitigation
A fix was included in the Jenkins Nomad Plugin version that includes the commit 93ea215a649575e4063e1dfe8361b684c29a91e3 [4]. Users should update to the latest version of the plugin. If an update is not immediately possible, restricting Overall/Read permission or disabling the plugin are potential workarounds. [1][4]
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:nomadMaven | < 0.6.3 | 0.6.3 |
Affected products
2- Range: all versions as of 2019-04-03
Patches
193ea215a6495Fixing Issue with Orphaned Agents and configurable startup timeout (#1) (#37)
14 files changed · +117 −41
.gitignore+8 −0 modified@@ -2,3 +2,11 @@ target/ work/ nomad.iml +.project +.settings/ +.factorypath +.classpath +.vscode/ +.vagrant/ +Vagrantfile +circleci/**
pom.xml+2 −0 modified@@ -37,6 +37,8 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.level>8</java.level> <findbugs.failOnError>false</findbugs.failOnError> + <jenkins.version>2.121.1</jenkins.version> + <java.level>8</java.level> </properties> <developers>
src/main/java/org/jenkinsci/plugins/nomad/NomadCloud.java+34 −10 modified@@ -21,16 +21,20 @@ import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; - public class NomadCloud extends AbstractCloudImpl { private static final Logger LOGGER = Logger.getLogger(NomadCloud.class.getName()); + private final List<? extends NomadSlaveTemplate> templates; + private final String nomadUrl; private String jenkinsUrl; private String jenkinsTunnel; private String slaveUrl; + private int workerTimeout = 1; + private NomadApi nomad; + private int pending = 0; @DataBoundConstructor @@ -40,13 +44,16 @@ public NomadCloud( String jenkinsUrl, String jenkinsTunnel, String slaveUrl, - List<? extends NomadSlaveTemplate> templates) { + String workerTimeout, + List<? extends NomadSlaveTemplate> templates) + { super(name, null); this.nomadUrl = nomadUrl; this.jenkinsUrl = jenkinsUrl; this.jenkinsTunnel = jenkinsTunnel; this.slaveUrl = slaveUrl; + setWorkerTimeout(workerTimeout); if (templates == null) { this.templates = Collections.emptyList(); @@ -122,11 +129,9 @@ public ProvisioningCallback(String slaveName, NomadSlaveTemplate template, Nomad } public Node call() throws Exception { - final NomadSlave slave = new NomadSlave( - cloud, slaveName, - "Nomad Jenkins Slave", + name, template, template.getLabels(), new NomadRetentionStrategy(template.getIdleTerminationInMinutes()), @@ -159,17 +164,18 @@ public Node call() throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); Future<Boolean> future = executorService.submit(callableTask); - try { - future.get(5, TimeUnit.MINUTES); + try{ + future.get(cloud.workerTimeout, TimeUnit.MINUTES); LOGGER.log(Level.INFO, "Connection established"); } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Slave computer did not come online within {0} minutes, terminating slave"); + LOGGER.log(Level.SEVERE, "Slave computer did not come online within " + workerTimeout + " minutes, terminating slave"+ slave); slave.terminate(); + throw new RuntimeException("Timed out waiting for agent to start up. Timeout: " + workerTimeout + " minutes."); } finally { future.cancel(true); executorService.shutdown(); + pending -= template.getNumExecutors(); } - pending -= template.getNumExecutors(); return slave; } } @@ -221,7 +227,9 @@ public FormValidation doTestConnection(@QueryParameter("nomadUrl") String nomadU } } + @RequirePOST public FormValidation doCheckName(@QueryParameter String name) { + Objects.requireNonNull(Jenkins.getInstance()).checkPermission(Jenkins.ADMINISTER); if (Strings.isNullOrEmpty(name)) { return FormValidation.error("Name must be set"); } else { @@ -231,7 +239,11 @@ public FormValidation doCheckName(@QueryParameter String name) { } // Getters - protected String getNomadUrl() { + public String getName() { + return name; + } + + public String getNomadUrl() { return nomadUrl; } @@ -243,6 +255,10 @@ public String getSlaveUrl() { return slaveUrl; } + public int getWorkerTimeout() { + return workerTimeout; + } + public void setJenkinsUrl(String jenkinsUrl) { this.jenkinsUrl = jenkinsUrl; } @@ -251,6 +267,14 @@ public void setSlaveUrl(String slaveUrl) { this.slaveUrl = slaveUrl; } + public void setWorkerTimeout(String workerTimeout) { + try { + this.workerTimeout = Integer.parseInt(workerTimeout); + } catch(NumberFormatException ex) { + LOGGER.log(Level.WARNING, "Failed to parse timeout defaulting to current value (default: 1 minute): " + workerTimeout + " minutes"); + } + } + public void setNomad(NomadApi nomad) { this.nomad = nomad; }
src/main/java/org/jenkinsci/plugins/nomad/NomadComputer.java+0 −3 modified@@ -11,14 +11,11 @@ public class NomadComputer extends AbstractCloudComputer<NomadSlave> { private static final Logger LOGGER = Logger.getLogger(NomadComputer.class.getName()); - private final NomadCloud cloud; - private final Boolean reusable; public NomadComputer(NomadSlave slave) { super(slave); - this.cloud = slave.getCloud(); this.reusable = slave.getReusable(); }
src/main/java/org/jenkinsci/plugins/nomad/NomadConstraintTemplate.java+1 −8 modified@@ -3,17 +3,10 @@ import hudson.Extension; import org.kohsuke.stapler.DataBoundConstructor; -import hudson.Util; import hudson.model.Describable; import hudson.model.Descriptor; import jenkins.model.Jenkins; -import javax.annotation.Nullable; -import java.util.*; -import java.util.logging.Logger; - -import org.json.JSONObject; - public class NomadConstraintTemplate implements Describable<NomadConstraintTemplate> { private final String ltarget; @@ -57,7 +50,7 @@ public String getDisplayName() { public Descriptor<NomadConstraintTemplate> getDescriptor() { return Jenkins.getInstance().getDescriptor(getClass()); } - + public String getLtarget() { return ltarget; }
src/main/java/org/jenkinsci/plugins/nomad/NomadProvisioningStrategy.java+1 −1 modified@@ -39,7 +39,7 @@ public NodeProvisioner.StrategyDecision apply(@Nonnull NodeProvisioner.StrategyS new Object[]{snapshot.getAvailableExecutors(), snapshot.getConnectingExecutors(), strategyState.getAdditionalPlannedCapacity(),((NomadCloud)nomadCloud).getPending() }); int availableCapacity = snapshot.getAvailableExecutors() + snapshot.getConnectingExecutors() + - strategyState.getAdditionalPlannedCapacity() + + strategyState.getAdditionalPlannedCapacity() + ((NomadCloud)nomadCloud).getPending(); int currentDemand = snapshot.getQueueLength();
src/main/java/org/jenkinsci/plugins/nomad/NomadRetentionStrategy.java+4 −0 modified@@ -9,6 +9,10 @@ public NomadRetentionStrategy(int idleMinutes) { super(idleMinutes); } + public NomadRetentionStrategy(String idleMinutes) { + super(Integer.parseInt(idleMinutes)); + } + public static class DescriptorImpl extends Descriptor<hudson.slaves.RetentionStrategy<?>> { @Override public String getDisplayName() {
src/main/java/org/jenkinsci/plugins/nomad/NomadSlave.java+44 −10 modified@@ -2,35 +2,45 @@ import hudson.Extension; import hudson.model.Descriptor; +import hudson.model.Descriptor.FormException; import hudson.model.Node; import hudson.model.TaskListener; import hudson.slaves.*; +import jenkins.model.Jenkins; import java.io.IOException; + +import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import org.kohsuke.stapler.DataBoundConstructor; + public class NomadSlave extends AbstractCloudSlave implements EphemeralNode { private static final Logger LOGGER = Logger.getLogger(NomadSlave.class.getName()); - private final NomadCloud cloud; - private final Boolean reusable; + private NomadSlaveTemplate template; + + private static final String NODE_DESCRIPTION = "Nomad Jenkins Slave"; + + private final String cloudName; + private final int idleTerminationInMinutes; + public NomadSlave( - NomadCloud cloud, String name, - String nodeDescription, + String cloudName, NomadSlaveTemplate template, String labelString, - hudson.slaves.RetentionStrategy retentionStrategy, + NomadRetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties ) throws Descriptor.FormException, IOException { super( name, - nodeDescription, + NODE_DESCRIPTION, template.getRemoteFs(), template.getNumExecutors(), template.getMode(), @@ -40,8 +50,21 @@ public NomadSlave( nodeProperties ); - this.cloud = cloud; + this.cloudName = cloudName; + this.reusable = template.getReusable(); + this.idleTerminationInMinutes = template.getIdleTerminationInMinutes(); + } + + @DataBoundConstructor + // {"name":"jenkins-95266550a531","cloudName":"NomadTest","labelString":"test","mode":"NORMAL","remoteFS":"/","numExecutors":"1","idleTerminationInMinutes":"10","reusable":true} + public NomadSlave(String name, String cloudName, String remoteFS, String numExecutors, Mode mode, String labelString, String idleTerminationInMinutes, boolean reusable) throws FormException, IOException{ + super(name, NODE_DESCRIPTION, remoteFS, numExecutors, mode, labelString, new JNLPLauncher(), new NomadRetentionStrategy(idleTerminationInMinutes), Collections.<NodeProperty<?>>emptyList()); + + this.cloudName = cloudName; + this.reusable = reusable; + + this.idleTerminationInMinutes = Integer.parseInt(idleTerminationInMinutes); } @Override @@ -51,6 +74,9 @@ public Node asNode() { @Extension public static class DescriptorImpl extends SlaveDescriptor { + public DescriptorImpl() { + load(); + } @Override public String getDisplayName() { @@ -67,21 +93,29 @@ public boolean isInstantiable() { } @Override - public AbstractCloudComputer createComputer() { + public AbstractCloudComputer<NomadSlave> createComputer() { return new NomadComputer(this); } @Override protected void _terminate(TaskListener listener) { LOGGER.log(Level.INFO, "Asking Nomad to deregister slave '" + getNodeName() + "'"); - cloud.Nomad().stopSlave(getNodeName()); + getCloud().Nomad().stopSlave(getNodeName()); } public NomadCloud getCloud() { - return cloud; + return (NomadCloud) Jenkins.getInstance().getCloud(cloudName); + } + + public String getCloudName() { + return cloudName; } public Boolean getReusable() { return reusable; } + + public int getIdleTerminationInMinutes() { + return this.idleTerminationInMinutes; + } }
src/main/java/org/jenkinsci/plugins/nomad/NomadSlaveTemplate.java+1 −3 modified@@ -10,9 +10,7 @@ import jenkins.model.Jenkins; import org.kohsuke.stapler.DataBoundConstructor; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.logging.Logger; public class NomadSlaveTemplate implements Describable<NomadSlaveTemplate> {
src/main/resources/org/jenkinsci/plugins/nomad/NomadCloud/config.jelly+5 −1 modified@@ -8,7 +8,7 @@ <f:entry title="Nomad URL" field="nomadUrl" description="Nomad API URL [hostname:port]"> <f:textbox default="http://127.0.0.1:4646"/> - </f:entry> + </f:entry>j <f:entry title="Jenkins Base URL" field="jenkinsUrl" description="Jenkins base URL"> <f:textbox default="${instance.getJenkinsUrl()}"/> @@ -18,6 +18,10 @@ <f:textbox default="${instance.getJenkinsTunnel()}"/> </f:entry> + <f:entry title="Worker Startup Timeout" field="workerTimeout" description="Worker Startup timeout in minutes"> + <f:textbox default="1"/> + </f:entry> + <f:entry title="Jenkins Slave URL" field="slaveUrl" description="Jenkins slave URL"> <f:textbox default="${instance.getSlaveUrl()}"/> </f:entry>
src/main/resources/org/jenkinsci/plugins/nomad/NomadSlave/configure-entries.jelly+14 −4 modified@@ -1,21 +1,31 @@ <?jelly escape-by-default='true'?> <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> - <table width="100%"> + <f:entry title="${%Cloud Name}" field="cloudName"> + <f:readOnlyTextbox /> + </f:entry> - <f:entry title="Labels" field="labelString"> + <f:entry title="${%Labels}" field="labelString"> <f:textbox/> </f:entry> + <f:slave-mode name="mode" node="${instance}" /> + + <f:entry title="${%Remote FS}" field="remoteFS"> + <f:readOnlyTextbox /> + </f:entry> + <f:entry title="Number of executors" field="numExecutors"> <f:textbox/> </f:entry> - <f:entry title="Idle termination time" field="idleTerminationInMinutes"> + <f:entry title="Idle termination time (minutes)" field="idleTerminationInMinutes"> <f:textbox/> </f:entry> + <f:entry title="Reusable"> + <f:checkbox name="reusable" field="reusable" default="true" value="${instance.reusable}" /> + </f:entry> </table> - </j:jelly>
src/main/resources/org/jenkinsci/plugins/nomad/NomadSlaveTemplate/config.jelly+1 −1 modified@@ -49,7 +49,7 @@ <f:entry title="Executors" field="numExecutors"> <f:textbox default="1" /> </f:entry> - + <f:slave-mode name="mode" node="${instance}" /> <f:entry title="Workspace root" field="remoteFs">
src/test/java/org/jenkinsci/plugins/nomad/NomadApiTest.java+1 −0 modified@@ -31,6 +31,7 @@ public class NomadApiTest { "jenkinsUrl", "jenkinsTunnel", "slaveUrl", + "1", Collections.singletonList(slaveTemplate)); @Before
src/test/java/org/jenkinsci/plugins/nomad/NomadCloudTest.java+1 −0 modified@@ -22,6 +22,7 @@ public class NomadCloudTest { "jenkinsUrl", "jenkinsTunnel", "slaveUrl", + "1", Collections.singletonList(slaveTemplate)); @Before
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-p278-2qh9-6mwjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-1003093ghsaADVISORY
- www.openwall.com/lists/oss-security/2019/04/12/2ghsamailing-listx_refsource_MLISTWEB
- www.securityfocus.com/bid/107790ghsavdb-entryx_refsource_BIDWEB
- github.com/jenkinsci/nomad-plugin/commit/93ea215a649575e4063e1dfe8361b684c29a91e3ghsaWEB
- github.com/jenkinsci/nomad-plugin/releases/tag/v0.6.3ghsaWEB
- jenkins.io/security/advisory/2019-04-03/ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.