CVE-2024-35183 (Go, published 2024-05-15T22:15Z)
Description: 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.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/wolfi-dev/wolfictl
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 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>


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,