CVE-2024-35183: Insufficient Information (NVD-CWE-noinfo)
value: wolfictl is a command line tool for working with Wolfi. A git authentication issue in versions prior to 0.16.10 allows a local user’s GitHub token to be sent to remote servers other than `github.com`. Most git-dependent functionality in wolfictl relies on its own `git` package, which contains centralized logic for implementing interactions with git repositories. Some of this functionality requires authentication in order to access private repositories. A central function `GetGitAuth` looks for a GitHub token in the environment variable `GITHUB_TOKEN` and returns it as an HTTP basic auth object to be used with the `github.com/go-git/go-git/v5` library. Most callers (direct or indirect) of `GetGitAuth` use the token to authenticate to github.com only; however, in some cases callers were passing this authentication without checking that the remote git repository was hosted on github.com. This behavior has existed in one form or another since commit 0d06e1578300327c212dda26a5ab31d09352b9d0 - committed January 25, 2023. This impacts anyone who ran the `wolfictl check update` commands with a Melange configuration that included a `git-checkout` directive step that referenced a git repository not hosted on github.com. This also impacts anyone who ran `wolfictl update <url>` with a remote URL outside of github.com. Additionally, these subcommands must have run with the `GITHUB_TOKEN` environment variable set to a valid GitHub token. Users should upgrade to version 0.16.10 to receive a patch.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-15T22:15Z
Repository: https://github.com/wolfi-dev/wolfictl
Language: Go
Files changed: pkg/advisory/data_session.go, pkg/git/git.go, pkg/git/tag.go, pkg/update/deps/cleanup.go, pkg/update/package.go, pkg/update/update.go
Commit date: 2024-05-14 12:54:11 +0100
Commit message: Git based commands, only use GITHUB_TOKEN when interacting with GitHub's API

This change also removes the submodules git update as this is not used anymore, therefore does not need updating with the new GetGitAuth signature.

Signed-off-by: James Rawlings <jrawlings@chainguard.dev>

Diff: diff --git a/pkg/advisory/data_session.go b/pkg/advisory/data_session.go
index ecbdd83..01e72aa 100644
--- a/pkg/advisory/data_session.go
+++ b/pkg/advisory/data_session.go
@@ -49,14 +49,19 @@ func NewDataSession(ctx context.Context, opts DataSessionOptions) (*DataSession,
 		distro:  opts.Distro,
 	}
 
 	ds.githubClient = opts.GitHubClient
 
+	gitAuth, err := wgit.GetGitAuth(opts.Distro.Absolute.AdvisoriesHTTPSCloneURL())
+	if err != nil {
+		return nil, fmt.Errorf("getting git auth: %w", err)
+	}
+
 	// clone advisories repo
 	repo, err := git.PlainCloneContext(ctx, tempDir, false, &git.CloneOptions{
 		URL:  opts.Distro.Absolute.AdvisoriesHTTPSCloneURL(),
-		Auth: wgit.GetGitAuth(),
+		Auth: gitAuth,
 	})
 	if err != nil {
 		return nil, fmt.Errorf("cloning advisories repo: %w", err)
 	}
 	ds.repo = repo
@@ -166,13 +171,18 @@ func (ds DataSession) Modified() bool {
 }
 
 // Push pushes the changes made during the session to the remote advisories
 // repository.
 func (ds DataSession) Push(ctx context.Context) error {
-	err := ds.repo.PushContext(ctx, &git.PushOptions{
+	gitAuth, err := wgit.GetGitAuth(ds.distro.Absolute.AdvisoriesHTTPSCloneURL())
+	if err != nil {
+		return fmt.Errorf("getting git auth: %w", err)
+	}
+
+	err = ds.repo.PushContext(ctx, &git.PushOptions{
 		RemoteURL: ds.distro.Absolute.AdvisoriesHTTPSCloneURL(),
-		Auth:      wgit.GetGitAuth(),
+		Auth:      gitAuth,
 	})
 	if err != nil {
 		return fmt.Errorf("pushing changes: %w", err)
 	}
 
diff --git a/pkg/git/git.go b/pkg/git/git.go
index f8b0458..eb589be 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -1,15 +1,18 @@
 package git
 
 import (
 	"fmt"
+	"log/slog"
 	"net/url"
 	"os"
 	"os/exec"
 	"strings"
 	"time"
 
+	"github.com/chainguard-dev/clog"
+
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/object"
 	"github.com/go-git/go-git/v5/plumbing/storer"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 
@@ -17,24 +20,37 @@ import (
 	"github.com/wolfi-dev/wolfictl/pkg/stringhelpers"
 
 	gitHttp "github.com/go-git/go-git/v5/plumbing/transport/http"
 )
 
-func GetGitAuth() *gitHttp.BasicAuth {
+func GetGitAuth(gitURL string) (*gitHttp.BasicAuth, error) {
+	logger := clog.NewLogger(slog.Default()) // TODO: plumb through context, everywhere
+
+	parsedURL, err := ParseGitURL(gitURL)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse git URL %q: %w", gitURL, err)
+	}
+
+	// Only use GITHUB_TOKEN for github.com URLs
+	if parsedURL.Host != "github.com" {
+		logger.Warnf("host %q is not github.com, not using GITHUB_TOKEN for authentication", parsedURL.Host)
+		return nil, nil
+	}
+
 	gitToken := os.Getenv("GITHUB_TOKEN")
 
 	if gitToken == "" {
 		// If the token is empty, there's no way we can return a usable authentication
 		// anyway. Whereas if we return nil, and don't auth, we have a chance at
 		// succeeding with access of a public repo.
-		return nil
+		return &gitHttp.BasicAuth{}, nil
 	}
 
 	return &gitHttp.BasicAuth{
 		Username: "abc123",
 		Password: gitToken,
-	}
+	}, nil
 }
 
 type URL struct {
 	Scheme       string
 	Host         string
@@ -180,11 +196,14 @@ func TempClone(gitURL, hash string, useAuth bool) (repoDir string, err error) {
 		return dir, fmt.Errorf("unable to create temp directory for git clone: %w", err)
 	}
 
 	var auth transport.AuthMethod
 	if useAuth {
-		auth = GetGitAuth()
+		auth, err = GetGitAuth(gitURL)
+		if err != nil {
+			return dir, fmt.Errorf("unable to get git auth: %w", err)
+		}
 	}
 
 	repo, err := git.PlainClone(dir, false, &git.CloneOptions{
 		Auth: auth,
 		URL:  gitURL,
diff --git a/pkg/git/submodules/testdata/multiple_submodules/.gitmodules b/pkg/git/submodules/testdata/multiple_submodules/.gitmodules
deleted file mode 100644
index a1386f3..0000000
--- a/pkg/git/submodules/testdata/multiple_submodules/.gitmodules
+++ /dev/null
@@ -1,13 +0,0 @@
-[submodule "images/cheese/mount/bar"]
-	path = images/cheese/mount/bar
-	url = https://github.com/foo/bar.git
-	branch = "v1.2.3"
-# next is a duplicate submodule repo but under a different path, test must update both
-[submodule "images/wine/mount/bar"]
-	path = images/wine/mount/bar
-	url = https://github.com/foo/bar.git
-	branch = "v1.2.3"
-[submodule "images/beer/mount/bar"]
-	path = images/beer/mount/bar
-	url = https://github.com/foo/beer.git
-	branch = "v7.8.9"
diff --git a/pkg/git/submodules/update.go b/pkg/git/submodules/update.go
deleted file mode 100644
index 671317f..0000000
--- a/pkg/git/submodules/update.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package submodules
-
-import (
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/config"
-
-	wgit "github.com/wolfi-dev/wolfictl/pkg/git"
-)
-
-// Update will modify a .gitmodules file and perform a `git submodule update --remote`
-func Update(dir, owner, repo, version string, wt *git.Worktree) error {
-	// update the .gitmodule config file
-	submodules, err := updateConfigFile(dir, owner, repo, version)
-	if err != nil {
-		return fmt.Errorf("failed to update gitmodules file: %w", err)
-	}
-
-	if _, err = wt.Add(".gitmodules"); err != nil {
-		return fmt.Errorf("failed to git add .gitmodules: %w", err)
-	}
-
-	// git submodule update --remote
-	for _, submodule := range submodules {
-		err := updateSubmodules(submodule.Name, wt)
-		if err != nil {
-			return fmt.Errorf("failed to update gitmodules: %w", err)
-		}
-
-		// need to fall back to using git CLI as go-git hasn't implemented adding submodules
-		// there are errors with empty dir that are references to git module shas when adding via go-git
-		//nolint:gosec
-		cmd := exec.Command("git", "add", submodule.Path)
-		cmd.Dir = dir
-		err = cmd.Run()
-		if err != nil {
-			return fmt.Errorf("failed to git add %s %w", submodule.Path, err)
-		}
-	}
-
-	return nil
-}
-
-func updateConfigFile(dir, owner, repo, version string) (map[string]*config.Submodule, error) {
-	updatedSubmodules := make(map[string]*config.Submodule)
-
-	filename := filepath.Join(dir, ".gitmodules")
-	data, err := os.ReadFile(filename)
-	if err != nil {
-		return nil, fmt.Errorf("failed to read gitmodules file %s: %w", filename, err)
-	}
-
-	cfg := config.NewModules()
-
-	err = cfg.Unmarshal(data)
-	if err != nil {
-		return updatedSubmodules, err
-	}
-
-	// loop through all submodules in the .gitmodules file and set the version for any matching URLs
-	for k, submodule := range cfg.Submodules {
-		if strings.HasSuffix(submodule.URL, fmt.Sprintf("%s/%s.git", owner, repo)) {
-			submodule.Branch = version
-			updatedSubmodules[k] = submodule
-		}
-	}
-
-	// modify the .gitmodules file if we have updated any submodules
-	if len(updatedSubmodules) > 0 {
-		info, err := os.Stat(filename)
-		if err != nil {
-			return updatedSubmodules, err
-		}
-		output, err := cfg.Marshal()
-		if err != nil {
-			return updatedSubmodules, err
-		}
-		return updatedSubmodules, os.WriteFile(filename, output, info.Mode())
-	}
-	return updatedSubmodules, err
-}
-
-func updateSubmodules(submodule string, wt *git.Worktree) error {
-	sub, err := wt.Submodule(submodule)
-	if err != nil {
-		return err
-	}
-
-	err = sub.Init()
-	if err != nil {
-		return err
-	}
-
-	sr, err := sub.Repository()
-	if err != nil {
-		return err
-	}
-
-	sw, err := sr.Worktree()
-	if err != nil {
-		return err
-	}
-
-	err = sw.Pull(&git.PullOptions{
-		RemoteName: "origin",
-		Auth:       wgit.GetGitAuth(),
-		Depth:      1,
-	})
-
-	return err
-}
diff --git a/pkg/git/submodules/update_test.go b/pkg/git/submodules/update_test.go
deleted file mode 100644
index c6da6db..0000000
--- a/pkg/git/submodules/update_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package submodules
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-
-	"github.com/go-git/go-git/v5/config"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestSubmodules_updateConfigFile(t *testing.T) {
-	dir := t.TempDir()
-
-	data, err := os.ReadFile(filepath.Join("testdata", "multiple_submodules", ".gitmodules"))
-	assert.NoError(t, err)
-
-	err = os.WriteFile(filepath.Join(dir, ".gitmodules"), data, 0o600)
-	assert.NoError(t, err)
-
-	_, err = updateConfigFile(dir, "foo", "bar", "v1.2.4")
-	assert.NoError(t, err)
-
-	data, err = os.ReadFile(filepath.Join(dir, ".gitmodules"))
-	assert.NoError(t, err)
-
-	cfg := config.NewModules()
-	err = cfg.Unmarshal(data)
-	assert.NoError(t, err)
-
-	assert.Equal(t, "v1.2.4", cfg.Submodules["images/cheese/mount/bar"].Branch)
-	assert.Equal(t, "v1.2.4", cfg.Submodules["images/wine/mount/bar"].Branch)
-}
diff --git a/pkg/git/tag.go b/pkg/git/tag.go
index e86a38a..f6d2001 100644
--- a/pkg/git/tag.go
+++ b/pkg/git/tag.go
@@ -49,15 +49,20 @@ func PushTag(dir, tagName string) error {
 	if err != nil {
 		return err
 	}
 	remoteURL := fmt.Sprintf("https://github.com/%s/%s.git", gitURL.Organisation, gitURL.Name)
 
+	gitAuth, err := GetGitAuth(remoteURL)
+	if err != nil {
+		return fmt.Errorf("failed to get git auth: %w", err)
+	}
+
 	po := &git.PushOptions{
 		RemoteName: "origin",
 		RemoteURL:  remoteURL,
 		RefSpecs:   []config.RefSpec{config.RefSpec(fmt.Sprintf("refs/tags/%s:refs/tags/%s", tagName, tagName))},
-		Auth:       GetGitAuth(),
+		Auth:       gitAuth,
 	}
 
 	err = r.Push(po)
 
 	if err != nil {
diff --git a/pkg/update/deps/cleanup.go b/pkg/update/deps/cleanup.go
index 3a5a1f1..d9b20ae 100644
--- a/pkg/update/deps/cleanup.go
+++ b/pkg/update/deps/cleanup.go
@@ -38,17 +38,22 @@ func gitCheckout(p *config.Pipeline, dir string, mutations map[string]string) er
 	evaluatedTag, err := util.MutateStringFromMap(mutations, tagValue)
 	if err != nil {
 		return err
 	}
 
+	gitAuth, err := wgit.GetGitAuth(repoValue)
+	if err != nil {
+		return fmt.Errorf("failed to get git auth: %w", err)
+	}
+
 	cloneOpts := &git.CloneOptions{
 		URL:               repoValue,
 		ReferenceName:     plumbing.ReferenceName(fmt.Sprintf("refs/tags/%s", evaluatedTag)),
 		Progress:          os.Stdout,
 		RecurseSubmodules: git.NoRecurseSubmodules,
 		Depth:             1,
-		Auth:              wgit.GetGitAuth(),
+		Auth:              gitAuth,
 	}
 
 	log.Printf("cloning sources from %s tag %s into a temporary directory '%s', this may take a while", repoValue, dir, evaluatedTag)
 
 	maxRetries := 3
diff --git a/pkg/update/package.go b/pkg/update/package.go
index 0b7d5c2..7866c3c 100644
--- a/pkg/update/package.go
+++ b/pkg/update/package.go
@@ -51,15 +51,20 @@ func (o *PackageOptions) UpdatePackageCmd(ctx context.Context) error {
 		o.Logger.Printf("using working directory %s", tempDir)
 	} else {
 		defer os.Remove(tempDir)
 	}
 
+	gitAuth, err := wolfigit.GetGitAuth(o.TargetRepo)
+	if err != nil {
+		return fmt.Errorf("failed to get git auth: %w", err)
+	}
+
 	cloneOpts := &git.CloneOptions{
 		URL:               o.TargetRepo,
 		Progress:          os.Stdout,
 		RecurseSubmodules: git.NoRecurseSubmodules,
-		Auth:              wolfigit.GetGitAuth(),
+		Auth:              gitAuth,
 		Depth:             1,
 	}
 
 	repo, err := git.PlainClone(tempDir, false, cloneOpts)
 	if err != nil {
@@ -117,16 +122,22 @@ func (o *PackageOptions) updateAdvisories(ctx context.Context, repo *git.Reposit
 	}
 	gitURL, err := wolfigit.GetRemoteURLFromDir(currentDir)
 	if err != nil {
 		return err
 	}
+
+	gitAuth, err := wolfigit.GetGitAuth(gitURL.RawURL)
+	if err != nil {
+		return fmt.Errorf("failed to get git auth: %w", err)
+	}
+
 	// checkout repo into tmp dir so we know we are working on a clean HEAD
 	cloneOpts := &git.CloneOptions{
 		URL:               gitURL.RawURL,
 		RecurseSubmodules: git.NoRecurseSubmodules,
 		ShallowSubmodules: true,
-		Auth:              wolfigit.GetGitAuth(),
+		Auth:              gitAuth,
 		Tags:              git.AllTags,
 		Depth:             20,
 	}
 
 	tempDir, err := os.MkdirTemp("", "wolfictl")
diff --git a/pkg/update/update.go b/pkg/update/update.go
index 7ce7b0b..022ee6e 100644
--- a/pkg/update/update.go
+++ b/pkg/update/update.go
@@ -1,11 +1,10 @@
 package update
 
 import (
 	"bufio"
 	"context"
-	"errors"
 	"fmt"
 	"log"
 	"net/http"
 	"os"
 	"os/exec"
@@ -23,11 +22,10 @@ import (
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/google/go-github/v58/github"
 	"github.com/google/uuid"
 	"github.com/wolfi-dev/wolfictl/pkg/gh"
 	wgit "github.com/wolfi-dev/wolfictl/pkg/git"
-	"github.com/wolfi-dev/wolfictl/pkg/git/submodules"
 	http2 "github.com/wolfi-dev/wolfictl/pkg/http"
 	"github.com/wolfi-dev/wolfictl/pkg/melange"
 	"github.com/wolfi-dev/wolfictl/pkg/update/deps"
 	wolfiversions "github.com/wolfi-dev/wolfictl/pkg/versions"
 	"golang.org/x/exp/maps"
@@ -133,16 +131,21 @@ func (o *Options) Update(ctx context.Context) error {
 			o.Logger.Printf("using working directory %s", tempDir)
 		} else {
 			defer os.Remove(tempDir)
 		}
 
+		gitAuth, err := wgit.GetGitAuth(o.RepoURI)
+		if err != nil {
+			return fmt.Errorf("failed to get git auth: %w", err)
+		}
+
 		cloneOpts := &git.CloneOptions{
 			URL:               o.RepoURI,
 			Progress:          os.Stdout,
 			RecurseSubmodules: git.DefaultSubmoduleRecursionDepth,
 			ShallowSubmodules: true,
-			Auth:              wgit.GetGitAuth(),
+			Auth:              gitAuth,
 			Depth:             1,
 		}
 
 		repo, err = git.PlainClone(tempDir, false, cloneOpts)
 		if err != nil {
@@ -399,24 +402,10 @@ func (o *Options) updateGitPackage(ctx context.Context, repo *git.Repository, pa
 	err = o.updateMakefile(root, packageName, newVersion.Version, worktree)
 	if err != nil {
 		return fmt.Sprintf("failed to update Makefile: %s", err.Error()), nil
 	}
 
-	// if mapping data has a strip prefix, add it back in to the version for when updating git modules
-	latestVersionWithPrefix := newVersion.Version
-	ghm := o.PackageConfigs[packageName].Config.Update.GitHubMonitor
-	if ghm != nil {
-		if ghm.StripPrefix != "" {
-			latestVersionWithPrefix = ghm.StripPrefix + latestVersionWithPrefix
-		}
-	}
-	// some repos could use git submodules, let's check if a submodule file exists and bump any matching packages
-	err = o.updateGitModules(root, packageName, latestVersionWithPrefix, worktree)
-	if err != nil {
-		return fmt.Sprintf("failed to update git modules: %s", err.Error()), nil
-	}
-
 	// now make sure update config is configured
 	updated, err := config.ParseConfiguration(ctx, filepath.Join(root, pc.Filename))
 	if err != nil {
 		return "", fmt.Errorf("failed to parse %v", err)
 	}
@@ -532,39 +521,10 @@ func (o *Options) updateMakefile(tempDir, packageName, latestVersion string, wor
 		return fmt.Errorf("failed to git add Makefile: %w", err)
 	}
 	return nil
 }
 
-// some melange config repos use submodules to pull in git repositories into the source dir before the melange pipelines run
-// this function is a noop if no git submodules exist
-func (o *Options) updateGitModules(dir, packageName, version string, wt *git.Worktree) error {
-	// if no gitmodules file exist this in a noop
-	if _, err := os.Stat(filepath.Join(dir, ".gitmodules")); errors.Is(err, os.ErrNotExist) {
-		return nil
-	}
-
-	ghm := o.PackageConfigs[packageName].Config.Update.GitHubMonitor
-
-	if ghm == nil {
-		o.Logger.Printf("package %s  is not a github repo in mapping data, not attempting to bump gitmodules", packageName)
-		return nil
-	}
-
-	if ghm.Identifier == "" {
-		o.Logger.Printf("no identifier found in mapping data for package %s, not attempting to bump gitmodules", packageName)
-		return nil
-	}
-
-	parts := strings.Split(ghm.Identifier, "/")
-	if len(parts) != 2 {
-		o.Logger.Printf("identifier doesn't look like a github owner/repo in mapping data for package %s, not attempting to bump gitmodules", packageName)
-		return nil
-	}
-
-	return submodules.Update(dir, parts[0], parts[1], version, wt)
-}
-
 // create a unique branch
 func (o *Options) createBranch(repo *git.Repository) (plumbing.ReferenceName, error) {
 	name := uuid.New().String()
 
 	headRef, err := repo.Head()
@@ -635,15 +595,19 @@ func (o *Options) proposeChanges(ctx context.Context, repo *git.Repository, ref
 	if err != nil {
 		return "", err
 	}
 	o.Logger.Printf("proposeChanges: %s git status: %s", packageName, string(rs))
 
+	gitAuth, err := wgit.GetGitAuth(o.RepoURI)
+	if err != nil {
+		return "", fmt.Errorf("failed to get git auth: %w", err)
+	}
+
 	// setup githubReleases auth using standard environment variables
 	pushOpts := &git.PushOptions{
 		RemoteName: "origin",
-		Auth:       wgit.GetGitAuth(),
-		Progress:   os.Stdout, // todo remove if this doesn't help: extra logging to help debug intermittent "object not found" when pushing
+		Auth:       gitAuth,
 	}
 
 	// push the version update changes to our working branch
 	if err := repo.Push(pushOpts); err != nil {
 		if err.Error() == "authorization failed" {

Vulnerable code: 		Auth: wgit.GetGitAuth(),
	err := ds.repo.PushContext(ctx, &git.PushOptions{
		Auth:      wgit.GetGitAuth(),
func GetGitAuth() *gitHttp.BasicAuth {
		return nil
	}
		auth = GetGitAuth()
[submodule "images/cheese/mount/bar"]
	path = images/cheese/mount/bar
	url = https://github.com/foo/bar.git
	branch = "v1.2.3"
# next is a duplicate submodule repo but under a different path, test must update both
[submodule "images/wine/mount/bar"]
	path = images/wine/mount/bar
	url = https://github.com/foo/bar.git
	branch = "v1.2.3"
[submodule "images/beer/mount/bar"]
	path = images/beer/mount/bar
	url = https://github.com/foo/beer.git
	branch = "v7.8.9"
package submodules

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"

	wgit "github.com/wolfi-dev/wolfictl/pkg/git"
)

// Update will modify a .gitmodules file and perform a `git submodule update --remote`
func Update(dir, owner, repo, version string, wt *git.Worktree) error {
	// update the .gitmodule config file
	submodules, err := updateConfigFile(dir, owner, repo, version)
	if err != nil {
		return fmt.Errorf("failed to update gitmodules file: %w", err)
	}

	if _, err = wt.Add(".gitmodules"); err != nil {
		return fmt.Errorf("failed to git add .gitmodules: %w", err)
	}

	// git submodule update --remote
	for _, submodule := range submodules {
		err := updateSubmodules(submodule.Name, wt)
		if err != nil {
			return fmt.Errorf("failed to update gitmodules: %w", err)
		}

		// need to fall back to using git CLI as go-git hasn't implemented adding submodules
		// there are errors with empty dir that are references to git module shas when adding via go-git
		//nolint:gosec
		cmd := exec.Command("git", "add", submodule.Path)
		cmd.Dir = dir
		err = cmd.Run()
		if err != nil {
			return fmt.Errorf("failed to git add %s %w", submodule.Path, err)
		}
	}

	return nil
}

func updateConfigFile(dir, owner, repo, version string) (map[string]*config.Submodule, error) {
	updatedSubmodules := make(map[string]*config.Submodule)

	filename := filepath.Join(dir, ".gitmodules")
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("failed to read gitmodules file %s: %w", filename, err)
	}

	cfg := config.NewModules()

	err = cfg.Unmarshal(data)
	if err != nil {
		return updatedSubmodules, err
	}

	// loop through all submodules in the .gitmodules file and set the version for any matching URLs
	for k, submodule := range cfg.Submodules {
		if strings.HasSuffix(submodule.URL, fmt.Sprintf("%s/%s.git", owner, repo)) {
			submodule.Branch = version
			updatedSubmodules[k] = submodule
		}
	}

	// modify the .gitmodules file if we have updated any submodules
	if len(updatedSubmodules) > 0 {
		info, err := os.Stat(filename)
		if err != nil {
			return updatedSubmodules, err
		}
		output, err := cfg.Marshal()
		if err != nil {
			return updatedSubmodules, err
		}
		return updatedSubmodules, os.WriteFile(filename, output, info.Mode())
	}
	return updatedSubmodules, err
}

func updateSubmodules(submodule string, wt *git.Worktree) error {
	sub, err := wt.Submodule(submodule)
	if err != nil {
		return err
	}

	err = sub.Init()
	if err != nil {
		return err
	}

	sr, err := sub.Repository()
	if err != nil {
		return err
	}

	sw, err := sr.Worktree()
	if err != nil {
		return err
	}

	err = sw.Pull(&git.PullOptions{
		RemoteName: "origin",
		Auth:       wgit.GetGitAuth(),
		Depth:      1,
	})

	return err
}
package submodules

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/go-git/go-git/v5/config"

	"github.com/stretchr/testify/assert"
)

func TestSubmodules_updateConfigFile(t *testing.T) {
	dir := t.TempDir()

	data, err := os.ReadFile(filepath.Join("testdata", "multiple_submodules", ".gitmodules"))
	assert.NoError(t, err)

	err = os.WriteFile(filepath.Join(dir, ".gitmodules"), data, 0o600)
	assert.NoError(t, err)

	_, err = updateConfigFile(dir, "foo", "bar", "v1.2.4")
	assert.NoError(t, err)

	data, err = os.ReadFile(filepath.Join(dir, ".gitmodules"))
	assert.NoError(t, err)

	cfg := config.NewModules()
	err = cfg.Unmarshal(data)
	assert.NoError(t, err)

	assert.Equal(t, "v1.2.4", cfg.Submodules["images/cheese/mount/bar"].Branch)
	assert.Equal(t, "v1.2.4", cfg.Submodules["images/wine/mount/bar"].Branch)
}
		Auth:       GetGitAuth(),
		Auth:              wgit.GetGitAuth(),
		Auth:              wolfigit.GetGitAuth(),
		Auth:              wolfigit.GetGitAuth(),
	"errors"
	"github.com/wolfi-dev/wolfictl/pkg/git/submodules"
			Auth:              wgit.GetGitAuth(),
	// if mapping data has a strip prefix, add it back in to the version for when updating git modules
	latestVersionWithPrefix := newVersion.Version
	ghm := o.PackageConfigs[packageName].Config.Update.GitHubMonitor
	if ghm != nil {
		if ghm.StripPrefix != "" {
			latestVersionWithPrefix = ghm.StripPrefix + latestVersionWithPrefix
		}
	}
	// some repos could use git submodules, let's check if a submodule file exists and bump any matching packages
	err = o.updateGitModules(root, packageName, latestVersionWithPrefix, worktree)
	if err != nil {
		return fmt.Sprintf("failed to update git modules: %s", err.Error()), nil
	}

// some melange config repos use submodules to pull in git repositories into the source dir before the melange pipelines run
// this function is a noop if no git submodules exist
func (o *Options) updateGitModules(dir, packageName, version string, wt *git.Worktree) error {
	// if no gitmodules file exist this in a noop
	if _, err := os.Stat(filepath.Join(dir, ".gitmodules")); errors.Is(err, os.ErrNotExist) {
		return nil
	}

	ghm := o.PackageConfigs[packageName].Config.Update.GitHubMonitor

	if ghm == nil {
		o.Logger.Printf("package %s  is not a github repo in mapping data, not attempting to bump gitmodules", packageName)
		return nil
	}

	if ghm.Identifier == "" {
		o.Logger.Printf("no identifier found in mapping data for package %s, not attempting to bump gitmodules", packageName)
		return nil
	}

	parts := strings.Split(ghm.Identifier, "/")
	if len(parts) != 2 {
		o.Logger.Printf("identifier doesn't look like a github owner/repo in mapping data for package %s, not attempting to bump gitmodules", packageName)
		return nil
	}

	return submodules.Update(dir, parts[0], parts[1], version, wt)
}

		Auth:       wgit.GetGitAuth(),
		Progress:   os.Stdout, // todo remove if this doesn't help: extra logging to help debug intermittent "object not found" when pushing
Fixed code: 	gitAuth, err := wgit.GetGitAuth(opts.Distro.Absolute.AdvisoriesHTTPSCloneURL())
	if err != nil {
		return nil, fmt.Errorf("getting git auth: %w", err)
	}

		Auth: gitAuth,
	gitAuth, err := wgit.GetGitAuth(ds.distro.Absolute.AdvisoriesHTTPSCloneURL())
	if err != nil {
		return fmt.Errorf("getting git auth: %w", err)
	}

	err = ds.repo.PushContext(ctx, &git.PushOptions{
		Auth:      gitAuth,
	"log/slog"
	"github.com/chainguard-dev/clog"

func GetGitAuth(gitURL string) (*gitHttp.BasicAuth, error) {
	logger := clog.NewLogger(slog.Default()) // TODO: plumb through context, everywhere

	parsedURL, err := ParseGitURL(gitURL)
	if err != nil {
		return nil, fmt.Errorf("failed to parse git URL %q: %w", gitURL, err)
	}

	// Only use GITHUB_TOKEN for github.com URLs
	if parsedURL.Host != "github.com" {
		logger.Warnf("host %q is not github.com, not using GITHUB_TOKEN for authentication", parsedURL.Host)
		return nil, nil
	}

		return &gitHttp.BasicAuth{}, nil
	}, nil
		auth, err = GetGitAuth(gitURL)
		if err != nil {
			return dir, fmt.Errorf("unable to get git auth: %w", err)
		}
	gitAuth, err := GetGitAuth(remoteURL)
	if err != nil {
		return fmt.Errorf("failed to get git auth: %w", err)
	}

		Auth:       gitAuth,
	gitAuth, err := wgit.GetGitAuth(repoValue)
	if err != nil {
		return fmt.Errorf("failed to get git auth: %w", err)
	}

		Auth:              gitAuth,
	gitAuth, err := wolfigit.GetGitAuth(o.TargetRepo)
	if err != nil {
		return fmt.Errorf("failed to get git auth: %w", err)
	}

		Auth:              gitAuth,

	gitAuth, err := wolfigit.GetGitAuth(gitURL.RawURL)
	if err != nil {
		return fmt.Errorf("failed to get git auth: %w", err)
	}

		Auth:              gitAuth,
		gitAuth, err := wgit.GetGitAuth(o.RepoURI)
		if err != nil {
			return fmt.Errorf("failed to get git auth: %w", err)
		}

			Auth:              gitAuth,
	gitAuth, err := wgit.GetGitAuth(o.RepoURI)
	if err != nil {
		return "", fmt.Errorf("failed to get git auth: %w", err)
	}

		Auth:       gitAuth,