VYPR
Moderate severityNVD Advisory· Published Sep 16, 2020· Updated Aug 4, 2024

CVE-2020-2254

CVE-2020-2254

Description

Jenkins Blue Ocean Plugin 1.23.2 and earlier has an undocumented feature flag that, when enabled, lets attackers with Job/Configure or Job/Create permission read arbitrary files.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins Blue Ocean Plugin 1.23.2 and earlier has an undocumented feature flag that, when enabled, lets attackers with Job/Configure or Job/Create permission read arbitrary files.

Vulnerability

Jenkins Blue Ocean Plugin 1.23.2 and earlier contains an undocumented feature flag. When this flag is enabled, an attacker who possesses either the Job/Configure or Job/Create permission can exploit it to read arbitrary files from the Jenkins controller file system [1][4].

Exploitation

The attack requires the undocumented feature flag to be enabled and the attacker to have at least Job/Configure or Job/Create permissions. No authentication bypass is involved; the attacker must already have these permissions on the Jenkins instance. The plugin does not validate or restrict which files can be read when the flag is active, allowing directory traversal-like access [1].

Impact

Successful exploitation allows an attacker to read any file on the Jenkins controller file system, including sensitive configuration files, credentials, and secrets. This could lead to full compromise of the Jenkins controller and any connected systems [1][4].

Mitigation

Jenkins released Blue Ocean Plugin 1.23.3, which removes the undocumented feature flag or disables it by default. Users should upgrade to version 1.23.3 or later. As noted in the GitHub repository, Blue Ocean will not receive further functionality updates but will continue to get security fixes [2][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.

PackageAffected versionsPatched versions
io.jenkins.blueocean:blueoceanMaven
< 1.23.31.23.3

Affected products

2

Patches

1
f0dd4b68d62a

[SECURITY-1956] remove this undocumented feature which has security issue

https://github.com/jenkinsci/blueocean-pluginolivier lamyAug 31, 2020via ghsa
6 files changed · +162 493
  • blueocean-git-pipeline/src/main/java/io/jenkins/blueocean/blueocean_git_pipeline/GitBareRepoReadSaveRequest.java+142 1 modified
    @@ -24,32 +24,73 @@
     package io.jenkins.blueocean.blueocean_git_pipeline;
     
     import com.cloudbees.plugins.credentials.common.StandardCredentials;
    +import hudson.EnvVars;
    +import hudson.model.TaskListener;
     import hudson.model.User;
    +import hudson.plugins.git.GitException;
    +import hudson.plugins.git.GitTool;
    +import hudson.remoting.VirtualChannel;
     import hudson.tasks.MailAddressResolver;
    +import hudson.util.LogTaskListener;
     import io.jenkins.blueocean.commons.ServiceException;
     
    +import java.io.File;
     import java.io.IOException;
    +import java.nio.file.Files;
     import java.util.Date;
     import java.util.TimeZone;
     import java.util.logging.Level;
    +import java.util.logging.Logger;
     
    +import jenkins.model.Jenkins;
     import jenkins.plugins.git.AbstractGitSCMSource;
     import jenkins.plugins.git.GitSCMFileSystem;
    +import jenkins.plugins.git.traits.GitToolSCMSourceTrait;
    +import jenkins.scm.api.SCMFileSystem;
    +import jenkins.scm.api.SCMHead;
    +import jenkins.scm.api.trait.SCMSourceTrait;
    +import org.apache.commons.io.FileUtils;
     import org.eclipse.jgit.lib.ObjectId;
     import org.eclipse.jgit.lib.RefUpdate;
     import org.eclipse.jgit.lib.Repository;
    +import org.jenkinsci.plugins.gitclient.GitClient;
    +import org.jenkinsci.plugins.gitclient.RepositoryCallback;
     
     /**
      * Uses the SCM Git cache operating on the bare repository to load/save content
      * "as efficiently as possible"
      * @author kzantow
      */
    -class GitBareRepoReadSaveRequest extends GitCacheCloneReadSaveRequest {
    +class GitBareRepoReadSaveRequest extends GitReadSaveRequest {
         private static final String LOCAL_REF_BASE = "refs/remotes/origin/";
         private static final String REMOTE_REF_BASE = "refs/heads/";
     
    +    private final File repositoryPath;
    +    private final GitTool gitTool;
    +
         GitBareRepoReadSaveRequest(AbstractGitSCMSource gitSource, String branch, String commitMessage, String sourceBranch, String filePath, byte[] contents) {
             super(gitSource, branch, commitMessage, sourceBranch, filePath, contents);
    +        GitTool.DescriptorImpl toolDesc = Jenkins.get().getDescriptorByType( GitTool.DescriptorImpl.class);
    +        @SuppressWarnings("deprecation")
    +        GitTool foundGitTool = null;
    +        if (gitSource != null) {
    +            for ( SCMSourceTrait trait : gitSource.getTraits()) {
    +                if (trait instanceof GitToolSCMSourceTrait ) {
    +                    foundGitTool = toolDesc.getInstallation(((GitToolSCMSourceTrait) trait).getGitTool());
    +                }
    +            }
    +        }
    +
    +        if (foundGitTool == null) {
    +            foundGitTool = GitTool.getDefaultInstallation();
    +        }
    +
    +        this.gitTool = foundGitTool;
    +        try {
    +            repositoryPath = Files.createTempDirectory( "git").toFile();
    +        } catch (IOException e) {
    +            throw new ServiceException.UnexpectedErrorException("Unable to create working directory for repository clone");
    +        }
         }
     
         @Override
    @@ -111,4 +152,104 @@ public Void invoke(Repository repo) throws IOException, InterruptedException {
                 }
             });
         }
    +
    +    <T> T invokeOnScm(final GitSCMFileSystem.FSFunction<T> function) throws IOException {
    +        try {
    +            GitSCMFileSystem fs = getFilesystem();
    +            if (fs == null) {
    +                // Fall back to a git clone if we can't get the repository filesystem
    +                GitClient git = cloneRepo();
    +                try {
    +                    return git.withRepository(new RepositoryCallbackToFSFunctionAdapter<>( function));
    +                } finally {
    +                    cleanupRepo();
    +                }
    +            }
    +            return fs.invoke(function);
    +        } catch (InterruptedException ex) {
    +            throw new ServiceException.UnexpectedErrorException("Unable to save " + filePath, ex);
    +        }
    +    }
    +
    +    private GitSCMFileSystem getFilesystem() throws IOException, InterruptedException {
    +        try {
    +            return (GitSCMFileSystem) SCMFileSystem.of( gitSource, new SCMHead( sourceBranch));
    +        } catch(NullPointerException e) {
    +            // If the repository is totally empty with no commits, it
    +            // results in a NPE during the SCMFileSystem.of call
    +            return null;
    +        } catch( GitException e) {
    +            // TODO localization?
    +            if (e.getMessage().contains("Permission denied")) {
    +                throw new ServiceException.UnauthorizedException("Not authorized", e);
    +            }
    +            throw e;
    +        }
    +    }
    +
    +    GitClient cloneRepo() throws InterruptedException, IOException {
    +        EnvVars environment = new EnvVars();
    +        TaskListener taskListener = new LogTaskListener( Logger.getAnonymousLogger(), Level.ALL);
    +        String gitExe = gitTool.getGitExe();
    +        GitClient git = org.jenkinsci.plugins.gitclient.Git.with( taskListener, environment)
    +            .in(repositoryPath)
    +            .using(gitExe)
    +            .getClient();
    +
    +        git.addCredentials(gitSource.getRemote(), getCredential());
    +
    +        try {
    +            git.clone(gitSource.getRemote(), "origin", true, null);
    +
    +            log.fine("Repository " + gitSource.getRemote() + " cloned to: " + repositoryPath.getCanonicalPath());
    +        } catch(GitException e) {
    +            // check if this is an empty repository
    +            boolean isEmptyRepo = false;
    +            try {
    +                if (git.getRemoteReferences(gitSource.getRemote(), null, true, false).isEmpty()) {
    +                    isEmptyRepo = true;
    +                }
    +            } catch(GitException ge) {
    +                // *sigh* @ this necessary hack; {@link org.jenkinsci.plugins.gitclient.CliGitAPIImpl#getRemoteReferences}
    +                if ("unexpected ls-remote output ".equals(ge.getMessage())) { // blank line, command succeeded
    +                    isEmptyRepo = true;
    +                }
    +                // ignore other reasons
    +            }
    +
    +            if(isEmptyRepo) {
    +                git.init();
    +                git.addRemoteUrl("origin", gitSource.getRemote());
    +
    +                log.fine("Repository " + gitSource.getRemote() + " not found, created new to: " + repositoryPath.getCanonicalPath());
    +            } else {
    +                throw e;
    +            }
    +        }
    +
    +        return git;
    +    }
    +
    +    void cleanupRepo() {
    +        try {
    +            FileUtils.deleteDirectory( repositoryPath);
    +        } catch (IOException e) {
    +            throw new RuntimeException(e);
    +        }
    +    }
    +
    +    static class RepositoryCallbackToFSFunctionAdapter<T> implements RepositoryCallback<T>
    +    {
    +        private final GitSCMFileSystem.FSFunction<T> function;
    +
    +        public RepositoryCallbackToFSFunctionAdapter(GitSCMFileSystem.FSFunction<T> function) {
    +            this.function = function;
    +        }
    +
    +        @Override
    +        public T invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
    +            return function.invoke(repo);
    +        }
    +    }
    +
     }
    
  • blueocean-git-pipeline/src/main/java/io/jenkins/blueocean/blueocean_git_pipeline/GitCacheCloneReadSaveRequest.java+0 266 removed
    @@ -1,266 +0,0 @@
    -/*
    - * The MIT License
    - *
    - * Copyright (c) 2017, CloudBees, Inc.
    - *
    - * Permission is hereby granted, free of charge, to any person obtaining a copy
    - * of this software and associated documentation files (the "Software"), to deal
    - * in the Software without restriction, including without limitation the rights
    - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    - * copies of the Software, and to permit persons to whom the Software is
    - * furnished to do so, subject to the following conditions:
    - *
    - * The above copyright notice and this permission notice shall be included in
    - * all copies or substantial portions of the Software.
    - *
    - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    - * THE SOFTWARE.
    - */
    -package io.jenkins.blueocean.blueocean_git_pipeline;
    -
    -import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey;
    -import hudson.model.User;
    -import hudson.plugins.git.GitException;
    -import hudson.remoting.VirtualChannel;
    -import io.jenkins.blueocean.commons.ServiceException;
    -import io.jenkins.blueocean.ssh.UserSSHKeyManager;
    -import jenkins.plugins.git.AbstractGitSCMSource;
    -import jenkins.plugins.git.GitSCMFileSystem;
    -import jenkins.scm.api.SCMFileSystem;
    -import jenkins.scm.api.SCMHead;
    -import org.apache.commons.io.FileUtils;
    -import org.apache.commons.io.IOUtils;
    -import org.apache.commons.lang.StringUtils;
    -import org.eclipse.jgit.api.AddCommand;
    -import org.eclipse.jgit.api.CheckoutCommand;
    -import org.eclipse.jgit.api.CommitCommand;
    -import org.eclipse.jgit.api.CreateBranchCommand;
    -import org.eclipse.jgit.api.FetchCommand;
    -import org.eclipse.jgit.api.Git;
    -import org.eclipse.jgit.api.RemoteAddCommand;
    -import org.eclipse.jgit.api.RemoteRemoveCommand;
    -import org.eclipse.jgit.api.errors.GitAPIException;
    -import org.eclipse.jgit.lib.Repository;
    -import org.eclipse.jgit.transport.URIish;
    -import org.jenkinsci.plugins.gitclient.GitClient;
    -import org.jenkinsci.plugins.gitclient.RepositoryCallback;
    -
    -import javax.annotation.Nonnull;
    -import java.io.File;
    -import java.io.FileInputStream;
    -import java.io.FileOutputStream;
    -import java.io.IOException;
    -import java.io.OutputStreamWriter;
    -import java.io.Writer;
    -import java.net.URISyntaxException;
    -
    -/**
    - * Uses the SCM Git cache with a local clone to load/save content
    - * @author kzantow
    - */
    -class GitCacheCloneReadSaveRequest extends GitReadSaveRequest {
    -    private static final String LOCAL_REF_BASE = "refs/heads/";
    -    private static final String REMOTE_REF_BASE = "refs/heads/";
    -
    -    public GitCacheCloneReadSaveRequest(AbstractGitSCMSource gitSource, String branch, String commitMessage, String sourceBranch, String filePath, byte[] contents) {
    -        super(gitSource, branch, commitMessage, sourceBranch, filePath, contents);
    -    }
    -
    -    @Override
    -    byte[] read() throws IOException {
    -        return invokeOnScm(new GitSCMFileSystem.FSFunction<byte[]>() {
    -            @Override
    -            public byte[] invoke(Repository repository) throws IOException, InterruptedException {
    -                Git activeRepo = getActiveRepository(repository);
    -                Repository repo = activeRepo.getRepository();
    -                File repoDir = repo.getDirectory().getParentFile();
    -                FileInputStream fis = null;
    -                try {
    -                    File f = new File(repoDir, filePath);
    -                    if (f.canRead()) {
    -                        fis = new FileInputStream(f);
    -                        return IOUtils.toByteArray(fis);
    -                    }
    -                    return null;
    -                } finally {
    -                    if (fis != null) {
    -                        fis.close();
    -                    }
    -                    FileUtils.deleteDirectory(repoDir);
    -                }
    -            }
    -        });
    -    }
    -
    -    @Override
    -    void save() throws IOException {
    -        invokeOnScm(new GitSCMFileSystem.FSFunction<Void>() {
    -            @Override
    -            public Void invoke(Repository repository) throws IOException, InterruptedException {
    -                Git activeRepo = getActiveRepository(repository);
    -                Repository repo = activeRepo.getRepository();
    -                File repoDir = repo.getDirectory().getParentFile();
    -                log.fine("Repo cloned to: " + repoDir.getCanonicalPath());
    -                try {
    -                    File f = new File(repoDir, filePath);
    -                    if (!f.exists() || f.canWrite()) {
    -                        try (Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8")) {
    -                            w.write(new String(contents, "utf-8"));
    -                        }
    -
    -                        try {
    -                            AddCommand add = activeRepo.add();
    -                            add.addFilepattern(filePath);
    -                            add.call();
    -
    -                            CommitCommand commit = activeRepo.commit();
    -                            commit.setMessage(commitMessage);
    -                            commit.call();
    -
    -                            // Push the changes
    -                            GitUtils.push(gitSource.getRemote(), repo, getCredential(), LOCAL_REF_BASE + sourceBranch, REMOTE_REF_BASE + branch);
    -                        } catch (GitAPIException ex) {
    -                            throw new ServiceException.UnexpectedErrorException(ex.getMessage(), ex);
    -                        }
    -
    -                        return null;
    -                    }
    -                    throw new ServiceException.UnexpectedErrorException("Unable to write " + filePath);
    -                } finally {
    -                    FileUtils.deleteDirectory(repoDir);
    -                }
    -            }
    -        });
    -    }
    -
    -    <T> T invokeOnScm(final GitSCMFileSystem.FSFunction<T> function) throws IOException {
    -        try {
    -            GitSCMFileSystem fs = getFilesystem();
    -            if (fs == null) {
    -                // Fall back to a git clone if we can't get the repository filesystem
    -                GitCloneReadSaveRequest gitClone = new GitCloneReadSaveRequest(gitSource, branch, commitMessage, sourceBranch, filePath, contents);
    -                GitClient git = gitClone.cloneRepo();
    -                try {
    -                    return git.withRepository(new RepositoryCallbackToFSFunctionAdapter<>(function));
    -                } finally {
    -                    gitClone.cleanupRepo();
    -                }
    -            }
    -            return fs.invoke(function);
    -        } catch (InterruptedException ex) {
    -            throw new ServiceException.UnexpectedErrorException("Unable to save " + filePath, ex);
    -        }
    -    }
    -
    -    private GitSCMFileSystem getFilesystem() throws IOException, InterruptedException {
    -        try {
    -            return (GitSCMFileSystem) SCMFileSystem.of(gitSource, new SCMHead(sourceBranch));
    -        } catch(NullPointerException e) {
    -            // If the repository is totally empty with no commits, it
    -            // results in a NPE during the SCMFileSystem.of call
    -            return null;
    -        } catch(GitException e) {
    -            // TODO localization?
    -            if (e.getMessage().contains("Permission denied")) {
    -                throw new ServiceException.UnauthorizedException("Not authorized", e);
    -            }
    -            throw e;
    -        }
    -    }
    -
    -    private @Nonnull Git getActiveRepository(Repository repository) throws IOException {
    -        try {
    -            // Clone the bare repository
    -            File cloneDir = File.createTempFile("clone", "");
    -
    -            if (cloneDir.exists()) {
    -                if (cloneDir.isDirectory()) {
    -                    FileUtils.deleteDirectory(cloneDir);
    -                } else {
    -                    if (!cloneDir.delete()) {
    -                        throw new ServiceException.UnexpectedErrorException("Unable to delete repository clone");
    -                    }
    -                }
    -            }
    -            if (!cloneDir.mkdirs()) {
    -                throw new ServiceException.UnexpectedErrorException("Unable to create repository clone directory");
    -            }
    -
    -            String url = repository.getConfig().getString( "remote", "origin", "url" );
    -            Git gitClient = Git.cloneRepository()
    -                .setCloneAllBranches(false)
    -                .setProgressMonitor(new CloneProgressMonitor(url))
    -                .setURI(repository.getDirectory().getCanonicalPath())
    -                .setDirectory(cloneDir)
    -                .call();
    -
    -            RemoteRemoveCommand remove = gitClient.remoteRemove();
    -            remove.setName("origin");
    -            remove.call();
    -
    -            RemoteAddCommand add = gitClient.remoteAdd();
    -            add.setName("origin");
    -            add.setUri(new URIish(gitSource.getRemote()));
    -            add.call();
    -
    -            if (GitUtils.isSshUrl(gitSource.getRemote())) {
    -                // Get committer info and credentials
    -                User user = User.current();
    -                if (user == null) {
    -                    throw new ServiceException.UnauthorizedException("Not authenticated");
    -                }
    -                BasicSSHUserPrivateKey privateKey = UserSSHKeyManager.getOrCreate(user);
    -
    -                // Make sure up-to-date and credentials work
    -                GitUtils.fetch(repository, privateKey);
    -            } else {
    -                FetchCommand fetch = gitClient.fetch();
    -                fetch.call();
    -            }
    -
    -            if (!StringUtils.isEmpty(sourceBranch) && !sourceBranch.equals(branch)) {
    -                CheckoutCommand checkout = gitClient.checkout();
    -                checkout.setStartPoint("origin/" + sourceBranch);
    -                checkout.setName(sourceBranch);
    -                checkout.setCreateBranch(true); // to create a new local branch
    -                checkout.setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.NOTRACK);
    -                checkout.call();
    -
    -                checkout = gitClient.checkout();
    -                checkout.setName(branch);
    -                checkout.setCreateBranch(true); // this *should* be a new branch
    -                checkout.setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.NOTRACK);
    -                checkout.call();
    -            } else {
    -                CheckoutCommand checkout = gitClient.checkout();
    -                checkout.setStartPoint("origin/" + branch);
    -                checkout.setName(branch);
    -                checkout.setCreateBranch(true); // to create a new local branch
    -                checkout.setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.NOTRACK);
    -                checkout.call();
    -            }
    -
    -            return gitClient;
    -        } catch (GitAPIException | URISyntaxException ex) {
    -            throw new ServiceException.UnexpectedErrorException("Unable to get working repository directory", ex);
    -        }
    -    }
    -
    -    static class RepositoryCallbackToFSFunctionAdapter<T> implements RepositoryCallback<T> {
    -        private final GitSCMFileSystem.FSFunction<T> function;
    -
    -        public RepositoryCallbackToFSFunctionAdapter(GitSCMFileSystem.FSFunction<T> function) {
    -            this.function = function;
    -        }
    -
    -        @Override
    -        public T invoke(Repository repo, VirtualChannel channel) throws IOException, InterruptedException {
    -            return function.invoke(repo);
    -        }
    -    }
    -}
    
  • blueocean-git-pipeline/src/main/java/io/jenkins/blueocean/blueocean_git_pipeline/GitCloneReadSaveRequest.java+0 181 removed
    @@ -1,181 +0,0 @@
    -/*
    - * The MIT License
    - *
    - * Copyright (c) 2017, CloudBees, Inc.
    - *
    - * Permission is hereby granted, free of charge, to any person obtaining a copy
    - * of this software and associated documentation files (the "Software"), to deal
    - * in the Software without restriction, including without limitation the rights
    - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    - * copies of the Software, and to permit persons to whom the Software is
    - * furnished to do so, subject to the following conditions:
    - *
    - * The above copyright notice and this permission notice shall be included in
    - * all copies or substantial portions of the Software.
    - *
    - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    - * THE SOFTWARE.
    - */
    -package io.jenkins.blueocean.blueocean_git_pipeline;
    -
    -import hudson.EnvVars;
    -import hudson.model.TaskListener;
    -import hudson.plugins.git.GitException;
    -import hudson.plugins.git.GitTool;
    -import hudson.util.LogTaskListener;
    -import io.jenkins.blueocean.commons.ServiceException;
    -import java.io.File;
    -import java.io.IOException;
    -import java.net.URISyntaxException;
    -import java.nio.file.Files;
    -import java.util.Arrays;
    -import java.util.logging.Level;
    -import java.util.logging.Logger;
    -
    -import jenkins.model.Jenkins;
    -import jenkins.plugins.git.AbstractGitSCMSource;
    -import jenkins.plugins.git.traits.GitToolSCMSourceTrait;
    -import jenkins.scm.api.trait.SCMSourceTrait;
    -import org.apache.commons.io.FileUtils;
    -import org.eclipse.jgit.transport.URIish;
    -import org.jenkinsci.plugins.gitclient.Git;
    -import org.jenkinsci.plugins.gitclient.GitClient;
    -
    -/**
    - * Basic functional read/save using a clone of the remote
    - * @author kzantow
    - */
    -class GitCloneReadSaveRequest extends GitReadSaveRequest {
    -    private final File repositoryPath;
    -    private final GitTool gitTool;
    -
    -    public GitCloneReadSaveRequest(AbstractGitSCMSource gitSource, String branch, String commitMessage, String sourceBranch, String filePath, byte[] contents) {
    -        super(gitSource, branch, commitMessage, sourceBranch, filePath, contents);
    -
    -        GitTool.DescriptorImpl toolDesc = Jenkins.getInstance().getDescriptorByType(GitTool.DescriptorImpl.class);
    -        @SuppressWarnings("deprecation")
    -        GitTool foundGitTool = null;
    -        for (SCMSourceTrait trait : gitSource.getTraits()) {
    -            if (trait instanceof GitToolSCMSourceTrait) {
    -                foundGitTool = toolDesc.getInstallation(((GitToolSCMSourceTrait) trait).getGitTool());
    -            }
    -        }
    -        if (foundGitTool == null) {
    -            foundGitTool = GitTool.getDefaultInstallation();
    -        }
    -
    -        this.gitTool = foundGitTool;
    -        try {
    -            repositoryPath = Files.createTempDirectory("git").toFile();
    -        } catch (IOException e) {
    -            throw new ServiceException.UnexpectedErrorException("Unable to create working directory for repository clone");
    -        }
    -    }
    -
    -    GitClient cloneRepo() throws InterruptedException, IOException {
    -        EnvVars environment = new EnvVars();
    -        TaskListener taskListener = new LogTaskListener(Logger.getAnonymousLogger(), Level.ALL);
    -        String gitExe = gitTool.getGitExe();
    -        GitClient git = Git.with(taskListener, environment)
    -                .in(repositoryPath)
    -                .using(gitExe)
    -                .getClient();
    -
    -        git.addCredentials(gitSource.getRemote(), getCredential());
    -
    -        try {
    -            git.clone(gitSource.getRemote(), "origin", true, null);
    -
    -            log.fine("Repository " + gitSource.getRemote() + " cloned to: " + repositoryPath.getCanonicalPath());
    -        } catch(GitException e) {
    -            // check if this is an empty repository
    -            boolean isEmptyRepo = false;
    -            try {
    -                if (git.getRemoteReferences(gitSource.getRemote(), null, true, false).isEmpty()) {
    -                    isEmptyRepo = true;
    -                }
    -            } catch(GitException ge) {
    -                // *sigh* @ this necessary hack; {@link org.jenkinsci.plugins.gitclient.CliGitAPIImpl#getRemoteReferences}
    -                if ("unexpected ls-remote output ".equals(ge.getMessage())) { // blank line, command succeeded
    -                    isEmptyRepo = true;
    -                }
    -                // ignore other reasons
    -            }
    -
    -            if(isEmptyRepo) {
    -                git.init();
    -                git.addRemoteUrl("origin", gitSource.getRemote());
    -
    -                log.fine("Repository " + gitSource.getRemote() + " not found, created new to: " + repositoryPath.getCanonicalPath());
    -            } else {
    -                throw e;
    -            }
    -        }
    -
    -        return git;
    -    }
    -
    -    @Override
    -    byte[] read() throws IOException {
    -        try {
    -            GitClient git = cloneRepo();
    -            try {
    -                // thank you test for how to use something...
    -                // https://github.com/jenkinsci/git-client-plugin/blob/master/src/test/java/org/jenkinsci/plugins/gitclient/GitClientTest.java#L1108
    -                git.checkoutBranch(branch, "origin/" + branch);
    -            } catch(Exception e) {
    -                throw new RuntimeException("Branch not found: " + branch);
    -            }
    -            File f = new File(repositoryPath, filePath);
    -            if (f.canRead()) {
    -                return FileUtils.readFileToByteArray(f);
    -            }
    -            return null;
    -        } catch (InterruptedException ex) {
    -            throw new ServiceException.UnexpectedErrorException("Unable to read " + filePath, ex);
    -        } finally {
    -            cleanupRepo();
    -        }
    -    }
    -
    -    @Override
    -    void save() throws IOException {
    -        try {
    -            GitClient git = cloneRepo();
    -            try {
    -                git.checkoutBranch(sourceBranch, "origin/" + sourceBranch);
    -            } catch(Exception e) {
    -                throw new RuntimeException("Branch not found: " + sourceBranch);
    -            }
    -            if (!sourceBranch.equals(branch)) {
    -                //git.branch(branch);
    -                git.checkoutBranch(branch, "origin/" + sourceBranch);
    -            }
    -            File f = new File(repositoryPath, filePath);
    -            // commit will fail if the contents hasn't changed
    -            if (!f.exists() || !Arrays.equals(FileUtils.readFileToByteArray(f), contents)) {
    -                FileUtils.writeByteArrayToFile(f, contents);
    -                git.add(filePath);
    -                git.commit(commitMessage);
    -            }
    -            git.push().ref(branch).to(new URIish(gitSource.getRemote())).execute();
    -        } catch (InterruptedException | GitException | URISyntaxException ex) {
    -            throw new ServiceException.UnexpectedErrorException("Unable to save " + filePath, ex);
    -        } finally {
    -            cleanupRepo();
    -        }
    -    }
    -
    -    void cleanupRepo() {
    -        try {
    -            FileUtils.deleteDirectory(repositoryPath);
    -        } catch (IOException e) {
    -            throw new RuntimeException(e);
    -        }
    -    }
    -}
    
  • blueocean-git-pipeline/src/main/java/io/jenkins/blueocean/blueocean_git_pipeline/GitReadSaveRequest.java+0 1 modified
    @@ -24,7 +24,6 @@
     package io.jenkins.blueocean.blueocean_git_pipeline;
     
     import com.cloudbees.plugins.credentials.common.StandardCredentials;
    -import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
     import hudson.model.User;
     import io.jenkins.blueocean.commons.ServiceException;
     import io.jenkins.blueocean.credential.CredentialsUtils;
    
  • blueocean-git-pipeline/src/main/java/io/jenkins/blueocean/blueocean_git_pipeline/GitReadSaveService.java+19 31 modified
    @@ -35,6 +35,7 @@
     import io.jenkins.blueocean.rest.impl.pipeline.scm.GitContent;
     
     import java.io.IOException;
    +import java.util.Locale;
     import javax.annotation.Nonnull;
     
     import jenkins.branch.MultiBranchProject;
    @@ -45,12 +46,16 @@
     import org.apache.commons.lang.StringUtils;
     import org.apache.commons.lang3.ObjectUtils;
     import org.kohsuke.stapler.StaplerRequest;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     /**
      * Content provider for load/save with git repositories
      */
     @Extension
     public class GitReadSaveService extends ScmContentProvider {
    +
    +    private static final Logger logger = LoggerFactory.getLogger(GitReadSaveService.class);
         @Nonnull
         private static ReadSaveType TYPE = ReadSaveType.DEFAULT;
     
    @@ -66,9 +71,12 @@ public enum ReadSaveType {
     
             static ReadSaveType get(String type) {
                 if (type != null) {
    -                return ReadSaveType.valueOf(type.toUpperCase());
    +                ReadSaveType readSaveType = ReadSaveType.valueOf(type.toUpperCase(Locale.ENGLISH));
    +                if(readSaveType != CACHE_BARE){
    +                    logger.warn( "CLONE/CACHE_CLONE options are not supported anymore. Using default option CACHE_BARE instead." );
    +                }
                 }
    -            return DEFAULT == null ? ReadSaveType.CACHE_BARE : DEFAULT;
    +            return DEFAULT == null ? CACHE_BARE : DEFAULT;
             }
         }
     
    @@ -109,35 +117,15 @@ static GitReadSaveRequest makeSaveRequest(
                 }
             }
     
    -        switch (TYPE) {
    -            case CLONE:
    -                return new GitCloneReadSaveRequest(
    -                    gitSource,
    -                    StringUtils.defaultIfEmpty(branch, defaultBranch),
    -                    commitMessage,
    -                    StringUtils.defaultIfEmpty(sourceBranch, defaultBranch),
    -                    filePath,
    -                    contents
    -                );
    -            case CACHE_CLONE:
    -                return new GitCacheCloneReadSaveRequest(
    -                    gitSource,
    -                    StringUtils.defaultIfEmpty(branch, defaultBranch),
    -                    commitMessage,
    -                    StringUtils.defaultIfEmpty(sourceBranch, defaultBranch),
    -                    filePath,
    -                    contents
    -                );
    -            default:
    -                return new GitBareRepoReadSaveRequest(
    -                    gitSource,
    -                    StringUtils.defaultIfEmpty(branch, defaultBranch),
    -                    commitMessage,
    -                    StringUtils.defaultIfEmpty(sourceBranch, defaultBranch),
    -                    filePath,
    -                    contents
    -                );
    -        }
    +        return new GitBareRepoReadSaveRequest(
    +            gitSource,
    +            StringUtils.defaultIfEmpty(branch, defaultBranch),
    +            commitMessage,
    +            StringUtils.defaultIfEmpty(sourceBranch, defaultBranch),
    +            filePath,
    +            contents
    +        );
    +
         }
     
         private GitReadSaveRequest makeSaveRequest(Item item, StaplerRequest req) {
    
  • blueocean-git-pipeline/src/test/java/io/jenkins/blueocean/blueocean_git_pipeline/GitReadSaveTest.java+1 13 modified
    @@ -191,7 +191,7 @@ public void stopSSHServer() throws InterruptedException, IOException {
         @Test
         public void testRepositoryCallbackToFSFunctionAdapter() throws IOException, InterruptedException {
             final boolean[] called = { false };
    -        new GitCacheCloneReadSaveRequest.RepositoryCallbackToFSFunctionAdapter<>(new GitSCMFileSystem.FSFunction<Object>() {
    +        new GitBareRepoReadSaveRequest.RepositoryCallbackToFSFunctionAdapter<>(new GitSCMFileSystem.FSFunction<Object>() {
                 @Override
                 public Object invoke(Repository repository) throws IOException, InterruptedException {
                     called[0] = true;
    @@ -201,18 +201,6 @@ public Object invoke(Repository repository) throws IOException, InterruptedExcep
             Assert.assertTrue(called[0]);
         }
     
    -    @Test
    -    public void testGitCloneReadWrite() throws Exception {
    -        testGitReadWrite(GitReadSaveService.ReadSaveType.CLONE, repoWithJenkinsfiles, masterPipelineScript);
    -        testGitReadWrite(GitReadSaveService.ReadSaveType.CLONE, repoNoJenkinsfile, null);
    -    }
    -
    -    @Test
    -    public void testGitCacheCloneReadWrite() throws Exception {
    -        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_CLONE, repoWithJenkinsfiles, masterPipelineScript);
    -        testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_CLONE, repoNoJenkinsfile, null);
    -    }
    -
         @Test
         public void testBareRepoReadWrite() throws Exception {
             testGitReadWrite(GitReadSaveService.ReadSaveType.CACHE_BARE, repoWithJenkinsfiles, masterPipelineScript);
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

1