diff --git a/src/utils/__tests__/git.spec.ts b/src/utils/__tests__/git.spec.ts index 7afb6b6665..2f702ee96d 100644 --- a/src/utils/__tests__/git.spec.ts +++ b/src/utils/__tests__/git.spec.ts @@ -915,12 +915,34 @@ describe("isGitHubRepository", () => { expect(isGitHubRepository("https://GITHUB.COM/user/repo.git")).toBe(true) }) + it("should return true for GitHub subdomains", () => { + expect(isGitHubRepository("https://gist.github.com/user/repo")).toBe(true) + expect(isGitHubRepository("https://api.github.com/repos/user/repo")).toBe(true) + expect(isGitHubRepository("git@gist.github.com:user/repo.git")).toBe(true) + }) + it("should return false for non-GitHub URLs", () => { expect(isGitHubRepository("https://gitlab.com/user/repo.git")).toBe(false) expect(isGitHubRepository("https://bitbucket.org/user/repo.git")).toBe(false) expect(isGitHubRepository("git@gitlab.com:user/repo.git")).toBe(false) }) + it("should return false for malicious URLs with github.com in hostname", () => { + // Security: These URLs have "github.com" as part of the hostname but are not GitHub + expect(isGitHubRepository("https://malicious-github.com/user/repo.git")).toBe(false) + expect(isGitHubRepository("https://github.com.evil.com/user/repo.git")).toBe(false) + expect(isGitHubRepository("https://fake-github.com/user/repo.git")).toBe(false) + expect(isGitHubRepository("git@malicious-github.com:user/repo.git")).toBe(false) + expect(isGitHubRepository("ssh://git@github.com.evil.com/user/repo.git")).toBe(false) + }) + + it("should return false for URLs with github.com in the path", () => { + // Security: These URLs have "github.com" in the path but not as the hostname + expect(isGitHubRepository("https://evil.com/github.com/malicious/repo.git")).toBe(false) + expect(isGitHubRepository("https://attacker.com/fake/github.com/path")).toBe(false) + expect(isGitHubRepository("git@evil.com:github.com/user/repo.git")).toBe(false) + }) + it("should return false for undefined or empty URLs", () => { expect(isGitHubRepository(undefined)).toBe(false) expect(isGitHubRepository("")).toBe(false) diff --git a/src/utils/git.ts b/src/utils/git.ts index ff10c181ef..f0c3dd4e58 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -222,8 +222,33 @@ export function isGitHubRepository(repositoryUrl?: string): boolean { } try { - // Check if the URL contains github.com - return repositoryUrl.toLowerCase().includes("github.com") + const url = repositoryUrl.toLowerCase().trim() + + // Try to parse as HTTPS/HTTP URL + if (url.startsWith("https://") || url.startsWith("http://")) { + const parsed = new URL(url) + return parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com") + } + + // Handle SSH format: git@github.com:user/repo.git + if (url.startsWith("git@")) { + const match = url.match(/^git@([^:]+):/) + if (match && match[1]) { + const host = match[1] + return host === "github.com" || host.endsWith(".github.com") + } + } + + // Handle SSH with protocol: ssh://git@github.com/user/repo.git + if (url.startsWith("ssh://")) { + const match = url.match(/^ssh:\/\/(?:git@)?([^\/]+)/) + if (match && match[1]) { + const host = match[1] + return host === "github.com" || host.endsWith(".github.com") + } + } + + return false } catch { return false }