Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/git/src/mcp_server_git/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
# For all commands, we need an existing repo
repo = git.Repo(repo_path)

if repository is not None:
if repo.working_dir:
validate_repo_path(Path(repo.working_dir), repository)
if repo.git_dir:
validate_repo_path(Path(repo.git_dir), repository)

match name:
case GitTools.STATUS:
status = git_status(repo)
Expand Down
43 changes: 43 additions & 0 deletions src/git/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,49 @@ def test_validate_repo_path_symlink_escape(tmp_path: Path):
with pytest.raises(ValueError) as exc_info:
validate_repo_path(symlink, allowed)
assert "outside the allowed repository" in str(exc_info.value)


def test_validate_repo_path_prefix_collision(tmp_path: Path):
"""Path sharing a common prefix with allowed_repository must not bypass validation."""
allowed = tmp_path / "allowed"
allowed.mkdir()
prefix_collision = tmp_path / "allowed_suffix"
prefix_collision.mkdir()

with pytest.raises(ValueError) as exc_info:
validate_repo_path(prefix_collision, allowed)
assert "outside the allowed repository" in str(exc_info.value)


def test_validate_repo_path_nested_symlink_chain(tmp_path: Path):
"""Chained symlinks pointing outside allowed_repository must be rejected."""
allowed = tmp_path / "allowed_repo"
allowed.mkdir()
outside = tmp_path / "outside"
outside.mkdir()

link1 = allowed / "link1"
link2 = allowed / "link2"
link2.symlink_to(outside)
link1.symlink_to(link2)

with pytest.raises(ValueError) as exc_info:
validate_repo_path(link1, allowed)
assert "outside the allowed repository" in str(exc_info.value)


def test_validate_repo_path_symlink_inside_allowed(tmp_path: Path):
"""Symlink pointing to a target inside allowed_repository should be accepted."""
allowed = tmp_path / "allowed_repo"
allowed.mkdir()
real_subdir = allowed / "real_dir"
real_subdir.mkdir()

symlink = allowed / "symlink_dir"
symlink.symlink_to(real_subdir)

validate_repo_path(symlink, allowed) # Should not raise

# Tests for argument injection protection

def test_git_diff_rejects_flag_injection(test_repository):
Expand Down