Skip to content

Commit

Permalink
feat(detect): support reading .gitleaksignore using git show
Browse files Browse the repository at this point in the history
  • Loading branch information
savely-krasovsky committed Aug 11, 2023
1 parent f0dcd4d commit a6de2d0
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 16 deletions.
37 changes: 32 additions & 5 deletions cmd/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/zricethezav/gitleaks/v8/config"
"github.com/zricethezav/gitleaks/v8/detect"
"github.com/zricethezav/gitleaks/v8/detect/git"
"github.com/zricethezav/gitleaks/v8/report"
)

Expand All @@ -21,6 +22,7 @@ func init() {
detectCmd.Flags().Bool("pipe", false, "scan input from stdin, ex: `cat some_file | gitleaks detect --pipe`")
detectCmd.Flags().Bool("follow-symlinks", false, "scan files that are symlinks to other files")
detectCmd.Flags().StringP("gitleaks-ignore-path", "i", ".", "path to .gitleaksignore file or folder containing one")
detectCmd.Flags().String("gitleaks-ignore-rev", "HEAD", "git revision where .gitleaksignore can be found (useful in bare repositories without working tree)")
}

var detectCmd = &cobra.Command{
Expand Down Expand Up @@ -85,21 +87,37 @@ func runDetect(cmd *cobra.Command, args []string) {
if err != nil {
log.Fatal().Err(err).Msg("could not get .gitleaksignore path")
}
gitleaksIgnoreRev, err := cmd.Flags().GetString("gitleaks-ignore-rev")
if err != nil {
log.Fatal().Err(err).Msg("could not get revision")
}

if fileExists(gitleaksIgnorePath) {
if err = detector.AddGitleaksIgnore(gitleaksIgnorePath); err != nil {
if err = detector.AddGitleaksIgnore(gitleaksIgnorePath, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
} else if gitPath := gitleaksIgnoreRev + ":" + gitleaksIgnorePath; fileExistsInRepo(gitPath) {
if err = detector.AddGitleaksIgnore(gitPath, true); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}

if fileExists(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")) {
if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")); err != nil {
if path := filepath.Join(gitleaksIgnorePath, ".gitleaksignore"); fileExists(path) {
if err = detector.AddGitleaksIgnore(path, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
} else if gitPath := gitleaksIgnoreRev + ":" + path; fileExistsInRepo(gitPath) {
if err = detector.AddGitleaksIgnore(gitPath, true); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}

if fileExists(filepath.Join(source, ".gitleaksignore")) {
if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore")); err != nil {
if path := filepath.Join(source, ".gitleaksignore"); fileExists(path) {
if err = detector.AddGitleaksIgnore(path, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
} else if gitPath := gitleaksIgnoreRev + ":" + path; fileExistsInRepo(gitPath) {
if err = detector.AddGitleaksIgnore(gitPath, true); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}
Expand Down Expand Up @@ -213,6 +231,15 @@ func fileExists(fileName string) bool {
return false
}

func fileExistsInRepo(filename string) bool {
exists, err := git.GitFileExists(filename)
if err != nil {
return false
}

return exists
}

func FormatDuration(d time.Duration) string {
scale := 100 * time.Second
// look for the max scale that is smaller than d
Expand Down
6 changes: 3 additions & 3 deletions cmd/protect.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,19 @@ func runProtect(cmd *cobra.Command, args []string) {
}

if fileExists(gitleaksIgnorePath) {
if err = detector.AddGitleaksIgnore(gitleaksIgnorePath); err != nil {
if err = detector.AddGitleaksIgnore(gitleaksIgnorePath, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}

if fileExists(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")) {
if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore")); err != nil {
if err = detector.AddGitleaksIgnore(filepath.Join(gitleaksIgnorePath, ".gitleaksignore"), false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}

if fileExists(filepath.Join(source, ".gitleaksignore")) {
if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore")); err != nil {
if err = detector.AddGitleaksIgnore(filepath.Join(source, ".gitleaksignore"), false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
}
Expand Down
14 changes: 10 additions & 4 deletions detect/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,21 +146,27 @@ func NewDetectorDefaultConfig() (*Detector, error) {
return NewDetector(cfg), nil
}

func (d *Detector) AddGitleaksIgnore(gitleaksIgnorePath string) error {
func (d *Detector) AddGitleaksIgnore(gitleaksIgnorePath string, foundInRepo bool) (err error) {
log.Debug().Msgf("found .gitleaksignore file: %s", gitleaksIgnorePath)
file, err := os.Open(gitleaksIgnorePath)

var readCloser io.ReadCloser
if !foundInRepo {
readCloser, err = os.Open(gitleaksIgnorePath)
} else {
readCloser, err = git.GitShowFile(gitleaksIgnorePath)
}

if err != nil {
return err
}

// https://github.com/securego/gosec/issues/512
defer func() {
if err := file.Close(); err != nil {
if err := readCloser.Close(); err != nil {
log.Warn().Msgf("Error closing .gitleaksignore file: %s\n", err)
}
}()
scanner := bufio.NewScanner(file)
scanner := bufio.NewScanner(readCloser)

for scanner.Scan() {
d.gitleaksIgnore[scanner.Text()] = true
Expand Down
6 changes: 3 additions & 3 deletions detect/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ func TestFromGit(t *testing.T) {
} else {
ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
}
if err = detector.AddGitleaksIgnore(ignorePath); err != nil {
if err = detector.AddGitleaksIgnore(ignorePath, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}

Expand Down Expand Up @@ -571,7 +571,7 @@ func TestFromGitStaged(t *testing.T) {
t.Error(err)
}
detector := NewDetector(cfg)
if err = detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore")); err != nil {
if err = detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore"), false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
findings, err := detector.DetectGit(tt.source, tt.logOpts, ProtectStagedType)
Expand Down Expand Up @@ -671,7 +671,7 @@ func TestFromFiles(t *testing.T) {
} else {
ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
}
if err = detector.AddGitleaksIgnore(ignorePath); err != nil {
if err = detector.AddGitleaksIgnore(ignorePath, false); err != nil {
log.Fatal().Err(err).Msg("could not call AddGitleaksIgnore")
}
detector.FollowSymlinks = true
Expand Down
66 changes: 65 additions & 1 deletion detect/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package git

import (
"bufio"
"errors"
"io"
"os/exec"
"path/filepath"
Expand All @@ -18,6 +19,7 @@ var ErrEncountered bool
// GitLog returns a channel of gitdiff.File objects from the
// git log -p command for the given source.
var quotedOptPattern = regexp.MustCompile(`^(?:"[^"]+"|'[^']+')$`)

func GitLog(source string, logOpts string) (<-chan *gitdiff.File, error) {
sourceClean := filepath.Clean(source)
var cmd *exec.Cmd
Expand Down Expand Up @@ -98,6 +100,65 @@ func GitDiff(source string, staged bool) (<-chan *gitdiff.File, error) {
return gitdiff.Parse(cmd, stdout)
}

func GitFileExists(gitPath string) (bool, error) {
parts := strings.Split(gitPath, ":")
if len(parts) != 2 {
return false, errors.New("invalid git path")
}
object := parts[0]
path := parts[1]

cmd := exec.Command("git", "ls-tree", "-r", object, "--name-only")
log.Debug().Msgf("executing: %s", cmd.String())

stdout, err := cmd.StdoutPipe()
if err != nil {
return false, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return false, err
}

go listenForStdErr(stderr)

if err := cmd.Start(); err != nil {
return false, err
}

scanner := bufio.NewScanner(stdout)

for scanner.Scan() {
if scanner.Text() == path {
return true, nil
}
}

return false, nil
}

func GitShowFile(gitPath string) (io.ReadCloser, error) {
cmd := exec.Command("git", "show", gitPath)
log.Debug().Msgf("executing: %s", cmd.String())

stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}

go listenForStdErr(stderr)

if err := cmd.Start(); err != nil {
return nil, err
}

return stdout, nil
}

// listenForStdErr listens for stderr output from git and prints it to stdout
// then exits with exit code 1
func listenForStdErr(stderr io.ReadCloser) {
Expand All @@ -122,7 +183,10 @@ func listenForStdErr(stderr io.ReadCloser) {
strings.Contains(scanner.Text(),
"inexact rename detection was skipped") ||
strings.Contains(scanner.Text(),
"you may want to set your diff.renameLimit") {
"you may want to set your diff.renameLimit") ||
// if git ls-tree check fails
strings.Contains(scanner.Text(),
"exists on disk, but not in") {
log.Warn().Msg(scanner.Text())
} else {
log.Error().Msgf("[git] %s", scanner.Text())
Expand Down

0 comments on commit a6de2d0

Please sign in to comment.