@@ -39,7 +39,7 @@ def test_git_checkout_existing_branch(test_repository):
3939
4040def test_git_checkout_nonexistent_branch (test_repository ):
4141
42- with pytest .raises (git .GitCommandError ):
42+ with pytest .raises (git .exc . BadName ):
4343 git_checkout (test_repository , "nonexistent-branch" )
4444
4545def test_git_branch_local (test_repository ):
@@ -248,3 +248,115 @@ def test_git_show_initial_commit(test_repository):
248248 assert "Commit:" in result
249249 assert "initial commit" in result
250250 assert "test.txt" in result
251+
252+
253+ # Tests for argument injection protection
254+
255+ def test_git_diff_rejects_flag_injection (test_repository ):
256+ """git_diff should reject flags that could be used for argument injection."""
257+ with pytest .raises (git .exc .BadName ):
258+ git_diff (test_repository , "--output=/tmp/evil" )
259+
260+ with pytest .raises (git .exc .BadName ):
261+ git_diff (test_repository , "--help" )
262+
263+ with pytest .raises (git .exc .BadName ):
264+ git_diff (test_repository , "-p" )
265+
266+
267+ def test_git_checkout_rejects_flag_injection (test_repository ):
268+ """git_checkout should reject flags that could be used for argument injection."""
269+ with pytest .raises (git .exc .BadName ):
270+ git_checkout (test_repository , "--help" )
271+
272+ with pytest .raises (git .exc .BadName ):
273+ git_checkout (test_repository , "--orphan=evil" )
274+
275+ with pytest .raises (git .exc .BadName ):
276+ git_checkout (test_repository , "-f" )
277+
278+
279+ def test_git_diff_allows_valid_refs (test_repository ):
280+ """git_diff should work normally with valid git refs."""
281+ # Get the default branch name
282+ default_branch = test_repository .active_branch .name
283+
284+ # Create a branch with a commit for diffing
285+ test_repository .git .checkout ("-b" , "valid-diff-branch" )
286+ file_path = Path (test_repository .working_dir ) / "test.txt"
287+ file_path .write_text ("valid diff content" )
288+ test_repository .index .add (["test.txt" ])
289+ test_repository .index .commit ("valid diff commit" )
290+
291+ # Test with branch name
292+ result = git_diff (test_repository , default_branch )
293+ assert "test.txt" in result
294+
295+ # Test with HEAD~1
296+ result = git_diff (test_repository , "HEAD~1" )
297+ assert "test.txt" in result
298+
299+ # Test with commit hash
300+ commit_sha = test_repository .head .commit .hexsha
301+ result = git_diff (test_repository , commit_sha )
302+ assert result is not None
303+
304+
305+ def test_git_checkout_allows_valid_branches (test_repository ):
306+ """git_checkout should work normally with valid branch names."""
307+ # Get the default branch name
308+ default_branch = test_repository .active_branch .name
309+
310+ # Create a branch to checkout
311+ test_repository .git .branch ("valid-checkout-branch" )
312+
313+ result = git_checkout (test_repository , "valid-checkout-branch" )
314+ assert "Switched to branch 'valid-checkout-branch'" in result
315+ assert test_repository .active_branch .name == "valid-checkout-branch"
316+
317+ # Checkout back to default branch
318+ result = git_checkout (test_repository , default_branch )
319+ assert "Switched to branch" in result
320+ assert test_repository .active_branch .name == default_branch
321+
322+
323+ def test_git_diff_rejects_malicious_refs (test_repository ):
324+ """git_diff should reject refs starting with '-' even if they exist.
325+
326+ This tests defense in depth against an attacker who creates malicious
327+ refs via filesystem manipulation (e.g. using mcp-filesystem to write
328+ to .git/refs/heads/--output=...).
329+ """
330+ import os
331+
332+ # Manually create a malicious ref by writing directly to .git/refs
333+ sha = test_repository .head .commit .hexsha
334+ refs_dir = Path (test_repository .git_dir ) / "refs" / "heads"
335+ malicious_ref_path = refs_dir / "--output=evil.txt"
336+ malicious_ref_path .write_text (sha )
337+
338+ # Even though the ref exists, it should be rejected
339+ with pytest .raises (git .exc .BadName ):
340+ git_diff (test_repository , "--output=evil.txt" )
341+
342+ # Verify no file was created (the attack was blocked)
343+ assert not os .path .exists ("evil.txt" )
344+
345+ # Cleanup
346+ malicious_ref_path .unlink ()
347+
348+
349+ def test_git_checkout_rejects_malicious_refs (test_repository ):
350+ """git_checkout should reject refs starting with '-' even if they exist."""
351+ # Manually create a malicious ref
352+ sha = test_repository .head .commit .hexsha
353+ refs_dir = Path (test_repository .git_dir ) / "refs" / "heads"
354+ malicious_ref_path = refs_dir / "--orphan=evil"
355+ malicious_ref_path .write_text (sha )
356+
357+ # Even though the ref exists, it should be rejected
358+ with pytest .raises (git .exc .BadName ):
359+ git_checkout (test_repository , "--orphan=evil" )
360+
361+ # Cleanup
362+ malicious_ref_path .unlink ()
0 commit comments