Summary
The smart-HTTP git transport endpoints return the absolute server filesystem path of a repository in their HTTP 500 error body. An unauthenticated client cloning a public repo can recover repos_dir and the on-disk owner-slug directory layout. Information disclosure, low severity (no content or secrets, but useful for reconnaissance).
Where
crates/gitlawb-node/src/git/smart_http.rs invokes git with repo_path as a positional argument:
info_refs (smart_http.rs:19-29): git <service> --stateless-rpc --advertise-refs <repo_path>, bails "git {service} --advertise-refs failed: {stderr}".
run_git_service (smart_http.rs:116-157): git <service> --stateless-rpc <repo_path>, bails "{service} failed: {stderr}".
When git gets the path as an argument and the path is not a valid repo, git echoes the absolute path in stderr. That stderr is the outermost anyhow layer, so the handlers in api/repos.rs (git_info_refs:472, git_upload_pack, git_receive_pack) wrap it with AppError::Git(e.to_string()) and error.rs:56 renders it verbatim into {"error":"git_error","message": ...}.
Reachability
RepoStore::acquire has a "not found anywhere, return the path anyway" branch, so a handler can run an advertise against a path that does not exist on local disk. This is realistic when a repo exists in the DB but is not present locally: not yet synced from Tigris, Tigris disabled, or evicted from the local cache. The info/refs endpoint allows anonymous callers for public repos, so no auth is required.
Reproduction
The exact invocation form info_refs uses, against a path with no repo:
$ git upload-pack --stateless-rpc --advertise-refs /srv/repos/z6Mkowner/NOTSYNCED.git
fatal: '/srv/repos/z6Mkowner/NOTSYNCED.git' does not appear to be a git repository
That string becomes the 500 message, disclosing the absolute path.
Scope notes
This is confined to the argument-style git invocations in smart_http.rs. The other git helpers (store.rs, issues.rs, push_delta.rs) run with .current_dir(repo_path) and a relative subcommand, so git reports .git rather than the absolute path and does not leak it. init_bare does pass the path as an argument but is .context("initializing bare repo")-wrapped, so the path stays a leaf cause that only reaches the server log, not the client.
Suggested direction
Keep the full git stderr in the existing tracing::error! (already present at the call sites) and return a generic message to the client for these two paths, rather than e.to_string(). Verify the fix against the same cases the handlers already special-case (the info/refs 404/empty-repo paths) so nothing that currently returns a useful client error regresses.
Found during review of #103; not introduced by it. The pattern is pre-existing on main.
Summary
The smart-HTTP git transport endpoints return the absolute server filesystem path of a repository in their HTTP 500 error body. An unauthenticated client cloning a public repo can recover
repos_dirand the on-disk owner-slug directory layout. Information disclosure, low severity (no content or secrets, but useful for reconnaissance).Where
crates/gitlawb-node/src/git/smart_http.rsinvokes git withrepo_pathas a positional argument:info_refs(smart_http.rs:19-29):git <service> --stateless-rpc --advertise-refs <repo_path>, bails"git {service} --advertise-refs failed: {stderr}".run_git_service(smart_http.rs:116-157):git <service> --stateless-rpc <repo_path>, bails"{service} failed: {stderr}".When git gets the path as an argument and the path is not a valid repo, git echoes the absolute path in stderr. That stderr is the outermost anyhow layer, so the handlers in
api/repos.rs(git_info_refs:472,git_upload_pack,git_receive_pack) wrap it withAppError::Git(e.to_string())anderror.rs:56renders it verbatim into{"error":"git_error","message": ...}.Reachability
RepoStore::acquirehas a "not found anywhere, return the path anyway" branch, so a handler can run an advertise against a path that does not exist on local disk. This is realistic when a repo exists in the DB but is not present locally: not yet synced from Tigris, Tigris disabled, or evicted from the local cache. Theinfo/refsendpoint allows anonymous callers for public repos, so no auth is required.Reproduction
The exact invocation form
info_refsuses, against a path with no repo:That string becomes the 500
message, disclosing the absolute path.Scope notes
This is confined to the argument-style git invocations in
smart_http.rs. The other git helpers (store.rs,issues.rs,push_delta.rs) run with.current_dir(repo_path)and a relative subcommand, so git reports.gitrather than the absolute path and does not leak it.init_baredoes pass the path as an argument but is.context("initializing bare repo")-wrapped, so the path stays a leaf cause that only reaches the server log, not the client.Suggested direction
Keep the full git stderr in the existing
tracing::error!(already present at the call sites) and return a generic message to the client for these two paths, rather thane.to_string(). Verify the fix against the same cases the handlers already special-case (theinfo/refs404/empty-repo paths) so nothing that currently returns a useful client error regresses.Found during review of #103; not introduced by it. The pattern is pre-existing on main.