Skip to content
Merged
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
3 changes: 3 additions & 0 deletions cmd/entire/cli/git_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ func BranchExistsLocally(branchName string) (bool, error) {
// Should be switched back to go-git once we upgrade to go-git v6
// Returns an error if the ref doesn't exist or checkout fails.
func CheckoutBranch(ref string) error {
if strings.HasPrefix(ref, "-") {
return fmt.Errorf("checkout failed: invalid ref %q", ref)
}
ctx := context.Background()
cmd := exec.CommandContext(ctx, "git", "checkout", ref)
if output, err := cmd.CombinedOutput(); err != nil {
Expand Down
30 changes: 30 additions & 0 deletions cmd/entire/cli/resume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
Expand Down Expand Up @@ -163,6 +164,35 @@ func TestCheckoutBranch(t *testing.T) {
t.Error("CheckoutBranch() expected error for nonexistent branch, got nil")
}
})

t.Run("rejects ref starting with dash to prevent argument injection", func(t *testing.T) {
// "git checkout -b evil" would create a new branch named "evil" instead
// of failing, because git interprets "-b" as a flag.
err := CheckoutBranch("-b evil")
if err == nil {
t.Fatal("CheckoutBranch() should reject refs starting with '-', got nil")
}
if !strings.Contains(err.Error(), "invalid ref") {
t.Errorf("CheckoutBranch() error = %q, want error containing 'invalid ref'", err.Error())
}
})
}

func TestPerformGitResetHard_RejectsArgumentInjection(t *testing.T) {
tmpDir := t.TempDir()
t.Chdir(tmpDir)

setupResumeTestRepo(t, tmpDir, false)

// "git reset --hard -q" would silently reset to HEAD in quiet mode instead
// of failing, because git interprets "-q" as the --quiet flag.
err := performGitResetHard("-q")
if err == nil {
t.Fatal("performGitResetHard() should reject hashes starting with '-', got nil")
}
if !strings.Contains(err.Error(), "invalid commit hash") {
t.Errorf("performGitResetHard() error = %q, want error containing 'invalid commit hash'", err.Error())
}
}

func TestResumeFromCurrentBranch_NoCheckpoint(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions cmd/entire/cli/rewind.go
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,9 @@ func countCommitsBetween(repo *git.Repository, ancestor, descendant plumbing.Has
// Uses the git CLI instead of go-git because go-git's HardReset incorrectly
// deletes untracked directories (like .entire/) even when they're in .gitignore.
func performGitResetHard(commitHash string) error {
if strings.HasPrefix(commitHash, "-") {
return fmt.Errorf("reset failed: invalid commit hash %q", commitHash)
}
ctx := context.Background()
cmd := exec.CommandContext(ctx, "git", "reset", "--hard", commitHash)
if output, err := cmd.CombinedOutput(); err != nil {
Expand Down