Skip to content

Conversation

@vinokurig
Copy link
Contributor

What does this PR do?

  • Fetch list of the repository's branches by executing git ls-remote <repo url>
  • Reveal the Git branches list to the dropdown in the Git Branch section:
Screenshot 2025-11-27 at 12 05 46

Screenshot/screencast of this PR

What issues does this PR fix or reference?

fixes eclipse-che/che#23171

Is it tested? How?

  1. Deploy che with the pull request image: quay.io/eclipse/che-dashboard:pr-
  2. Paste a Git repository URL to the Import from Git section.
  3. Go to Git Repo Options and click the Git Branch dropdown, see: Git branches list is revealed.
  4. Choose a Git branch from the list, see: the git branch is applied to the repository URL.
  5. Edit the repository URL to be invalid e.g. https://github.com/vinokurig/t, see: empty branch list warning is shown:
Screenshot 2025-11-27 at 12 10 55

Release Notes

Docs PR

@openshift-ci
Copy link

openshift-ci bot commented Nov 27, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: vinokurig

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@che-bot
Copy link
Contributor

che-bot commented Nov 27, 2025

Click here to review and test in web IDE: Contribute

export async function getBranches(url: string): Promise<api.IGitBranches | undefined> {
try {
return new Promise((resolve, reject) => {
run(`git`, ['ls-remote', '--refs', url], 1000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vinokurig please sanitize the url to prevent injections.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to do anything with URLs here; moreover, modifying the URL here can add some edge cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best approach to protect backend just to validate the target URL with provider patterns:

/packages/dashboard-frontend/src/store/Workspaces/Preferences/helpers.ts

export const gitProviderPatterns = {
  github: {
    https: /^https:\/\/github\.com\/([^\\/]+\/[^\\/]+)(?:\/.*)?$/,
    ssh: /^(?:(?:git\+)?ssh:\/\/)?git@github\.com:([^\\/]+\/[^\\/]+?)(?:\.git)?$/,
  },
  gitlab: {
    https: /^https:\/\/gitlab\.com\/([^\\/]+\/[^\\/]+)(?:\/.*)?$/,
    ssh: /^(?:(?:git\+)?ssh:\/\/)?git@gitlab\.com:([^\\/]+\/[^\\/]+?)(?:\.git)?$/,
  },
  bitbucket: {
    https: /^https:\/\/bitbucket\.org\/([^\\/]+\/[^\\/]+)(?:\/.*)?$/,
    ssh: /^(?:(?:git\+)?ssh:\/\/)?git@bitbucket\.org:([^\\/]+\/[^\\/]+?)(?:\.git)?$/,
  },
  azureDevOps: {
    https: /^https:\/\/(?:\w+@)?dev\.azure\.com\/([^\\/]+\/[^\\/]+\/_git\/[^\\/]+?)(?:\?.*)?$/,
    ssh: /^(?:ssh:\/\/)?git@ssh\.dev\.azure\.com:v3\/([^\\/]+\/[^\\/]+\/[^\\/]+)(?:\/[^\\/]+)?$/,
  },
};

If it is valid - execute code. If not - return the proper error.

@akurinnoy WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could also add a time-limited cashing option here. Since multiple users may be requesting the same repository(It is optional)

Copy link
Contributor Author

@vinokurig vinokurig Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this we will limit the functionality to just 4 supported git providers, server version of supported git providers will not be included. On the other hand, this will protect the dashboard container from executing git ls-remote with suspicious urls.
My proposal is to add a simple http https ssh validation and probably a character quantity limit.
@akurinnoy @svor We need to make a decision here: limited functionality or security protection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have to support GitLab and Bitbucket servers as well. This providers may have custom host URLs, I'm not sure if the pattern provided above will work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olexii4 @vinokurig @svor I would also prefer more general URL validation to exclude possible injections.

@olexii4 Regarding the server-side caching, I would rather disagree, because this will introduce security issues. But, the client-side cashing sounds reasonable to me.

@vinokurig vinokurig force-pushed the che-23171 branch 2 times, most recently from 04f74f5 to f8826a9 Compare November 28, 2025 15:03
@github-actions
Copy link

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1416

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1416", name: che-dashboard}]}}]"

@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 84.75452% with 59 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.14%. Comparing base (239203a) to head (2f4b92c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...d-frontend/src/components/ImportFromGit/helpers.ts 50.00% 18 Missing ⚠️
...tend/src/services/backend-client/gitBranchesApi.ts 79.31% 16 Missing and 2 partials ⚠️
...nd/src/devworkspaceClient/services/helpers/exec.ts 10.52% 17 Missing ⚠️
.../dashboard-backend/src/services/gitClient/index.ts 91.17% 3 Missing ⚠️
...cordion/GitRepoOptions/GitBranchDropdown/index.tsx 97.43% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1416      +/-   ##
==========================================
- Coverage   92.18%   92.14%   -0.04%     
==========================================
  Files         511      514       +3     
  Lines       47614    47926     +312     
  Branches     3432     3472      +40     
==========================================
+ Hits        43891    44161     +270     
- Misses       3682     3726      +44     
+ Partials       41       39       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

export async function getBranches(url: string): Promise<api.IGitBranches | undefined> {
try {
return new Promise((resolve, reject) => {
run(`git`, ['ls-remote', '--refs', url], 1000)

Check failure

Code scanning / CodeQL

Second order command injection High

Command line argument that depends on
a user-provided value
can execute an arbitrary command if --upload-pack is used with git.

Copilot Autofix

AI 3 days ago

The safest and most effective fix is to validate the url argument in the getBranches function before invoking the run command. This ensures that it cannot begin with a dash (i.e., -) or contain a string that would cause it to be interpreted as a git option rather than a repository address. The validation should accept only valid Git remote URLs (e.g., those beginning with git@, http://, https://, ssh://, or that conform to accepted address formats), and reject any value that could be used for command injection.
The change should be made directly inside the getBranches function, as that is the main sink for the tainted value.
The fix requires either a simple check (rejecting any value that starts with a dash), or a more rigorous check (accepting only URLs that match expected patterns).


Suggested changeset 1
packages/dashboard-backend/src/services/gitClient/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/dashboard-backend/src/services/gitClient/index.ts b/packages/dashboard-backend/src/services/gitClient/index.ts
--- a/packages/dashboard-backend/src/services/gitClient/index.ts
+++ b/packages/dashboard-backend/src/services/gitClient/index.ts
@@ -15,6 +15,15 @@
 import { run } from '@/devworkspaceClient/services/helpers/exec';
 
 export async function getBranches(url: string): Promise<api.IGitBranches | undefined> {
+  // Disallow remote URLs that start with "-" (prevent git option injection)
+  // Optionally, only allow typical git URLs: git@, http(s)://, ssh://
+  if (
+    typeof url !== 'string' ||
+    url.startsWith('-') ||
+    !(url.startsWith('git@') || url.startsWith('https://') || url.startsWith('http://') || url.startsWith('ssh://'))
+  ) {
+    throw new Error('Invalid remote URL: ' + url);
+  }
   try {
     return new Promise((resolve, reject) => {
       run(`git`, ['ls-remote', '--refs', url], 1000)
EOF
@@ -15,6 +15,15 @@
import { run } from '@/devworkspaceClient/services/helpers/exec';

export async function getBranches(url: string): Promise<api.IGitBranches | undefined> {
// Disallow remote URLs that start with "-" (prevent git option injection)
// Optionally, only allow typical git URLs: git@, http(s)://, ssh://
if (
typeof url !== 'string' ||
url.startsWith('-') ||
!(url.startsWith('git@') || url.startsWith('https://') || url.startsWith('http://') || url.startsWith('ssh://'))
) {
throw new Error('Invalid remote URL: ' + url);
}
try {
return new Promise((resolve, reject) => {
run(`git`, ['ls-remote', '--refs', url], 1000)
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Docker image build succeeded: quay.io/eclipse/che-dashboard:pr-1416

kubectl patch command
kubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/dashboard/deployment", "value": {containers: [{image: "quay.io/eclipse/che-dashboard:pr-1416", name: che-dashboard}]}}]"

@svor svor self-requested a review December 3, 2025 11:53
FROM docker.io/node:18.19.1-alpine3.19

RUN apk --no-cache add curl
RUN apk --no-cache add curl git
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don’t forget to include Git in the downstream image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 128 to 155
export function run(cmd: string, args?: string[], timeOut?: number): Promise<string> {
let command: ChildProcessWithoutNullStreams;
const promise: Promise<string> = new Promise((resolve, reject) => {
command = spawn(cmd, args);
let result = '';
command.stdout.on('data', data => {
result += data.toString();
});
command.on('close', () => {
resolve(result.replace(/\n/g, ' ').trim());
});
command.on('error', err => {
reject(err);
});
});
if (timeOut && timeOut > 0) {
const timeoutPromise: Promise<string> = new Promise((_, reject) => {
setTimeout(() => {
if (command && command.exitCode == null) {
command.kill();
reject(new Error('Timeout exceeded'));
}
}, timeOut);
});
return Promise.race([promise, timeoutPromise]);
} else {
return promise;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use async/await for better readability and maintainability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to define the promises explicitly for the Promise.race function.

Comment on lines +20 to +34
run(`git`, ['ls-remote', '--refs', url], 1000)
.then(result => {
resolve({
branches: result
.split(' ')
.filter(b => b.indexOf('refs/heads/') > 0 || b.indexOf('refs/tags/') > 0)
.map(b => b.replace(new RegExp('.*\\trefs/((heads)|(tags))/'), '')),
});
})
.catch(err => reject(err));
});
} catch (error) {
return undefined;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use async/await for better readability and maintainability.

Comment on lines +107 to +115
fetchGitBranches(getRepositoryUrlFromLocation(location))
.then(branches => {
state.branchList = branches.branches;
this.setState(state);
})
.catch(() => {
state.branchList = undefined;
this.setState(state);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use async/await for better readability and maintainability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

See available git branches when creating a workspace in User Dashboard

6 participants