From 594bb81fd46e9991d8f361b368a0d17b41984971 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 21 Aug 2025 14:08:45 -0300 Subject: [PATCH 1/8] sec: added --end-of-options to prevent unintended options Signed-off-by: Carlos Alexandro Becker --- repo.go | 10 ++++++---- repo_commit.go | 8 ++++++-- repo_diff.go | 14 +++++++------- repo_grep.go | 1 + repo_pull.go | 1 + repo_reference.go | 4 +++- repo_remote.go | 8 +++++++- repo_tag.go | 3 ++- 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/repo.go b/repo.go index 769a23bb..dd327131 100644 --- a/repo.go +++ b/repo.go @@ -83,6 +83,7 @@ func Init(path string, opts ...InitOptions) error { if opt.Bare { cmd.AddArgs("--bare") } + cmd.AddArgs("--end-of-options") _, err = cmd.RunInDirWithTimeout(opt.Timeout, path) return err } @@ -157,6 +158,7 @@ func Clone(url, dst string, opts ...CloneOptions) error { cmd.AddArgs("--depth", strconv.FormatUint(opt.Depth, 10)) } + cmd.AddArgs("--end-of-options") _, err = cmd.AddArgs(url, dst).RunWithTimeout(opt.Timeout) return err } @@ -259,7 +261,7 @@ func Push(repoPath, remote, branch string, opts ...PushOptions) error { opt = opts[0] } - cmd := NewCommand("push").AddOptions(opt.CommandOptions).AddArgs(remote, branch) + cmd := NewCommand("push").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", remote, branch) _, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) return err } @@ -346,7 +348,7 @@ func Reset(repoPath, rev string, opts ...ResetOptions) error { cmd.AddArgs("--hard") } - _, err := cmd.AddOptions(opt.CommandOptions).AddArgs(rev).RunInDir(repoPath) + _, err := cmd.AddOptions(opt.CommandOptions).AddArgs("--end-of-options", rev).RunInDir(repoPath) return err } @@ -382,7 +384,7 @@ func Move(repoPath, src, dst string, opts ...MoveOptions) error { opt = opts[0] } - _, err := NewCommand("mv").AddOptions(opt.CommandOptions).AddArgs(src, dst).RunInDirWithTimeout(opt.Timeout, repoPath) + _, err := NewCommand("mv").AddOptions(opt.CommandOptions).AddArgs("--end-of-options", src, dst).RunInDirWithTimeout(opt.Timeout, repoPath) return err } @@ -549,7 +551,7 @@ func ShowNameStatus(repoPath, rev string, opts ...ShowNameStatusOptions) (*NameS stderr := new(bytes.Buffer) cmd := NewCommand("show", "--name-status", "--pretty=format:''"). AddOptions(opt.CommandOptions). - AddArgs(rev) + AddArgs("--end-of-options", rev) err := cmd.RunInDirPipelineWithTimeout(opt.Timeout, w, stderr, repoPath) _ = w.Close() // Close writer to exit parsing goroutine if err != nil { diff --git a/repo_commit.go b/repo_commit.go index e4049a66..0617ff62 100644 --- a/repo_commit.go +++ b/repo_commit.go @@ -221,7 +221,7 @@ func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { cmd := NewCommand("log"). AddOptions(opt.CommandOptions). - AddArgs("--pretty="+LogFormatHashOnly, rev) + AddArgs("--pretty=" + LogFormatHashOnly) if opt.MaxCount > 0 { cmd.AddArgs("--max-count=" + strconv.Itoa(opt.MaxCount)) } @@ -237,6 +237,7 @@ func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { if opt.RegexpIgnoreCase { cmd.AddArgs("--regexp-ignore-case") } + cmd.AddArgs("--end-of-options", rev) cmd.AddArgs("--") if opt.Path != "" { cmd.AddArgs(escapePath(opt.Path)) @@ -406,6 +407,7 @@ func DiffNameOnly(repoPath, base, head string, opts ...DiffNameOnlyOptions) ([]s cmd := NewCommand("diff"). AddOptions(opt.CommandOptions). AddArgs("--name-only") + cmd.AddArgs("--end-of-options") if opt.NeedsMergeBase { cmd.AddArgs(base + "..." + head) } else { @@ -471,7 +473,8 @@ func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions cmd := NewCommand("rev-list"). AddOptions(opt.CommandOptions). - AddArgs("--count") + AddArgs("--count"). + AddArgs("--end-of-options") cmd.AddArgs(refspecs...) cmd.AddArgs("--") if opt.Path != "" { @@ -512,6 +515,7 @@ func (r *Repository) RevList(refspecs []string, opts ...RevListOptions) ([]*Comm } cmd := NewCommand("rev-list").AddOptions(opt.CommandOptions) + cmd.AddArgs("--end-of-options") cmd.AddArgs(refspecs...) cmd.AddArgs("--") if opt.Path != "" { diff --git a/repo_diff.go b/repo_diff.go index 87848bbc..ffe9d6ca 100644 --- a/repo_diff.go +++ b/repo_diff.go @@ -45,7 +45,7 @@ func (r *Repository) Diff(rev string, maxFiles, maxFileLines, maxLineChars int, if commit.ParentsCount() == 0 { cmd = cmd.AddArgs("show"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", rev) + AddArgs("--full-index", "--end-of-options", rev) } else { c, err := commit.Parent(0) if err != nil { @@ -53,12 +53,12 @@ func (r *Repository) Diff(rev string, maxFiles, maxFileLines, maxLineChars int, } cmd = cmd.AddArgs("diff"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", "-M", c.ID.String(), rev) + AddArgs("--full-index", "-M", c.ID.String(), "--end-of-options", rev) } } else { cmd = cmd.AddArgs("diff"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", "-M", opt.Base, rev) + AddArgs("--full-index", "-M", opt.Base, "--end-of-options", rev) } stdout, w := io.Pipe() @@ -114,7 +114,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op if commit.ParentsCount() == 0 { cmd = cmd.AddArgs("show"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", rev) + AddArgs("--full-index", "--end-of-options", rev) } else { c, err := commit.Parent(0) if err != nil { @@ -122,13 +122,13 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op } cmd = cmd.AddArgs("diff"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", "-M", c.ID.String(), rev) + AddArgs("--full-index", "-M", c.ID.String(), "--end-of-options", rev) } case RawDiffPatch: if commit.ParentsCount() == 0 { cmd = cmd.AddArgs("format-patch"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", "--root", rev) + AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", "--root", "--end-of-options", rev) } else { c, err := commit.Parent(0) if err != nil { @@ -136,7 +136,7 @@ func (r *Repository) RawDiff(rev string, diffType RawDiffFormat, w io.Writer, op } cmd = cmd.AddArgs("format-patch"). AddOptions(opt.CommandOptions). - AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", rev+"..."+c.ID.String()) + AddArgs("--full-index", "--no-signoff", "--no-signature", "--stdout", "--end-of-options", rev+"..."+c.ID.String()) } default: return fmt.Errorf("invalid diffType: %s", diffType) diff --git a/repo_grep.go b/repo_grep.go index 81f6e306..c743f0ce 100644 --- a/repo_grep.go +++ b/repo_grep.go @@ -96,6 +96,7 @@ func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { if opt.ExtendedRegexp { cmd.AddArgs("--extended-regexp") } + cmd.AddArgs("--end-of-options") cmd.AddArgs(pattern, opt.Tree) if opt.Pathspec != "" { cmd.AddArgs("--", opt.Pathspec) diff --git a/repo_pull.go b/repo_pull.go index 111347d1..f34dd2c8 100644 --- a/repo_pull.go +++ b/repo_pull.go @@ -32,6 +32,7 @@ func MergeBase(repoPath, base, head string, opts ...MergeBaseOptions) (string, e stdout, err := NewCommand("merge-base"). AddOptions(opt.CommandOptions). + AddArgs("--end-of-options"). AddArgs(base, head). RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { diff --git a/repo_reference.go b/repo_reference.go index 9da086ce..c6a2b582 100644 --- a/repo_reference.go +++ b/repo_reference.go @@ -56,7 +56,7 @@ func ShowRefVerify(repoPath, ref string, opts ...ShowRefVerifyOptions) (string, opt = opts[0] } - cmd := NewCommand("show-ref", "--verify", ref).AddOptions(opt.CommandOptions) + cmd := NewCommand("show-ref", "--verify", "--end-of-options", ref).AddOptions(opt.CommandOptions) stdout, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { if strings.Contains(err.Error(), "not a valid ref") { @@ -162,6 +162,7 @@ func SymbolicRef(repoPath string, opts ...SymbolicRefOptions) (string, error) { if opt.Name == "" { opt.Name = "HEAD" } + cmd.AddArgs("--end-of-options") cmd.AddArgs(opt.Name) if opt.Ref != "" { cmd.AddArgs(opt.Ref) @@ -281,6 +282,7 @@ func DeleteBranch(repoPath, name string, opts ...DeleteBranchOptions) error { } else { cmd.AddArgs("-d") } + cmd.AddArgs("--end-of-options") _, err := cmd.AddArgs(name).RunInDirWithTimeout(opt.Timeout, repoPath) return err } diff --git a/repo_remote.go b/repo_remote.go index d8b110d2..8835a272 100644 --- a/repo_remote.go +++ b/repo_remote.go @@ -49,6 +49,7 @@ func LsRemote(url string, opts ...LsRemoteOptions) ([]*Reference, error) { if opt.Refs { cmd.AddArgs("--refs") } + cmd.AddArgs("--end-of-options") cmd.AddArgs(url) if len(opt.Patterns) > 0 { cmd.AddArgs(opt.Patterns...) @@ -120,6 +121,7 @@ func RemoteAdd(repoPath, name, url string, opts ...RemoteAddOptions) error { if opt.MirrorFetch { cmd.AddArgs("--mirror=fetch") } + cmd.AddArgs("--end-of-options") _, err := cmd.AddArgs(name, url).RunInDirWithTimeout(opt.Timeout, repoPath) return err @@ -166,7 +168,7 @@ func RemoteRemove(repoPath, name string, opts ...RemoteRemoveOptions) error { _, err := NewCommand("remote", "remove"). AddOptions(opt.CommandOptions). - AddArgs(name). + AddArgs("--end-of-options", name). RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { // the error status may differ from git clients @@ -262,6 +264,7 @@ func RemoteGetURL(repoPath, name string, opts ...RemoteGetURLOptions) ([]string, if opt.All { cmd.AddArgs("--all") } + cmd.AddArgs("--end-of-options") stdout, err := cmd.AddArgs(name).RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { @@ -306,6 +309,7 @@ func RemoteSetURL(repoPath, name, newurl string, opts ...RemoteSetURLOptions) er cmd.AddArgs("--push") } + cmd.AddArgs("--end-of-options") cmd.AddArgs(name, newurl) if opt.Regex != "" { @@ -361,6 +365,7 @@ func RemoteSetURLAdd(repoPath, name, newurl string, opts ...RemoteSetURLAddOptio cmd.AddArgs("--push") } + cmd.AddArgs("--end-of-options") cmd.AddArgs(name, newurl) _, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) @@ -407,6 +412,7 @@ func RemoteSetURLDelete(repoPath, name, regex string, opts ...RemoteSetURLDelete cmd.AddArgs("--push") } + cmd.AddArgs("--end-of-options") cmd.AddArgs(name, regex) _, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) diff --git a/repo_tag.go b/repo_tag.go index 342979f3..fe2d043d 100644 --- a/repo_tag.go +++ b/repo_tag.go @@ -247,6 +247,7 @@ func (r *Repository) CreateTag(name, rev string, opts ...CreateTagOptions) error if opt.Author != nil { cmd.AddCommitter(opt.Author) } + cmd.AddArgs("--end-of-options") } else { // 🚨 SECURITY: Prevent including unintended options in the path to the Git command. cmd.AddArgs("--end-of-options") @@ -279,7 +280,7 @@ func (r *Repository) DeleteTag(name string, opts ...DeleteTagOptions) error { opt = opts[0] } - _, err := NewCommand("tag", "--delete", name). + _, err := NewCommand("tag", "--delete", "--end-of-options", name). AddOptions(opt.CommandOptions). RunInDirWithTimeout(opt.Timeout, r.path) return err From 0b2b4c764cee5d89692de9b79202741a31d49f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 22:58:08 -0500 Subject: [PATCH 2/8] Combine end-of-options and separator arguments --- repo_commit.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/repo_commit.go b/repo_commit.go index 0617ff62..a1bb7863 100644 --- a/repo_commit.go +++ b/repo_commit.go @@ -237,8 +237,7 @@ func (r *Repository) Log(rev string, opts ...LogOptions) ([]*Commit, error) { if opt.RegexpIgnoreCase { cmd.AddArgs("--regexp-ignore-case") } - cmd.AddArgs("--end-of-options", rev) - cmd.AddArgs("--") + cmd.AddArgs("--end-of-options", rev, "--") if opt.Path != "" { cmd.AddArgs(escapePath(opt.Path)) } From 0bdd9316d8035eb023e33afb07dff652d30f51c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:05:29 -0500 Subject: [PATCH 3/8] Refactor command argument addition for clarity --- repo_commit.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/repo_commit.go b/repo_commit.go index a1bb7863..d4e8c1fd 100644 --- a/repo_commit.go +++ b/repo_commit.go @@ -472,8 +472,10 @@ func (r *Repository) RevListCount(refspecs []string, opts ...RevListCountOptions cmd := NewCommand("rev-list"). AddOptions(opt.CommandOptions). - AddArgs("--count"). - AddArgs("--end-of-options") + AddArgs( + "--count", + "--end-of-options", + ) cmd.AddArgs(refspecs...) cmd.AddArgs("--") if opt.Path != "" { From 46d1d61dd28a5defa0fb8191f42a5654569e64a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:07:06 -0500 Subject: [PATCH 4/8] Refactor AddArgs calls for better readability --- repo_grep.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/repo_grep.go b/repo_grep.go index c743f0ce..f6394bce 100644 --- a/repo_grep.go +++ b/repo_grep.go @@ -96,8 +96,11 @@ func (r *Repository) Grep(pattern string, opts ...GrepOptions) []*GrepResult { if opt.ExtendedRegexp { cmd.AddArgs("--extended-regexp") } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(pattern, opt.Tree) + cmd.AddArgs( + "--end-of-options", + pattern, + opt.Tree, + ) if opt.Pathspec != "" { cmd.AddArgs("--", opt.Pathspec) } From 56b1bcb8c6f32328d09d58597da54d5cbb18e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:07:48 -0500 Subject: [PATCH 5/8] Refactor command arguments for clarity --- repo_pull.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/repo_pull.go b/repo_pull.go index f34dd2c8..5d2cfb1e 100644 --- a/repo_pull.go +++ b/repo_pull.go @@ -32,9 +32,11 @@ func MergeBase(repoPath, base, head string, opts ...MergeBaseOptions) (string, e stdout, err := NewCommand("merge-base"). AddOptions(opt.CommandOptions). - AddArgs("--end-of-options"). - AddArgs(base, head). - RunInDirWithTimeout(opt.Timeout, repoPath) + AddArgs( + "--end-of-options", + base, + head, + ).RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { if strings.Contains(err.Error(), "exit status 1") { return "", ErrNoMergeBase From 58bb06f694b6899ddcb648a6e481d5fa654cec8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:10:52 -0500 Subject: [PATCH 6/8] Refactor command argument handling in repo_reference.go --- repo_reference.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/repo_reference.go b/repo_reference.go index c6a2b582..43dfb3f9 100644 --- a/repo_reference.go +++ b/repo_reference.go @@ -162,8 +162,7 @@ func SymbolicRef(repoPath string, opts ...SymbolicRefOptions) (string, error) { if opt.Name == "" { opt.Name = "HEAD" } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(opt.Name) + cmd.AddArgs("--end-of-options", opt.Name) if opt.Ref != "" { cmd.AddArgs(opt.Ref) } @@ -282,8 +281,7 @@ func DeleteBranch(repoPath, name string, opts ...DeleteBranchOptions) error { } else { cmd.AddArgs("-d") } - cmd.AddArgs("--end-of-options") - _, err := cmd.AddArgs(name).RunInDirWithTimeout(opt.Timeout, repoPath) + _, err := cmd.AddArgs("--end-of-options", name).RunInDirWithTimeout(opt.Timeout, repoPath) return err } From e0cc65c96fc2e591cc7ea05cbdb6e8c0e6db5649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:12:27 -0500 Subject: [PATCH 7/8] Refactor AddArgs calls to include url parameter --- repo_remote.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/repo_remote.go b/repo_remote.go index 8835a272..363b8998 100644 --- a/repo_remote.go +++ b/repo_remote.go @@ -49,8 +49,7 @@ func LsRemote(url string, opts ...LsRemoteOptions) ([]*Reference, error) { if opt.Refs { cmd.AddArgs("--refs") } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(url) + cmd.AddArgs("--end-of-options", url) if len(opt.Patterns) > 0 { cmd.AddArgs(opt.Patterns...) } @@ -121,9 +120,8 @@ func RemoteAdd(repoPath, name, url string, opts ...RemoteAddOptions) error { if opt.MirrorFetch { cmd.AddArgs("--mirror=fetch") } - cmd.AddArgs("--end-of-options") - _, err := cmd.AddArgs(name, url).RunInDirWithTimeout(opt.Timeout, repoPath) + _, err := cmd.AddArgs("--end-of-options", name, url).RunInDirWithTimeout(opt.Timeout, repoPath) return err } @@ -264,9 +262,8 @@ func RemoteGetURL(repoPath, name string, opts ...RemoteGetURLOptions) ([]string, if opt.All { cmd.AddArgs("--all") } - cmd.AddArgs("--end-of-options") - stdout, err := cmd.AddArgs(name).RunInDirWithTimeout(opt.Timeout, repoPath) + stdout, err := cmd.AddArgs("--end-of-options", name).RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil { return nil, err } @@ -309,8 +306,7 @@ func RemoteSetURL(repoPath, name, newurl string, opts ...RemoteSetURLOptions) er cmd.AddArgs("--push") } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(name, newurl) + cmd.AddArgs("--end-of-options", name, newurl) if opt.Regex != "" { cmd.AddArgs(opt.Regex) @@ -365,8 +361,7 @@ func RemoteSetURLAdd(repoPath, name, newurl string, opts ...RemoteSetURLAddOptio cmd.AddArgs("--push") } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(name, newurl) + cmd.AddArgs("--end-of-options", name, newurl) _, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") { @@ -412,8 +407,7 @@ func RemoteSetURLDelete(repoPath, name, regex string, opts ...RemoteSetURLDelete cmd.AddArgs("--push") } - cmd.AddArgs("--end-of-options") - cmd.AddArgs(name, regex) + cmd.AddArgs("--end-of-options", name, regex) _, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath) if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") { From 1c4fb1985a1fc4df09745005ce5d67c17ad77d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=8A=E1=B4=8F=E1=B4=87=20=E1=B4=84=CA=9C=E1=B4=87?= =?UTF-8?q?=C9=B4?= Date: Tue, 18 Nov 2025 23:13:33 -0500 Subject: [PATCH 8/8] Refactor command arguments for security --- repo_tag.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/repo_tag.go b/repo_tag.go index fe2d043d..fd00deac 100644 --- a/repo_tag.go +++ b/repo_tag.go @@ -249,9 +249,7 @@ func (r *Repository) CreateTag(name, rev string, opts ...CreateTagOptions) error } cmd.AddArgs("--end-of-options") } else { - // 🚨 SECURITY: Prevent including unintended options in the path to the Git command. - cmd.AddArgs("--end-of-options") - cmd.AddArgs(name) + cmd.AddArgs("--end-of-options", name) } cmd.AddArgs(rev)