Skip to content

Smart-HTTP git endpoints leak absolute server path in 500 body #106

Description

@beardthelion

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:lowCosmetic, cleanup, or nice-to-havesubsystem:apiNode REST API request/response surface

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions