CVE-2020-2310
Description
Missing permission checks in Jenkins Ansible Plugin 1.0 and earlier allow attackers with Overall/Read permission to enumerate credentials IDs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Missing permission checks in Jenkins Ansible Plugin 1.0 and earlier allow attackers with Overall/Read permission to enumerate credentials IDs.
Vulnerability
Overview
The Jenkins Ansible Plugin versions 1.0 and earlier contain a missing permission check in the credential ID selection method. The doFillCredentialsIdItems method did not verify that the user had the required permissions (such as Item.EXTENDED_READ or CredentialsProvider.USE_ITEM) before returning a list of credential IDs. This flaw allows any user with the low-privilege Overall/Read permission to enumerate the IDs of all credentials stored in Jenkins [1][3].
Exploitation
An attacker with only Overall/Read permission can exploit this vulnerability by directly invoking the credential ID selection endpoint. No additional authentication or special network position is required beyond having a Jenkins account with that minimal permission. The method returns a list of credential IDs that are otherwise not accessible to users without broader permissions [1].
Impact
While credential IDs themselves are not secret values, their enumeration provides attackers with valuable information. Knowing the IDs allows an attacker to target specific credentials in subsequent attacks, such as using them in other plugin forms or attempting to brute-force the corresponding secrets. This information disclosure can be a stepping stone for more severe compromises [1][4].
Mitigation
The issue has been fixed in Ansible Plugin version 1.1 by adding proper permission checks in the doFillCredentialsIdItems method. Users are strongly advised to update to version 1.1 or later. No workarounds are available for earlier versions [1][3].
AI Insight generated on May 21, 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:ansibleMaven | < 1.1 | 1.1 |
Affected products
2- Range: unspecified
Patches
1503be2bc90f7[SECURITY-1943]
4 files changed · +126 −50
pom.xml+2 −2 modified@@ -145,13 +145,13 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <jenkins.version>1.596.1</jenkins.version> + <jenkins.version>1.609.1</jenkins.version> <maven-compiler-plugin.version>3.2</maven-compiler-plugin.version> <maven-resources-plugin.version>2.6</maven-resources-plugin.version> <maven-release-plugin.version>2.5.2</maven-release-plugin.version> <ssh-credentials.version>1.10</ssh-credentials.version> <plain-credentials.version>1.4</plain-credentials.version> - <credentials.version>1.16.1</credentials.version> + <credentials.version>2.1.0</credentials.version> <workflow-step-api.version>1.10</workflow-step-api.version> <junit.version>4.12</junit.version> <mockito.version>1.10.19</mockito.version>
src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleBuilderDescriptor.java+49 −20 modified@@ -11,6 +11,7 @@ import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; +import hudson.model.Item; import org.jenkinsci.plugins.plaincredentials.FileCredentials; import org.jenkinsci.plugins.plaincredentials.StringCredentials; import hudson.model.AbstractProject; @@ -23,6 +24,7 @@ import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.ansible.Inventory.InventoryDescriptor; import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.QueryParameter; /** * Common descriptor for Ansible build steps @@ -44,31 +46,58 @@ protected FormValidation checkNotNullOrEmpty(String parameter, String errorMessa } } - public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() + public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String credentialsId) { + + StandardListBoxModel result = new StandardListBoxModel(); + if (item == null) { + if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(credentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(credentialsId); + } + } + + return result.includeEmptyValue() .withMatching(anyOf( - instanceOf(SSHUserPrivateKey.class), - instanceOf(UsernamePasswordCredentials.class)), - CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project)); + instanceOf(SSHUserPrivateKey.class), + instanceOf(UsernamePasswordCredentials.class)), + CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, item)) + .includeCurrentValue(credentialsId); + } + + public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String vaultCredentialsId) { + return fillVaultCredentials(item, vaultCredentialsId); } - public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(FileCredentials.class), - instanceOf(StringCredentials.class)), - CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); + public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String newVaultCredentialsId) { + return fillVaultCredentials(item, newVaultCredentialsId); } - public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(FileCredentials.class), - instanceOf(StringCredentials.class)), - CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); + private ListBoxModel fillVaultCredentials(Item item, String credentialsId) { + StandardListBoxModel result = new StandardListBoxModel(); + if (item == null) { + if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(credentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(credentialsId); + } + } + + return result.includeEmptyValue() + .withMatching(anyOf( + instanceOf(FileCredentials.class), + instanceOf(StringCredentials.class)), + CredentialsProvider.lookupCredentials(StandardCredentials.class, item)) + .includeCurrentValue(credentialsId); } public List<InventoryDescriptor> getInventories() {
src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsiblePlaybookStep.java+44 −14 modified@@ -32,11 +32,13 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.*; import hudson.model.Computer; +import hudson.model.Item; import hudson.model.Node; import hudson.model.Project; import hudson.model.Run; import hudson.model.TaskListener; import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.ansible.AnsibleInstallation; import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder; @@ -53,6 +55,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; /** * The Ansible playbook invocation step for the Jenkins workflow plugin. @@ -291,22 +294,49 @@ public String getDisplayName() { return "Invoke an ansible playbook"; } - public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(SSHUserPrivateKey.class), - instanceOf(UsernamePasswordCredentials.class)), - CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project)); + public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String credentialsId) { + + StandardListBoxModel result = new StandardListBoxModel(); + if (item == null) { + if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(credentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(credentialsId); + } + } + + return result.includeEmptyValue() + .withMatching(anyOf( + instanceOf(SSHUserPrivateKey.class), + instanceOf(UsernamePasswordCredentials.class)), + CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, item)) + .includeCurrentValue(credentialsId); } - public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(FileCredentials.class), - instanceOf(StringCredentials.class)), - CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); + public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String vaultCredentialsId) { + StandardListBoxModel result = new StandardListBoxModel(); + if (item == null) { + if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(vaultCredentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(vaultCredentialsId); + } + } + + return result.includeEmptyValue() + .withMatching(anyOf( + instanceOf(FileCredentials.class), + instanceOf(StringCredentials.class)), + CredentialsProvider.lookupCredentials(StandardCredentials.class, item)) + .includeCurrentValue(vaultCredentialsId); } public ListBoxModel doFillInstallationItems() {
src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsibleVaultStep.java+31 −14 modified@@ -24,11 +24,13 @@ import com.google.inject.Inject; import hudson.*; import hudson.model.Computer; +import hudson.model.Item; import hudson.model.Node; import hudson.model.Project; import hudson.model.Run; import hudson.model.TaskListener; import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; import org.jenkinsci.plugins.ansible.AnsibleInstallation; import org.jenkinsci.plugins.ansible.AnsibleVaultBuilder; import org.jenkinsci.plugins.plaincredentials.FileCredentials; @@ -40,6 +42,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; /** * The Ansible vault invocation step for the Jenkins workflow plugin. @@ -139,22 +142,14 @@ public String getDisplayName() { return "Invoke ansible vault"; } - public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(FileCredentials.class), - instanceOf(StringCredentials.class)), - CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); + public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String vaultCredentialsId) { + return fillVaultCredentials(item, vaultCredentialsId); } - public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Project project) { - return new StandardListBoxModel() - .withEmptySelection() - .withMatching(anyOf( - instanceOf(FileCredentials.class), - instanceOf(StringCredentials.class)), - CredentialsProvider.lookupCredentials(StandardCredentials.class, project)); + public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Item item, + @QueryParameter String newVaultCredentialsId) { + return fillVaultCredentials(item, newVaultCredentialsId); } public ListBoxModel doFillInstallationItems() { @@ -164,6 +159,28 @@ public ListBoxModel doFillInstallationItems() { } return model; } + + + private ListBoxModel fillVaultCredentials(Item item, String credentialsId) { + StandardListBoxModel result = new StandardListBoxModel(); + if (item == null) { + if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) { + return result.includeCurrentValue(credentialsId); + } + } else { + if (!item.hasPermission(Item.EXTENDED_READ) + && !item.hasPermission(CredentialsProvider.USE_ITEM)) { + return result.includeCurrentValue(credentialsId); + } + } + + return result.includeEmptyValue() + .withMatching(anyOf( + instanceOf(FileCredentials.class), + instanceOf(StringCredentials.class)), + CredentialsProvider.lookupCredentials(StandardCredentials.class, item)) + .includeCurrentValue(credentialsId); + } } public static final class AnsibleVaultExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-6xqj-wvgp-rqp8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-2310ghsaADVISORY
- github.com/jenkinsci/ansible-plugin/commit/503be2bc90f78a726ca339ce9aaea044f23db14eghsaWEB
- www.jenkins.io/security/advisory/2020-11-04/ghsax_refsource_CONFIRMWEB
News mentions
1- Jenkins Security Advisory 2020-11-04Jenkins Security Advisories · Nov 4, 2020