fix: properly validate GitHub repository URLs to prevent URL substring attacks

This commit is contained in:
Roo Code 2025-11-04 15:32:04 +00:00
parent d4ca58d5d5
commit b924e46d9f
2 changed files with 49 additions and 2 deletions

View file

@ -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)

View file

@ -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
}