Blind SSRF via git ls-remote in Gitspace lookup-repo endpoint
Summary
The POST /api/v1/gitspaces/lookup-repo endpoint accepts a user-supplied url field and passes it directly to git ls-remote without validating whether the URL targets a private or internal IP address. Any authenticated user with InfraProviderView permission (a low-privilege role) can use this to make the Harness server issue outbound TCP/HTTP connections to arbitrary hosts including internal network addresses and cloud metadata endpoints (e.g., 169.254.169.254), enabling blind SSRF and internal port scanning.
Details
Vulnerable path:
app/api/handler/gitspace/lookup_repo.go -> HandleLookupRepo ->
app/api/controller/gitspace/lookup_repo.go -> Controller.LookupRepo ->
app/gitspace/scm/scm.go -> SCM.CheckValidCodeRepo -> detectBranch(ctx, url) ->
detectDefaultGitBranch(ctx, url) ->
git ls-remote --symref -q <attacker-url> HEAD
The sanitizeLookupRepoInput function at app/api/controller/gitspace/lookup_repo.go (lines 78-93) only validates that the URL parses successfully and has a non-empty scheme. It does not check whether the resolved host is a loopback, link-local, or private RFC-1918 address:
func (c *Controller) sanitizeLookupRepoInput(in *LookupRepoInput) error {
if in.RepoType == "" && in.URL == "" {
return ErrRepoMissing
}
parsedURL, err := url.Parse(in.URL)
if err != nil {
return ErrInvalidURL
}
if parsedURL.Scheme == "" {
return ErrBadURLScheme
}
// NO private IP check
return nil
}detectDefaultGitBranch (scm.go lines 113-130) then runs:
cmd := command.New("ls-remote",
command.WithFlag("--symref"),
command.WithFlag("-q"),
command.WithArg(gitRepoDir), // attacker URL
command.WithArg("HEAD"),
)git ls-remote makes an actual TCP connection and HTTP GET to <url>/info/refs?service=git-upload-pack. The resulting error distinguishes open from closed ports (see PoC below), providing a port-scanning oracle.
Contrast with the webhook service (app/services/webhook/http_client.go), which explicitly blocks private IPs via a post-DNS DialContext check -- that guard is entirely absent in the gitspace SCM code path.
PoC
(available upon request)
Impact
An authenticated attacker with InfraProviderView access can:
- Enumerate open TCP ports on internal hosts (distinguishable error messages for open vs. closed ports).
- Reach cloud instance metadata endpoints (
169.254.169.254,fd00:ec2::254) to probe for credential exposure depending on metadata endpoint behavior. - Send HTTP requests with git-specific headers to any internal HTTP service reachable from the server.
In multi-tenant Harness installations, any project member can trigger the SSRF against the internal network hosting the Harness deployment (AWS VPC, GCP VPC, Kubernetes cluster internal CIDRs, etc.).
Source: harness/harness