CVE-2024-35183
Description
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.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/wolfi-dev/wolfictlGo | < 0.16.10 | 0.16.10 |
Patches
2403e93569f46Git based commands, only use GITHUB_TOKEN when interacting with GitHub's API
9 files changed · +73 −222
pkg/advisory/data_session.go+13 −3 modified@@ -51,10 +51,15 @@ func NewDataSession(ctx context.Context, opts DataSessionOptions) (*DataSession, 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) @@ -168,9 +173,14 @@ 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)
pkg/git/git.go+23 −4 modified@@ -2,12 +2,15 @@ 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" @@ -19,20 +22,33 @@ import ( 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 { @@ -182,7 +198,10 @@ func TempClone(gitURL, hash string, useAuth bool) (repoDir string, err error) { 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{
pkg/git/submodules/testdata/multiple_submodules/.gitmodules+0 −13 removed@@ -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"
pkg/git/submodules/update.go+0 −116 removed@@ -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 -}
pkg/git/submodules/update_test.go+0 −34 removed@@ -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) -}
pkg/git/tag.go+6 −1 modified@@ -51,11 +51,16 @@ func PushTag(dir, tagName string) error { } 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)
pkg/update/deps/cleanup.go+6 −1 modified@@ -40,13 +40,18 @@ func gitCheckout(p *config.Pipeline, dir string, mutations map[string]string) er 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)
pkg/update/package.go+13 −2 modified@@ -53,11 +53,16 @@ func (o *PackageOptions) UpdatePackageCmd(ctx context.Context) error { 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, } @@ -119,12 +124,18 @@ func (o *PackageOptions) updateAdvisories(ctx context.Context, repo *git.Reposit 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, }
pkg/update/update.go+12 −48 modified@@ -3,7 +3,6 @@ package update import ( "bufio" "context" - "errors" "fmt" "log" "net/http" @@ -25,7 +24,6 @@ import ( "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" @@ -135,12 +133,17 @@ func (o *Options) Update(ctx context.Context) error { 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, } @@ -401,20 +404,6 @@ func (o *Options) updateGitPackage(ctx context.Context, repo *git.Repository, pa 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 { @@ -534,35 +523,6 @@ func (o *Options) updateMakefile(tempDir, packageName, latestVersion string, wor 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() @@ -637,11 +597,15 @@ func (o *Options) proposeChanges(ctx context.Context, repo *git.Repository, ref } 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
0d06e1578300wolfictl update: use git auth when cloning melange config repo
4 files changed · +16 −7
pkg/git/submodules/testdata/multiple_submodules/.gitmodules+0 −0 renamedpkg/git/submodules/update.go+2 −2 modified@@ -15,7 +15,7 @@ import ( func Update(dir, owner, repo, version string, wt *git.Worktree) error { // update the .gitmodule config file - submodules, err := updateConfigfile(dir, owner, repo, version) + submodules, err := updateConfigFile(dir, owner, repo, version) if err != nil { return errors.Wrap(err, "failed to update gitmodules file") } @@ -30,7 +30,7 @@ func Update(dir, owner, repo, version string, wt *git.Worktree) error { return nil } -func updateConfigfile(dir, owner, repo, version string) ([]string, error) { +func updateConfigFile(dir, owner, repo, version string) ([]string, error) { var submodules []string filename := filepath.Join(dir, ".gitmodules") data, err := os.ReadFile(filename)
pkg/git/submodules/update_test.go+3 −3 modified@@ -10,17 +10,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSubmodules_update(t *testing.T) { +func TestSubmodules_updateConfigFile(t *testing.T) { dir := t.TempDir() - data, err := os.ReadFile(filepath.Join("testdata", ".gitmodules")) + data, err := os.ReadFile(filepath.Join("testdata", "multiple_submodules", ".gitmodules")) assert.NoError(t, err) err = os.WriteFile(filepath.Join(dir, ".gitmodules"), data, 0666) assert.NoError(t, err) - _, err = updateConfigfile(dir, "foo", "bar", "v1.2.4") + _, err = updateConfigFile(dir, "foo", "bar", "v1.2.4") assert.NoError(t, err) data, err = os.ReadFile(filepath.Join(dir, ".gitmodules"))
pkg/update/update.go+11 −2 modified@@ -104,10 +104,19 @@ func (o *Options) Update() error { defer os.Remove(tempDir) } - repo, err := git.PlainClone(tempDir, false, &git.CloneOptions{ + cloneOpts := &git.CloneOptions{ URL: o.RepoURI, Progress: os.Stdout, - }) + } + gitToken := os.Getenv("GITHUB_TOKEN") + if gitToken != "" { + cloneOpts.Auth = &gitHttp.BasicAuth{ + Username: "abc123", + Password: gitToken, + } + } + + repo, err := git.PlainClone(tempDir, false, cloneOpts) if err != nil { return fmt.Errorf("failed to clone repository %s into %s: %w", o.RepoURI, tempDir, err) }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-8fg7-hp93-qhvrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-35183ghsaADVISORY
- github.com/wolfi-dev/wolfictl/blob/488b53823350caa706de3f01ec0eded9350c7da7/pkg/update/update.gonvdWEB
- github.com/wolfi-dev/wolfictl/blob/4dd6c95abb4bc0f9306350a8601057bd7a92bded/pkg/update/deps/cleanup.gonvdWEB
- github.com/wolfi-dev/wolfictl/blob/6d99909f7b1aa23f732d84dad054b02a61f530e6/pkg/git/git.gonvdWEB
- github.com/wolfi-dev/wolfictl/commit/0d06e1578300327c212dda26a5ab31d09352b9d0nvdWEB
- github.com/wolfi-dev/wolfictl/commit/403e93569f46766b4e26e06cf9cd0cae5ee0c2a2nvdWEB
- github.com/wolfi-dev/wolfictl/security/advisories/GHSA-8fg7-hp93-qhvrnvdWEB
News mentions
0No linked articles in our index yet.