CVE-2019-10363
Description
Jenkins Configuration as Code Plugin 1.24 and earlier did not reliably identify sensitive values expected to be exported in their encrypted form.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins Configuration as Code Plugin 1.24 and earlier fails to reliably identify sensitive values for encrypted export, potentially leaking plaintext secrets.
Root
Cause
The Jenkins Configuration as Code (CasC) Plugin versions 1.24 and earlier contain a flaw in the export functionality. The plugin is designed to export configuration as YAML, intending to replace sensitive values with their encrypted form (using hudson.util.Secret). However, the mechanism to detect which fields or getters contain secret values is unreliable. The plugin fails to consistently identify values that should be encrypted, causing them to be exported in plaintext [1][4].
Exploitation
This vulnerability affects users who export the Jenkins configuration as code using the CasC plugin. No special network position or authentication beyond normal Jenkins access is required to trigger the export; any administrator or user with the permission to export configuration could inadvertently expose secrets. The flaw lies in the export serialization logic, which does not properly inspect all methods and constructors that return Secret-typed data [3].
Impact
An attacker who obtains a copy of the exported configuration (e.g., through a compromised backup, log file, or shared repository) could read sensitive credentials, tokens, or other secret values in cleartext. This could lead to lateral movement or privilege escalation within the Jenkins environment and connected systems, as the exposed secrets might grant access to other resources [1][4].
Mitigation
Jenkins released version 1.25 of the Configuration as Code Plugin, which fixes this issue by adding proper detection of secret getters, fields, and constructor parameters during export (see commit [3]). Users are strongly advised to update to version 1.25 or later. Affected versions should not be used to export configurations for storage or sharing, as secrets may be leaked [1][2].
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 |
|---|---|---|
io.jenkins:configuration-as-codeMaven | < 1.25 | 1.25 |
Affected products
2- Jenkins project/Jenkins Configuration as Code Pluginv5Range: 1.24 and earlier
Patches
17506d50b8464[SECURITY-1458]
7 files changed · +242 −5
integrations/pom.xml+7 −0 modified@@ -219,6 +219,13 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>io.jenkins.plugins</groupId> + <artifactId>sge-cloud-plugin</artifactId> + <version>1.22</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.jenkins-ci.plugins</groupId> <artifactId>ssh-credentials</artifactId>
integrations/src/test/java/io/jenkins/plugins/casc/CredentialsTest.java+2 −1 modified@@ -92,7 +92,8 @@ public void testExportSSHCredentials() throws Exception { String passphrase = sshCredential.getScalarValue("passphrase"); assertThat(passphrase, not("password")); - assertThat(requireNonNull(Secret.decrypt(passphrase)).getPlainText(), is("password")); + assertThat(requireNonNull(Secret.decrypt(passphrase), "Failed to decrypt the password from " + passphrase) + .getPlainText(), is("password")); String sshKeyExported = sshCredential.get("privateKeySource") .asMapping()
integrations/src/test/java/io/jenkins/plugins/casc/SGECloudTest.java+39 −0 added@@ -0,0 +1,39 @@ +package io.jenkins.plugins.casc; + +import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; +import java.io.StringWriter; +import org.apache.commons.io.output.WriterOutputStream; +import org.jenkinsci.plugins.sge.BatchCloud; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +public class SGECloudTest { + + @Rule + public JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule(); + + @Test + @Issue("SECURITY-1458") + public void shouldNotExportPassword() throws Exception { + ConfigurationAsCode casc = ConfigurationAsCode.get(); + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + + final String passwordText = "Hello, world!"; + BatchCloud cloud = new BatchCloud("testBatchCloud", "whatever", + "sge", 5, "sge.acmecorp.com", 8080, + "username", passwordText); + j.jenkins.clouds.add(cloud); + + StringWriter writer = new StringWriter(); + casc.export(new WriterOutputStream(writer)); + String exported = writer.toString(); + assertThat("Password should not have been exported", + exported, not(containsString(passwordText))); + } +}
plugin/src/main/java/io/jenkins/plugins/casc/Attribute.java+24 −2 modified@@ -223,15 +223,18 @@ public CNode describe(Owner instance, ConfigurationContext context) throws Confi if (o == null) { return null; } + + // In Export we sensitive only those values which do not get rendered as secrets + boolean shouldBeMasked = isSecret(instance); if (multiple) { Sequence seq = new Sequence(); if (o.getClass().isArray()) o = Arrays.asList((Object[]) o); for (Object value : (Iterable) o) { - seq.add(c.describe(value, context)); + seq.add(_describe(c, context, value, shouldBeMasked)); } return seq; } - return c.describe(o, context); + return _describe(c, context, o, shouldBeMasked); } catch (Exception | /* Jenkins.getDescriptorOrDie */AssertionError e) { // Don't fail the whole export, prefer logging this error LOGGER.log(Level.WARNING, "Failed to export", e); @@ -240,6 +243,25 @@ public CNode describe(Owner instance, ConfigurationContext context) throws Confi } } + /** + * Describes a node. + * @param c Configurator + * @param context Context to be passed + * @param value Value + * @param shouldBeMasked If {@code true}, the value should be masked in the output. + * It will be applied to {@link Scalar} nodes only. + * @throws Exception export error + * @return Node + */ + private CNode _describe(Configurator c, ConfigurationContext context, Object value, boolean shouldBeMasked) + throws Exception { + CNode node = c.describe(value, context); + if (shouldBeMasked && node instanceof Scalar) { + ((Scalar)node).sensitive(true); + } + return node; + } + public boolean equals(Owner o1, Owner o2) throws Exception { final Object v1 = getValue(o1); final Object v2 = getValue(o2);
plugin/src/main/java/io/jenkins/plugins/casc/impl/configurators/PrimitiveConfigurator.java+2 −1 modified@@ -63,7 +63,8 @@ public CNode describe(Object instance, ConfigurationContext context) { return new Scalar((Boolean) instance); } if (instance instanceof Secret) { - return new Scalar(((Secret) instance).getEncryptedValue()); + // Secrets are sensitive, but they do not need masking since they are exported in the encrypted form + return new Scalar(((Secret) instance).getEncryptedValue()).encrypted(true); } if (target.isEnum()) { return new Scalar((Enum) instance);
plugin/src/main/java/io/jenkins/plugins/casc/model/Scalar.java+51 −1 modified@@ -9,10 +9,14 @@ public final class Scalar implements CNode, CharSequence { + private static final String SECRET_VALUE_STRING = "****"; + private String value; private Format format; private boolean raw; private Source source; + private boolean sensitive; + private boolean encrypted; public enum Format { STRING, MULTILINESTRING, BOOLEAN, NUMBER, FLOATING } @@ -67,8 +71,52 @@ public Scalar asScalar() { return this; } + /** + * Gets value of the scalar for export. + * @return Value of the scalar if not {@link #isMasked()}, + * {@link #SECRET_VALUE_STRING} otherwise. + * Encrypted sensitive data will be returned as is. + * + */ public String getValue() { - return value; + return isMasked() ? SECRET_VALUE_STRING : value; + } + + /** + * Check whether the scalar value should be masked in the output. + * @return {@code true} if the value is masked + * @since TODO + */ + public boolean isMasked() { + return sensitive && !encrypted; + } + + /** + * Sets the sensitive flag. + * It indicates that the scalar represents a sensitive argument (secret or other restricted data). + * @param sensitive value to set + * @return Object instance + * @since TODO + */ + public Scalar sensitive(boolean sensitive) { + this.sensitive = sensitive; + return this; + } + + /** + * Indicates that the data is encrypted and hence safe to be exported. + * @param encrypted Value to set + * @return Object instance + * @since TODO + */ + public Scalar encrypted(boolean encrypted) { + this.encrypted = encrypted; + return this; + } + + @Override + public boolean isSensitiveData() { + return sensitive; } @NonNull @@ -116,5 +164,7 @@ private Scalar(Scalar it) { this.format = it.format; this.raw = it.raw; this.source = it.source; + this.sensitive = it.sensitive; + this.encrypted = it.encrypted; } }
plugin/src/test/java/io/jenkins/plugins/casc/yaml/ExportTest.java+117 −0 added@@ -0,0 +1,117 @@ +package io.jenkins.plugins.casc.yaml; + + +import hudson.util.Secret; +import io.jenkins.plugins.casc.ConfigurationAsCode; +import io.jenkins.plugins.casc.ConfigurationContext; +import io.jenkins.plugins.casc.ConfiguratorRegistry; +import io.jenkins.plugins.casc.impl.configurators.DataBoundConfigurator; +import io.jenkins.plugins.casc.model.CNode; +import io.jenkins.plugins.casc.snakeyaml.error.YAMLException; +import io.jenkins.plugins.casc.snakeyaml.nodes.Node; +import java.io.IOException; +import java.io.StringWriter; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.kohsuke.stapler.DataBoundConstructor; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Contains tests for particular export cases. + */ +public class ExportTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void shouldNotExportValuesWithSecretGetters() throws Exception { + DataBoundConfigurator<DataBoundSecret> c = new DataBoundConfigurator<>(DataBoundSecret.class); + String res = export(c, new DataBoundSecret("test")); + assertThat(res, not(containsString("test"))); + } + + @Test + @Issue("SECURITY-1458") + public void shouldNotExportValuesWithSecretFields() throws Exception { + DataBoundConfigurator<DataBoundSecretField> c = new DataBoundConfigurator<>(DataBoundSecretField.class); + String res = export(c, new DataBoundSecretField("test")); + assertThat(res, not(containsString("test"))); + } + + @Test + @Issue("SECURITY-1458") + public void shouldNotExportValuesWithSecretConstructors() throws Exception { + DataBoundConfigurator<DataBoundSecretConstructor> c = new DataBoundConfigurator<>(DataBoundSecretConstructor.class); + String res = export(c, new DataBoundSecretConstructor(Secret.fromString("test"))); + // TODO: uncomment after https://github.com/jenkinsci-cert/configuration-as-code-plugin/pull/5 + // assertThat(res, not(containsString("test"))); + } + + public <T> String export(DataBoundConfigurator<T> configurator, T object) throws Exception { + ConfigurationAsCode casc = ConfigurationAsCode.get(); + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + + final CNode config = configurator.describe(object, context); + final Node valueNode = casc.toYaml(config); + + try (StringWriter writer = new StringWriter()) { + ConfigurationAsCode.serializeYamlNode(valueNode, writer); + return writer.toString(); + } catch (IOException e) { + throw new YAMLException(e); + } + } + + public static class DataBoundSecret { + + Secret mySecretValue; + + @DataBoundConstructor + public DataBoundSecret(String mySecretValue) { + this.mySecretValue = Secret.fromString(mySecretValue); + } + + public Secret getMySecretValue() { + return mySecretValue; + } + } + + public static class DataBoundSecretField { + + Secret mySecretValue; + + @DataBoundConstructor + public DataBoundSecretField(String mySecretValue) { + this.mySecretValue = Secret.fromString(mySecretValue); + } + + public String getMySecretValue() { + return mySecretValue.getPlainText(); + } + } + + /** + * Example of a safe persistency to the disk when JCasC cannot discover the field. + */ + public static class DataBoundSecretConstructor { + + Secret mySecretValueField; + + @DataBoundConstructor + public DataBoundSecretConstructor(Secret mySecretValue) { + this.mySecretValueField = mySecretValue; + } + + public String getMySecretValue() { + return mySecretValueField.getPlainText(); + } + } + +}
Vulnerability mechanics
Generated 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-r69h-6c4g-63xfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10363ghsaADVISORY
- www.openwall.com/lists/oss-security/2019/07/31/1ghsamailing-listx_refsource_MLISTWEB
- github.com/jenkinsci/configuration-as-code-plugin/commit/7506d50b846460ec9f4506f0e228d2e44f0d5a3eghsaWEB
- jenkins.io/security/advisory/2019-07-31/ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.