mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2026-05-16 13:20:35 +00:00
[INS-397] Fix git version parser panic on non-numeric patch versions (#4882)
git built from source can report versions like "2.52.gaea8cc3", causing an index out of range panic. The patch component is unused, so the regex now captures only major.minor. Extract the helper into a shared pkg/gitcmd package to remove duplication with the azureapimanagement detector. Fixes #4801
This commit is contained in:
@@ -3,10 +3,8 @@ package repositorykey
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
@@ -14,6 +12,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
|
||||
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
|
||||
)
|
||||
|
||||
@@ -100,36 +99,8 @@ func (s Scanner) Description() string {
|
||||
return "Azure API Management Repository Keys provide access to the API Management (APIM) configuration repository, allowing users to directly interact with and modify API definitions, policies, and settings. These keys enable programmatic access to APIM's Git-based repository, where configurations can be cloned, edited, and pushed back to apply changes. They are primarily used for managing API configurations as code, automating deployments, and synchronizing APIM settings across environments."
|
||||
}
|
||||
|
||||
func gitCmdCheck() error {
|
||||
if errors.Is(exec.Command("git").Run(), exec.ErrNotFound) {
|
||||
return fmt.Errorf("'git' command not found in $PATH. Make sure git is installed and included in $PATH")
|
||||
}
|
||||
|
||||
// Check the version is greater than or equal to 2.20.0
|
||||
out, err := exec.Command("git", "--version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check git version: %w", err)
|
||||
}
|
||||
|
||||
// Extract the version string using a regex to find the version numbers
|
||||
var regex = regexp.MustCompile(`\d+\.\d+\.\d+`)
|
||||
|
||||
versionStr := regex.FindString(string(out))
|
||||
versionParts := strings.Split(versionStr, ".")
|
||||
|
||||
// Parse version numbers
|
||||
major, _ := strconv.Atoi(versionParts[0])
|
||||
minor, _ := strconv.Atoi(versionParts[1])
|
||||
|
||||
// Compare with version 2.20.0<=x<3.0.0
|
||||
if major == 2 && minor >= 20 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("git version is %s, but must be greater than or equal to 2.20.0, and less than 3.0.0", versionStr)
|
||||
}
|
||||
|
||||
func verifyUrlPassword(_ context.Context, repoUrl, user, password string) (bool, error) {
|
||||
if err := gitCmdCheck(); err != nil {
|
||||
if err := gitcmd.CheckVersion(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Package gitcmd provides helpers for interacting with the local git binary.
|
||||
package gitcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
)
|
||||
|
||||
// gitVersionRegex captures the major and minor numbers from `git --version` output.
|
||||
// We intentionally do not capture the patch version: git built from source can report
|
||||
// a non-numeric patch like "git version 2.52.gaea8cc3", and the patch is unused here.
|
||||
var gitVersionRegex = regexp.MustCompile(`(\d+)\.(\d+)`)
|
||||
|
||||
// CheckVersion checks if git is installed and meets 2.20.0<=x<3.0.0 version requirements.
|
||||
func CheckVersion() error {
|
||||
if errors.Is(exec.Command("git").Run(), exec.ErrNotFound) {
|
||||
return fmt.Errorf("'git' command not found in $PATH. Make sure git is installed and included in $PATH")
|
||||
}
|
||||
|
||||
// Check the version is greater than or equal to 2.20.0
|
||||
out, err := exec.Command("git", "--version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check git version: %w", err)
|
||||
}
|
||||
|
||||
major, minor, err := parseGitVersion(string(out))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare with version 2.20.0<=x<3.0.0
|
||||
if major == 2 && minor >= 20 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("git version is %d.%d, but must be greater than or equal to 2.20.0, and less than 3.0.0", major, minor)
|
||||
}
|
||||
|
||||
// parseGitVersion extracts the major and minor numbers from `git --version` output.
|
||||
func parseGitVersion(out string) (major, minor int, err error) {
|
||||
matches := gitVersionRegex.FindStringSubmatch(out)
|
||||
if len(matches) < 3 {
|
||||
return 0, 0, fmt.Errorf("failed to parse git version from %q", strings.TrimSpace(out))
|
||||
}
|
||||
// Errors are impossible here since the regex only matches digits.
|
||||
major, _ = strconv.Atoi(matches[1])
|
||||
minor, _ = strconv.Atoi(matches[2])
|
||||
return major, minor, nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package gitcmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseGitVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
out string
|
||||
wantMajor int
|
||||
wantMinor int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "standard semver",
|
||||
out: "git version 2.34.1\n",
|
||||
wantMajor: 2,
|
||||
wantMinor: 34,
|
||||
},
|
||||
{
|
||||
name: "non-numeric patch (built from source)",
|
||||
out: "git version 2.52.gaea8cc3\n",
|
||||
wantMajor: 2,
|
||||
wantMinor: 52,
|
||||
},
|
||||
{
|
||||
name: "apple git suffix",
|
||||
out: "git version 2.39.2 (Apple Git-143)\n",
|
||||
wantMajor: 2,
|
||||
wantMinor: 39,
|
||||
},
|
||||
{
|
||||
name: "windows git suffix",
|
||||
out: "git version 2.39.2.windows.1\n",
|
||||
wantMajor: 2,
|
||||
wantMinor: 39,
|
||||
},
|
||||
{
|
||||
name: "no patch component",
|
||||
out: "git version 2.20\n",
|
||||
wantMajor: 2,
|
||||
wantMinor: 20,
|
||||
},
|
||||
{
|
||||
name: "no version present",
|
||||
out: "git is not a version\n",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty output",
|
||||
out: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
major, minor, err := parseGitVersion(tt.out)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantMajor, major)
|
||||
assert.Equal(t, tt.wantMinor, minor)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
)
|
||||
|
||||
// Extract the version string using a regex to find the version numbers
|
||||
var regex = regexp.MustCompile(`\d+\.\d+\.\d+`)
|
||||
|
||||
// CmdCheck checks if git is installed and meets 2.20.0<=x<3.0.0 version requirements.
|
||||
func CmdCheck() error {
|
||||
if errors.Is(exec.Command("git").Run(), exec.ErrNotFound) {
|
||||
return fmt.Errorf("'git' command not found in $PATH. Make sure git is installed and included in $PATH")
|
||||
}
|
||||
|
||||
// Check the version is greater than or equal to 2.20.0
|
||||
out, err := exec.Command("git", "--version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check git version: %w", err)
|
||||
}
|
||||
|
||||
versionStr := regex.FindString(string(out))
|
||||
versionParts := strings.Split(versionStr, ".")
|
||||
|
||||
// Parse version numbers
|
||||
major, _ := strconv.Atoi(versionParts[0])
|
||||
minor, _ := strconv.Atoi(versionParts[1])
|
||||
|
||||
// Compare with version 2.20.0<=x<3.0.0
|
||||
if major == 2 && minor >= 20 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("git version is %s, but must be greater than or equal to 2.20.0, and less than 3.0.0", versionStr)
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitparse"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
@@ -214,7 +215,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so
|
||||
concurrency = runtime.NumCPU()
|
||||
}
|
||||
|
||||
if err = CmdCheck(); err != nil {
|
||||
if err = gitcmd.CheckVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -614,7 +615,7 @@ func executeClone(ctx context.Context, params cloneParams) (*git.Repository, err
|
||||
//
|
||||
// Pinging using other authentication methods is only unimplemented because there's been no pressing need for it yet.
|
||||
func PingRepoUsingToken(ctx context.Context, token, gitUrl, user string) error {
|
||||
if err := CmdCheck(); err != nil {
|
||||
if err := gitcmd.CheckVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
lsUrl, err := GitURLParse(gitUrl)
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/giturl"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
@@ -213,7 +214,7 @@ func (c *filteredRepoCache) wantRepo(s string) bool {
|
||||
|
||||
// Init returns an initialized GitHub source.
|
||||
func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {
|
||||
err := git.CmdCheck()
|
||||
err := gitcmd.CheckVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/giturl"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
@@ -68,7 +69,7 @@ func (s *Source) JobID() sources.JobID {
|
||||
|
||||
// Init returns an initialized GitHubExperimental source.
|
||||
func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {
|
||||
err := git.CmdCheck()
|
||||
err := gitcmd.CheckVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/giturl"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
@@ -166,7 +167,7 @@ func (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sou
|
||||
s.jobPool = &errgroup.Group{}
|
||||
s.jobPool.SetLimit(concurrency)
|
||||
|
||||
if err := git.CmdCheck(); err != nil {
|
||||
if err := gitcmd.CheckVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/gitcmd"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/giturl"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
|
||||
@@ -169,7 +170,7 @@ func (c *filteredRepoCache) includeRepo(s string) bool {
|
||||
|
||||
// Init returns an initialized HuggingFace source.
|
||||
func (s *Source) Init(ctx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {
|
||||
err := git.CmdCheck()
|
||||
err := gitcmd.CheckVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user