Skip to content

fix/serve-git: serve only listed repositories - #1413

Closed
cbrnrd wants to merge 1 commit into
mainfrom
carterbrainerd-fix-serve-git-nested-repos
Closed

cbrnrd wants to merge 1 commit into
mainfrom
carterbrainerd-fix-serve-git-nested-repos

Conversation

@cbrnrd

@cbrnrd cbrnrd commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Summary

src serve-git served any Git directory below the served root, not only the repositories that /v1/list-repos lists. A repository's working tree can track a directory that Git accepts as a repository: a HEAD file plus objects and refs symlinks that point to a repository outside the served root. If such a repository is cloned into the served directory, anyone who can reach the server can clone the repository outside it. The earlier fixes do not catch this, because the tracked directory is a real directory inside the root, and the symlinks below it are followed by Git, not by os.Root.

This PR also fixes an unrelated bug: when the served directory was a relative path, such as src serve-git served, bare repositories were missing from the list.

Behavior changes:

  • Only listed repositories are served. Requests for a repository that is nested in another repository's directory, or that is inside a .git directory, now fail with 404 repository not found. /v1/list-repos never listed these repositories, so a Sourcegraph instance does not request them.
  • Bare repositories are listed with a relative path. src serve-git DIR with a relative DIR now lists and serves the bare repositories in it, as it already did with an absolute path.

Implementation Details

  • Added checkListed in internal/servegit/serve.go, which the handler's Dir function calls before every Git request. It allows the request only if the repository is one that Repos would list:
    • Its path is valid and has no .git element, except the trailing .git of a working tree's clone path.
    • No directory between the served root and the repository, including the root itself, is a working tree or a bare repository. It uses the same isGitRepo and isBareRepo checks as Repos, which stops descending at the first repository it finds.
  • Handler.ServeHTTP returns 404 repository not found for these requests, instead of a 500 that included the error.
  • isBareRepo now makes its path absolute. It ran git --git-dir <path> rev-parse with the working directory set to that same path, so Git resolved a relative path against itself and found nothing.

Verification Evidence

  • Added TestHandlerServesOnlyListedRepositories. It clones a repository that tracks such a directory into the served root, checks that git upload-pack serves the outside repository's refs from it, and then checks that the handler returns 404 for it without the outside repository's HEAD in the response. It also checks that listed working trees and bare repositories still return 200, and that a root that is itself a repository serves only itself. Without the fix, the test fails with the response has the private repository's HEAD.
  • Added TestReposRelativeRoot, which lists a relative directory with a working tree and a bare repository. Without the fix, the bare repository is missing.
  • go test ./internal/servegit/ ./cmd/src/ passes, and dev/golangci-lint.sh run ./internal/servegit/... reports 0 issues.

I also ran the built binary as src serve-git served, with a relative path, and cloned over HTTP with real git clone:

-list served:
  dir/archive.bare
  public
clone /repos/public/.git:
  attacker repo
clone /repos/dir/archive.bare:
  attacker repo
clone /repos/public/evil:
  fatal: repository 'http://127.0.0.1:18571/repos/public/evil/' not found

Before this change, the same git clone of /repos/public/evil succeeded and checked out the commit of the repository outside the served root.

src serve-git served any Git directory below its root, not only the
repositories that /v1/list-repos lists. A repository's working tree can
track a directory that Git accepts as a repository, with symlinks to the
objects and refs of a repository outside the root, so a client could
clone a repository outside the served directory. Requests for
repositories that are nested in another repository or inside a .git
directory now fail with 404.

Also list bare repositories when the served directory is a relative
path. isBareRepo passed a relative --git-dir while running Git in that
same directory, so Git resolved the path twice and found nothing.

Amp-Thread-ID: https://ampcode.com/threads/T-01a13097-b634-70ff-ad0f-7d0d82cac5fd
Co-authored-by: Amp <amp@ampcode.com>
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

@cbrnrd cbrnrd closed this Nov 12, 2026
@cbrnrd
cbrnrd deleted the carterbrainerd-fix-serve-git-nested-repos branch November 12, 2026 11:45
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.

1 participant