CVE-2019-10439
Description
In CRX Content Package Deployer Plugin ≤1.8.1, missing permission checks in doFillCredentialsIdItems methods let Overall/Read users enumerate credential IDs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In CRX Content Package Deployer Plugin ≤1.8.1, missing permission checks in doFillCredentialsIdItems methods let Overall/Read users enumerate credential IDs.
Vulnerability
Description
A missing permission check in Jenkins CRX Content Package Deployer Plugin versions 1.8.1 and earlier allowed users with only Overall/Read access to enumerate credential IDs stored in Jenkins. The flaw existed in various doFillCredentialsIdItems methods, which did not properly verify that the user had the necessary permissions (such as Item.CONFIGURE) before returning the list of credential IDs [1][3].
Exploitation
An attacker with Overall/Read access could trigger these form-validation methods directly, bypassing any required authorization. The methods did not require POST requests, making the vulnerability also exploitable via a cross-site request forgery (CSRF) attack, as noted in the related advisory [1][2]. No other special privileges or prior authentication were needed beyond the basic Overall/Read permission.
Impact
By exploiting this issue, an attacker could gather the IDs of all credentials stored in the Jenkins instance. While the credential secrets themselves are not directly exposed, knowledge of valid credential IDs can facilitate further attacks, such as using captured credential IDs in combination with other plugin vulnerabilities (e.g., CVE-2019-10437/CVE-2019-10438) to exfiltrate credential secrets or connect to attacker-controlled servers [1].
Mitigation
The vulnerability has been fixed in versions after 1.8.1 of the CRX Content Package Deployer Plugin. The fix introduced proper permission checks and added @RequirePOST annotations to prevent CSRF, as shown in the commit that restricts doFillCredentialsIdItems to administrators only [2]. Users are advised to update to the latest plugin version from the Jenkins Update Center.
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:crx-content-package-deployerMaven | < 1.9 | 1.9 |
Affected products
2- Range: 1.8.1 and earlier
Patches
106cd0e7e1b3fReverted jelly changes; assert global scope; fixed null Item handling
9 files changed · +39 −18
src/main/java/org/jenkinsci/plugins/graniteclient/GraniteAHCFactory.java+4 −5 modified@@ -67,7 +67,7 @@ public final class GraniteAHCFactory extends Descriptor<GraniteAHCFactory> private static final Logger LOGGER = Logger.getLogger(GraniteAHCFactory.class.getName()); private static final TaskListener DEFAULT_LISTENER = new LogTaskListener(LOGGER, Level.INFO); - private static final long serialVersionUID = 1329103722879551701L; + private static final long serialVersionUID = 1329103722879551702L; private static final int DEFAULT_TIMEOUT = GraniteClientGlobalConfig.DEFAULT_TIMEOUT; private String credentialsId; @@ -146,10 +146,9 @@ public String getDisplayName() { } @RequirePOST - public AbstractIdCredentialsListBoxModel doFillCredentialsIdItems(@AncestorInPath Item context, - @QueryParameter("value") String value) { - context.checkPermission(Item.CONFIGURE); - return GraniteCredentialsListBoxModel.fillItems(value, context); + public AbstractIdCredentialsListBoxModel doFillCredentialsIdItems(@QueryParameter("value") String value) { + Jenkins.getActiveInstance().checkPermission(Jenkins.ADMINISTER); + return GraniteCredentialsListBoxModel.fillItems(value, null); } public Credentials getDefaultCredentials() {
src/main/java/org/jenkinsci/plugins/graniteclient/GraniteCredentialsListBoxModel.java+27 −6 modified@@ -34,22 +34,26 @@ import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsNameProvider; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.common.AbstractIdCredentialsListBoxModel; import com.cloudbees.plugins.credentials.common.IdCredentials; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.model.Item; +import hudson.model.Queue; +import hudson.model.queue.Tasks; import hudson.security.ACL; import hudson.security.AccessControlled; +import jenkins.model.Jenkins; /** * */ public class GraniteCredentialsListBoxModel extends AbstractIdCredentialsListBoxModel<GraniteCredentialsListBoxModel, IdCredentials> { - private static final long serialVersionUID = 6621529150670191090L; + private static final long serialVersionUID = 6621529150670191091L; @NonNull @Override @@ -77,27 +81,44 @@ private static AbstractIdCredentialsListBoxModel fillItems(final String currentV AbstractIdCredentialsListBoxModel<GraniteCredentialsListBoxModel, IdCredentials> model = new GraniteCredentialsListBoxModel().withEmptySelection(); - if (context == null || !context.hasPermission(Item.CONFIGURE)) { + // when a context is provided (by a job config) but lacks configure permissions + // return the existing value + if (context != null && !context.hasPermission(Item.CONFIGURE)) { + Credentials _credentials = GraniteNamedIdCredentials.getCredentialsById(currentValue); + return model.with(GraniteNamedIdCredentials.maybeWrap(_credentials)); + } + + // when a context is not provided (by global config) but lacks admin permissions + // return the existing value + if (context == null && !Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { Credentials _credentials = GraniteNamedIdCredentials.getCredentialsById(currentValue); return model.with(GraniteNamedIdCredentials.maybeWrap(_credentials)); } List<SSHUserPrivateKey> keys = CredentialsProvider.lookupCredentials(SSHUserPrivateKey.class, - context, ACL.SYSTEM, reqs); + context, + context instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) context) : ACL.SYSTEM, + reqs); if (!keys.isEmpty()) { for (SSHUserPrivateKey key : keys) { - model = model.with(GraniteNamedIdCredentials.wrap(key)); + if (key.getScope() == CredentialsScope.GLOBAL) { + model = model.with(GraniteNamedIdCredentials.wrap(key)); + } } } List<StandardUsernamePasswordCredentials> basicAuthCredsList = CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, - context, ACL.SYSTEM, reqs); + context, + context instanceof Queue.Task ? Tasks.getAuthenticationOf((Queue.Task) context) : ACL.SYSTEM, + reqs); if (!basicAuthCredsList.isEmpty()) { for (StandardUsernamePasswordCredentials creds : basicAuthCredsList) { - model = model.with(GraniteNamedIdCredentials.wrap(creds)); + if (creds.getScope() == CredentialsScope.GLOBAL) { + model = model.with(GraniteNamedIdCredentials.wrap(creds)); + } } }
src/main/java/org/jenkinsci/plugins/graniteclient/GraniteNamedIdCredentials.java+2 −1 modified@@ -289,7 +289,8 @@ private CredentialsIdMatcher(String credentialsId) { } public boolean matches(@NonNull Credentials item) { - if (credentialsId != null && !credentialsId.isEmpty()) { + if ((item.getScope() == CredentialsScope.GLOBAL) + && credentialsId != null && !credentialsId.isEmpty()) { if (item instanceof SSHUserPrivateKey) { return credentialsId.equals(((SSHUserPrivateKey) item).getId()); } else if (item instanceof IdCredentials) {
src/main/resources/org/jenkinsci/plugins/graniteclient/BuildPackageBuilder/config.jelly+1 −1 modified@@ -54,7 +54,7 @@ <f:advanced title="Connection Options"> <f:entry title="Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:validateButton
src/main/resources/org/jenkinsci/plugins/graniteclient/DeployPackagesBuilder/config.jelly+1 −1 modified@@ -69,7 +69,7 @@ <f:advanced title="Connection Options"> <f:entry title="Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:validateButton
src/main/resources/org/jenkinsci/plugins/graniteclient/DownloadPackagesBuilder/config.jelly+1 −1 modified@@ -51,7 +51,7 @@ <f:advanced title="Connection Options"> <f:entry title="Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:validateButton
src/main/resources/org/jenkinsci/plugins/graniteclient/GraniteAHCFactory/global.jelly+1 −1 modified@@ -30,7 +30,7 @@ <f:section title="CRX Content Package Deployer - HTTP Client" name="GraniteAHCFactory"> <f:entry title="Default Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:entry title="Preempt Login Base URL Patterns" field="preemptLoginForBaseUrls">
src/main/resources/org/jenkinsci/plugins/graniteclient/PackageChoiceParameterDefinition/config.jelly+1 −1 modified@@ -68,7 +68,7 @@ <f:advanced title="Connection Options"> <f:entry title="Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:validateButton
src/main/resources/org/jenkinsci/plugins/graniteclient/ReplicatePackagesBuilder/config.jelly+1 −1 modified@@ -43,7 +43,7 @@ <f:advanced title="Connection Options"> <f:entry title="Credentials" field="credentialsId"> - <creds:select checkMethod="post"/> + <creds:select/> </f:entry> <f:validateButton
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-4cmq-88f8-53r5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10439ghsaADVISORY
- github.com/jenkinsci/crx-content-package-deployer-plugin/commit/06cd0e7e1b3f2fb87b3fa332ee1da710ca94b8e1ghsaWEB
- jenkins.io/security/advisory/2019-10-16/mitrex_refsource_CONFIRM
- jenkins.io/security/advisory/2019-10-16/ghsaWEB
News mentions
0No linked articles in our index yet.