Jelajahi Sumber

my, things have changed...

zricethezav 8 tahun lalu
induk
melakukan
7919e15a65
6 mengubah file dengan 414 tambahan dan 306 penghapusan
  1. 1 3
      checks.go
  2. 0 69
      leaks.go
  3. 27 61
      main.go
  4. 229 49
      options.go
  5. 88 77
      owner.go
  6. 69 47
      repo.go

+ 1 - 3
checks.go

@@ -6,10 +6,8 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
-// TODO LOCAL REPO!!!!
-
 // checks Regex and if enabled, entropy and stopwords
 // checks Regex and if enabled, entropy and stopwords
-func doChecks(diff string, commit Commit, opts *Options, repo *Repo) []Leak {
+func doChecks(diff string, commit Commit, repo *Repo) []Leak {
 	var (
 	var (
 		match string
 		match string
 		leaks []Leak
 		leaks []Leak

+ 0 - 69
leaks.go

@@ -1,69 +0,0 @@
-package main
-
-// TODO https://medium.com/@sebdah/go-best-practices-error-handling-2d15e1f0c5ee
-// implement better error handling
-
-// Commit is so and so
-
-/*
-// start kicks off the audit
-func start(repos []Repo, owner *Owner, opts *Options) error {
-	var (
-		report []Leak
-		err error
-	)
-	defer rmTmpDirs(owner, opts)
-
-	// interrupt handling
-	c := make(chan os.Signal, 2)
-	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
-	go func() {
-		<-c
-		rmTmpDirs(owner, opts)
-		os.Exit(1)
-	}()
-
-	// run checks on repos
-	for _, repo := range repos {
-		dotGitPath := filepath.Join(repo.path, ".git")
-		if _, err := os.Stat(dotGitPath); err == nil {
-			if err := os.Chdir(fmt.Sprintf(repo.path)); err != nil {
-				log.Fatal(err)
-			}
-			// use pre-cloned repo
-			fmt.Printf("Checking \x1b[37;1m%s\x1b[0m...\n", repo.url)
-			err = exec.Command("git", "fetch").Run()
-		} else {
-			// no repo present, clone it
-			if err := os.Chdir(fmt.Sprintf(owner.path)); err != nil {
-				log.Fatal(err)
-			}
-			fmt.Printf("Cloning \x1b[37;1m%s\x1b[0m...\n", repo.url)
-			err = exec.Command("git", "clone", repo.url).Run()
-		}
-		if err != nil {
-			log.Printf("failed to fetch repo %v", err)
-			return nil
-		}
-
-		report, err = audit(&repo, opts)
-		if err != nil {
-			return nil
-		}
-
-		if len(report) == 0 {
-			fmt.Printf("No Leaks detected for \x1b[35;2m%s\x1b[0m...\n", repo.url)
-		}
-
-		if opts.EnableJSON && len(report) != 0 {
-			writeReport(report, repo)
-		}
-	}
-	return nil
-}
-*/
-
-// Used by start, writeReport will generate a report and write it out to
-// $GITLEAKS_HOME/report/<owner>/<repo>. No report will be generated if
-// no leaks have been found
-

+ 27 - 61
main.go

@@ -2,38 +2,38 @@ package main
 
 
 import (
 import (
 	_ "fmt"
 	_ "fmt"
-	"github.com/mitchellh/go-homedir"
-	"log"
-	_"io/ioutil"
+	"go.uber.org/zap"
+	_ "io/ioutil"
 	"os"
 	"os"
-	"path/filepath"
 	"regexp"
 	"regexp"
-	"go.uber.org/zap"
-	_"time"
-	"go.uber.org/zap/zapcore"
+	_ "time"
+	"fmt"
 )
 )
 
 
 const EXIT_CLEAN = 0
 const EXIT_CLEAN = 0
 const EXIT_FAILURE = 1
 const EXIT_FAILURE = 1
 const EXIT_LEAKS = 2
 const EXIT_LEAKS = 2
 
 
+// package globals
 var (
 var (
-	regexes            map[string]*regexp.Regexp
-	stopWords          []string
-	base64Chars        string
-	hexChars           string
-	assignRegex        *regexp.Regexp
-	fileDiffRegex      *regexp.Regexp
-	gitLeaksPath       string
-	gitLeaksClonePath  string
-	gitLeaksReportPath string
-	logger  *zap.Logger
+	regexes       map[string]*regexp.Regexp
+	stopWords     []string
+	base64Chars   string
+	hexChars      string
+	assignRegex   *regexp.Regexp
+	fileDiffRegex *regexp.Regexp
+	logger        *zap.Logger
+	opts 		  *Options
 )
 )
 
 
 func init() {
 func init() {
 	base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
 	base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
 	hexChars = "1234567890abcdefABCDEF"
 	hexChars = "1234567890abcdefABCDEF"
 	stopWords = []string{"setting", "info", "env", "environment"}
 	stopWords = []string{"setting", "info", "env", "environment"}
+	fileDiffRegex = regexp.MustCompile("diff --git a.+b/")
+	assignRegex = regexp.MustCompile(`(=|:|:=|<-)`)
+
+	// TODO Externalize regex... this is tricky making it yml compliant
 	regexes = map[string]*regexp.Regexp{
 	regexes = map[string]*regexp.Regexp{
 		"PKCS8":    regexp.MustCompile("-----BEGIN PRIVATE KEY-----"),
 		"PKCS8":    regexp.MustCompile("-----BEGIN PRIVATE KEY-----"),
 		"RSA":      regexp.MustCompile("-----BEGIN RSA PRIVATE KEY-----"),
 		"RSA":      regexp.MustCompile("-----BEGIN RSA PRIVATE KEY-----"),
@@ -44,53 +44,19 @@ func init() {
 		"AWS":      regexp.MustCompile("AKIA[0-9A-Z]{16}"),
 		"AWS":      regexp.MustCompile("AKIA[0-9A-Z]{16}"),
 		"Reddit":   regexp.MustCompile("(?i)reddit.*['|\"][0-9a-zA-Z]{14}['|\"]"),
 		"Reddit":   regexp.MustCompile("(?i)reddit.*['|\"][0-9a-zA-Z]{14}['|\"]"),
 		"Heroku":   regexp.MustCompile("(?i)heroku.*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}"),
 		"Heroku":   regexp.MustCompile("(?i)heroku.*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}"),
-		// "Custom": regexp.MustCompile(".*")
-	}
-	fileDiffRegex = regexp.MustCompile("diff --git a.+b/")
-	assignRegex = regexp.MustCompile(`(=|:|:=|<-)`)
-
-	// gitleaks dir defaults to $HOME/.gitleaks if no env var GITLEAKS_HOME is present.
-	gitLeaksPath = os.Getenv("GITLEAKS_HOME")
-	if gitLeaksPath == "" {
-		homeDir, err := homedir.Dir()
-		if err != nil {
-			log.Fatal("Cant find home dir")
-		}
-		gitLeaksPath = filepath.Join(homeDir, ".gitleaks")
-	}
-
-	if _, err := os.Stat(gitLeaksPath); os.IsNotExist(err) {
-		os.Mkdir(gitLeaksPath, os.ModePerm)
-	}
-	gitLeaksClonePath = filepath.Join(gitLeaksPath, "clones")
-	if _, err := os.Stat(gitLeaksClonePath); os.IsNotExist(err) {
-		os.Mkdir(gitLeaksClonePath, os.ModePerm)
-	}
-	gitLeaksReportPath = filepath.Join(gitLeaksPath, "report")
-	if _, err := os.Stat(gitLeaksReportPath); os.IsNotExist(err) {
-		os.Mkdir(gitLeaksReportPath, os.ModePerm)
 	}
 	}
 }
 }
 
 
 func main() {
 func main() {
-	// TODO abstract logging
-	atom := zap.NewAtomicLevel()
-	encoderCfg := zap.NewProductionEncoderConfig()
-	encoderCfg.TimeKey = ""
-	logger = zap.New(zapcore.NewCore(
-		zapcore.NewJSONEncoder(encoderCfg),
-		zapcore.Lock(os.Stdout),
-		atom,
-	))
-	logger.Info("HEY")
-	atom.SetLevel(zap.InfoLevel)
-	logger.Info("HEY")
-
-
 	args := os.Args[1:]
 	args := os.Args[1:]
-	opts := parseOptions(args)
-	owner := newOwner(opts)
-	owner.auditRepos(opts)
-	// repos := getRepos(opts, owner)
-	// start(repos, owner, opts)
+	opts = newOpts(args)
+	owner := newOwner()
+	os.Exit(owner.auditRepos())
 }
 }
+
+func failF(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	os.Exit(EXIT_FAILURE)
+}
+
+

+ 229 - 49
options.go

@@ -4,52 +4,90 @@ import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"strconv"
 	"strconv"
+	"strings"
+	"regexp"
+	"github.com/mitchellh/go-homedir"
+	"path/filepath"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
 )
 )
 
 
-const usage = `usage: gitleaks [options] <url>
+const DEBUG = 0
+const INFO = 1
+const ERROR = 2
+
+const usage = `usage: gitleaks [options] <URL>/<path_to_repo>
 
 
 Options:
 Options:
- -c --concurrency 	Upper bound on concurrent diffs
- -u --user 		Git user url
- -r --repo 		Git repo url
- -o --org 		Git organization url
- -s --since 		Commit to stop at
- -b --b64Entropy 	Base64 entropy cutoff (default is 70)
- -x --hexEntropy  	Hex entropy cutoff (default is 40)
+Modes
+ -u --user 		Git user mode
+ -r --repo 		Git repo mode
+ -o --org 		Git organization mode
+ -l --local 		Local mode, gitleaks will look for local repo in <path>
+
+Logging
+ -ll <INT> --log=<INT> 	0: Debug, 1: Info, 3: Error
+ -v --verbose 		Verbose mode, will output leaks as gitleaks finds them
+
+Locations
+ --report_path=<STR> 	Report output, default $GITLEAKS_HOME/report
+ --clone_path=<STR>	Gitleaks will clone repos here, default $GITLEAKS_HOME/clones
+
+Other
+ -t --temp 		Clone to temporary directory
+ -c <INT> 		Upper bound on concurrent diffs
+ --since=<STR> 		Commit to stop at
+ --b64Entropy=<INT> 	Base64 entropy cutoff (default is 70)
+ --hexEntropy=<INT>  	Hex entropy cutoff (default is 40)
  -e --entropy		Enable entropy		
  -e --entropy		Enable entropy		
- -j --json 		Output gitleaks report
  -h --help 		Display this message
  -h --help 		Display this message
- --token    		Github API token
- --strict 		Enables stopwords
+ --token=<STR>    	Github API token
+ --stopwords  		Enables stopwords
+
 `
 `
 
 
-// Options for gitleaks
+
+// Options for gitleaks. need to support remote repo/owner
+// and local repo/owner mode
 type Options struct {
 type Options struct {
+	URL 			 string
+	RepoPath 	     string
+
+	ClonePath 		 string
+	ReportPath 	     string
+
 	Concurrency      int
 	Concurrency      int
 	B64EntropyCutoff int
 	B64EntropyCutoff int
 	HexEntropyCutoff int
 	HexEntropyCutoff int
-	UserURL          string
-	OrgURL           string
-	RepoURL          string
+
+	// MODES
+	UserMode         bool
+	OrgMode          bool
+	RepoMode       	 bool
+	LocalMode 		 bool
+
+	// OPTS
 	Strict           bool
 	Strict           bool
 	Entropy          bool
 	Entropy          bool
 	SinceCommit      string
 	SinceCommit      string
 	Persist          bool
 	Persist          bool
 	IncludeForks     bool
 	IncludeForks     bool
 	Tmp              bool
 	Tmp              bool
-	EnableJSON       bool
+	ReportOut 		 bool
 	Token            string
 	Token            string
+
+	// LOGS/REPORT
 	Verbose          bool
 	Verbose          bool
+	LogLevel 		 int
 }
 }
 
 
 // help prints the usage string and exits
 // help prints the usage string and exits
 func help() {
 func help() {
 	os.Stderr.WriteString(usage)
 	os.Stderr.WriteString(usage)
-	os.Exit(1)
 }
 }
 
 
 // optionsNextInt is a parseOptions helper that returns the value (int) of an option if valid
 // optionsNextInt is a parseOptions helper that returns the value (int) of an option if valid
-func optionsNextInt(args []string, i *int) int {
+func (opts *Options) nextInt(args []string, i *int) int {
 	if len(args) > *i+1 {
 	if len(args) > *i+1 {
 		*i++
 		*i++
 	} else {
 	} else {
@@ -57,30 +95,90 @@ func optionsNextInt(args []string, i *int) int {
 	}
 	}
 	argInt, err := strconv.Atoi(args[*i])
 	argInt, err := strconv.Atoi(args[*i])
 	if err != nil {
 	if err != nil {
-		fmt.Printf("Invalid %s option: %s\n", args[*i-1], args[*i])
-		help()
+		opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
 	}
 	}
 	return argInt
 	return argInt
 }
 }
 
 
 // optionsNextString is a parseOptions helper that returns the value (string) of an option if valid
 // optionsNextString is a parseOptions helper that returns the value (string) of an option if valid
-func optionsNextString(args []string, i *int) string {
+func (opts *Options) nextString(args []string, i *int) string {
 	if len(args) > *i+1 {
 	if len(args) > *i+1 {
 		*i++
 		*i++
 	} else {
 	} else {
-		fmt.Printf("Invalid %s option: %s\n", args[*i-1], args[*i])
-		help()
+		opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
 	}
 	}
 	return args[*i]
 	return args[*i]
 }
 }
 
 
-// parseOptions
-func parseOptions(args []string) *Options {
-	opts := &Options{
+// optInt grabs the string ...
+func (opts *Options) optString(arg string, prefixes ...string) (bool, string) {
+	for _, prefix := range prefixes {
+		if strings.HasPrefix(arg, prefix) {
+			return true, arg[len(prefix):]
+		}
+	}
+	return false, ""
+}
+
+// optInt grabs the int ...
+func (opts *Options) optInt(arg string, prefixes ...string) (bool, int) {
+	for _, prefix := range prefixes {
+		if strings.HasPrefix(arg, prefix) {
+			i, err := strconv.Atoi(arg[len(prefix):])
+			if err != nil {
+				opts.failF("Invalid %s int option\n", prefix)
+			}
+			return true, i
+		}
+	}
+	return false, 0
+}
+
+// newOpts generates opts and parses arguments
+func newOpts(args []string) (*Options) {
+	opts, err := defaultOptions()
+	if err != nil{
+		opts.failF("%v", err)
+	}
+	err = opts.parseOptions(args)
+	if err != nil{
+		opts.failF("%v", err)
+	}
+	opts.setupLogger()
+	return opts
+}
+
+// deafultOptions provides the default options
+func defaultOptions() (*Options, error) {
+	// default GITLEAKS_HOME is $HOME/.gitleaks
+	// gitleaks will use this location for clones if
+	// no clone-path is provided
+	gitleaksHome := os.Getenv("GITLEAKS_HOME")
+	if gitleaksHome == "" {
+		homeDir, err := homedir.Dir()
+		if err != nil {
+			return nil, fmt.Errorf("could not find system home dir")
+		}
+		gitleaksHome = filepath.Join(homeDir, ".gitleaks")
+	}
+
+	// make sure gitleaks home exists
+	if _, err := os.Stat(gitleaksHome); os.IsNotExist(err) {
+		os.Mkdir(gitleaksHome, os.ModePerm)
+	}
+
+	return &Options{
 		Concurrency:      10,
 		Concurrency:      10,
 		B64EntropyCutoff: 70,
 		B64EntropyCutoff: 70,
 		HexEntropyCutoff: 40,
 		HexEntropyCutoff: 40,
-	}
+		LogLevel: INFO,
+		ClonePath: filepath.Join(gitleaksHome, "clone"),
+		ReportPath: filepath.Join(gitleaksHome, "report"),
+	}, nil
+}
+
+// parseOptions
+func (opts *Options) parseOptions(args []string) error {
 
 
 	if len(args) == 0 {
 	if len(args) == 0 {
 		help()
 		help()
@@ -89,48 +187,130 @@ func parseOptions(args []string) *Options {
 	for i := 0; i < len(args); i++ {
 	for i := 0; i < len(args); i++ {
 		arg := args[i]
 		arg := args[i]
 		switch arg {
 		switch arg {
-		case "-s", "--since":
-			opts.SinceCommit = optionsNextString(args, &i)
+		case "-s":
+			opts.SinceCommit = opts.nextString(args, &i)
 		case "--strict":
 		case "--strict":
 			opts.Strict = true
 			opts.Strict = true
 		case "-b", "--b64Entropy":
 		case "-b", "--b64Entropy":
-			opts.B64EntropyCutoff = optionsNextInt(args, &i)
+			opts.B64EntropyCutoff = opts.nextInt(args, &i)
 		case "-x", "--hexEntropy":
 		case "-x", "--hexEntropy":
-			opts.HexEntropyCutoff = optionsNextInt(args, &i)
+			opts.HexEntropyCutoff = opts.nextInt(args, &i)
 		case "-e", "--entropy":
 		case "-e", "--entropy":
 			opts.Entropy = true
 			opts.Entropy = true
-		case "-c", "--concurrency":
-			opts.Concurrency = optionsNextInt(args, &i)
+		case "-c":
+			opts.Concurrency = opts.nextInt(args, &i)
+
 		case "-o", "--org":
 		case "-o", "--org":
-			opts.OrgURL = optionsNextString(args, &i)
+			opts.OrgMode = true
 		case "-u", "--user":
 		case "-u", "--user":
-			opts.UserURL = optionsNextString(args, &i)
+			opts.UserMode = true
 		case "-r", "--repo":
 		case "-r", "--repo":
-			opts.RepoURL = optionsNextString(args, &i)
-		case "-t", "--temporary":
+			opts.RepoMode = true
+		case "-l", "--local":
+			opts.LocalMode = true
+
+		case "--report-out":
+			opts.ReportOut = true
+
+		case "-t", "--temp":
 			opts.Tmp = true
 			opts.Tmp = true
-		case "--token":
-			opts.Token = optionsNextString(args, &i)
-		case "-j", "--json":
-			opts.EnableJSON = true
+		case "-ll":
+			opts.LogLevel = opts.nextInt(args, &i)
 		case "-h", "--help":
 		case "-h", "--help":
 			help()
 			help()
-			return nil
+			os.Exit(EXIT_CLEAN)
 		default:
 		default:
-			if i == len(args)-1 && opts.OrgURL == "" && opts.RepoURL == "" &&
-				opts.UserURL == "" {
-				opts.RepoURL = arg
+			// TARGETS
+			if i == len(args)-1 {
+				fmt.Println(arg[i])
+				if opts.LocalMode {
+					opts.RepoPath = args[i]
+				} else {
+					opts.URL = args[i]
+				}
+			} else if match, value := opts.optString(arg, "--token="); match {
+				opts.Token = value
+			} else if match, value := opts.optString(arg, "--since="); match {
+				opts.SinceCommit = value
+			} else if match, value := opts.optString(arg, "--report-path="); match {
+				opts.ReportPath = value
+			} else if match, value := opts.optString(arg, "--clone-path="); match {
+				opts.ClonePath = value
+			} else if match, value := opts.optInt(arg, "--log="); match {
+				opts.LogLevel = value
+			} else if match, value := opts.optInt(arg, "--b64Entropy="); match {
+				opts.B64EntropyCutoff = value
+			} else if match, value := opts.optInt(arg, "--hexEntropy="); match {
+				opts.HexEntropyCutoff = value
 			} else {
 			} else {
 				fmt.Printf("Unknown option %s\n\n", arg)
 				fmt.Printf("Unknown option %s\n\n", arg)
 				help()
 				help()
+				return fmt.Errorf("Unknown option %s\n\n", arg)
 			}
 			}
 		}
 		}
 	}
 	}
+	err := opts.guards()
+	if err != nil{
+		fmt.Printf("%v", err)
+	}
+	return err
+}
 
 
-	// "guards"
-	if opts.Tmp && opts.EnableJSON {
-		fmt.Println("Report generation with temporary clones not supported")
+// failF prints a failure message out to stderr, displays help
+// and exits with a exit code 2
+func (opts *Options) failF(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	help()
+	os.Exit(EXIT_FAILURE)
+}
+
+// guards will prevent gitleaks from continuing if any invalid options
+// are found.
+func (opts *Options) guards() error {
+	if (opts.RepoMode || opts.OrgMode || opts.UserMode) && !isGithubTarget(opts.URL) {
+		return fmt.Errorf("Not valid github target %s\n", opts.URL)
+	} else if (opts.RepoMode || opts.OrgMode || opts.UserMode) && opts.LocalMode {
+		return fmt.Errorf("Cannot run Gitleaks on repo/user/org mode and local mode\n")
+	} else if (opts.RepoMode || opts.UserMode) && opts.OrgMode {
+		return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
+	} else if (opts.OrgMode || opts.UserMode) && opts.RepoMode {
+		return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
+	} else if (opts.OrgMode || opts.RepoMode) && opts.UserMode{
+		return fmt.Errorf("Cannot run Gitleaks on more than one mode\n")
+	} else if opts.LocalMode && opts.Tmp {
+		return fmt.Errorf("Cannot run Gitleaks with temp settings and local mode\n")
+	} else if opts.SinceCommit != "" && (opts.OrgMode || opts.UserMode) {
+		return fmt.Errorf("Cannot run Gitleaks with since commit flag and a owner mode\n")
 	}
 	}
 
 
-	return opts
+	return nil
+}
+
+// setupLogger initiates the logger and sets the logging level
+// based on what is set in arguments. Default logging level is
+// INFO
+func (opts *Options) setupLogger() {
+	atom := zap.NewAtomicLevel()
+	encoderCfg := zap.NewProductionEncoderConfig()
+	encoderCfg.TimeKey = ""
+	logger = zap.New(zapcore.NewCore(
+		zapcore.NewJSONEncoder(encoderCfg),
+		zapcore.Lock(os.Stdout),
+		atom,
+	))
+
+	switch opts.LogLevel {
+	case DEBUG:
+		atom.SetLevel(zap.DebugLevel)
+	case INFO:
+		atom.SetLevel(zap.InfoLevel)
+	case ERROR:
+		atom.SetLevel(zap.ErrorLevel)
+	}
+}
+
+// isGithubTarget checks if url is a valid github target
+func isGithubTarget(url string) bool {
+	re := regexp.MustCompile("github\\.com\\/")
+	return re.MatchString(url)
 }
 }

+ 88 - 77
owner.go

@@ -1,18 +1,17 @@
 package main
 package main
 
 
 import (
 import (
-	"path"
-	"io/ioutil"
-	"path/filepath"
-	"os"
-	"github.com/google/go-github/github"
-	"strings"
 	"context"
 	"context"
+	_ "fmt"
+	"github.com/google/go-github/github"
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2"
+	"io/ioutil"
 	"net/http"
 	"net/http"
-	"log"
+	"os"
 	"os/signal"
 	"os/signal"
-	_"fmt"
+	"path"
+	"strings"
+	"fmt"
 )
 )
 
 
 type Owner struct {
 type Owner struct {
@@ -21,21 +20,17 @@ type Owner struct {
 	accountType string
 	accountType string
 	path        string
 	path        string
 	reportPath  string
 	reportPath  string
-	repos      []Repo
+	repos       []Repo
 }
 }
 
 
 // newOwner instantiates an owner and creates any necessary resources for said owner.
 // newOwner instantiates an owner and creates any necessary resources for said owner.
 // newOwner returns a Owner struct pointer
 // newOwner returns a Owner struct pointer
-func newOwner(opts *Options) *Owner {
-	name, err := ownerName(opts)
+func newOwner() (*Owner)  {
+	name  := ownerName()
 	owner := &Owner{
 	owner := &Owner{
 		name:        name,
 		name:        name,
-		url:         opts.UserURL,
-		accountType: ownerType(opts),
-	}
-
-	if err != nil {
-		owner.failf()
+		url:         opts.URL,
+		accountType: ownerType(),
 	}
 	}
 
 
 	// listen for ctrl-c
 	// listen for ctrl-c
@@ -49,8 +44,23 @@ func newOwner(opts *Options) *Owner {
 		owner.rmTmp()
 		owner.rmTmp()
 	}()
 	}()
 
 
-	owner.setupDir(opts)
-	owner.fetchRepos(opts)
+
+	// if running on local repo, just go right to it.
+	if opts.LocalMode {
+		repo := newLocalRepo(opts.RepoPath)
+		owner.repos = append(owner.repos, *repo)
+		return owner
+	}
+
+	err := owner.setupDir()
+	if err != nil {
+		owner.failF("%v", err)
+	}
+
+	err = owner.fetchRepos()
+	if err != nil {
+		owner.failF("%v", err)
+	}
 	return owner
 	return owner
 }
 }
 
 
@@ -58,16 +68,17 @@ func newOwner(opts *Options) *Owner {
 // of the owner's repos. If opts.RepoURL is not the empty string then fetchRepos will
 // of the owner's repos. If opts.RepoURL is not the empty string then fetchRepos will
 // only grab the repo specified in opts.RepoURL. Otherwise, fetchRepos will reach out to
 // only grab the repo specified in opts.RepoURL. Otherwise, fetchRepos will reach out to
 // github's api and grab all repos associated with owner.
 // github's api and grab all repos associated with owner.
-func (owner *Owner) fetchRepos(opts *Options) {
+func (owner *Owner) fetchRepos() error {
+	var err error
 	ctx := context.Background()
 	ctx := context.Background()
 	if owner.accountType == "" {
 	if owner.accountType == "" {
 		// single repo, ambiguous account type
 		// single repo, ambiguous account type
-		_, repoName := path.Split(opts.RepoURL)
-		repo := newRepo(owner, repoName, opts.RepoURL)
+		_, repoName := path.Split(opts.URL)
+		repo := newRepo(repoName, opts.URL)
 		owner.repos = append(owner.repos, *repo)
 		owner.repos = append(owner.repos, *repo)
 	} else {
 	} else {
 		// org or user account type, would fail if not valid before
 		// org or user account type, would fail if not valid before
-		tokenClient := githubTokenClient(opts)
+		tokenClient := githubTokenClient()
 		gitClient := github.NewClient(tokenClient)
 		gitClient := github.NewClient(tokenClient)
 
 
 		if owner.accountType == "org" {
 		if owner.accountType == "org" {
@@ -75,26 +86,27 @@ func (owner *Owner) fetchRepos(opts *Options) {
 			orgOpt := &github.RepositoryListByOrgOptions{
 			orgOpt := &github.RepositoryListByOrgOptions{
 				ListOptions: github.ListOptions{PerPage: 10},
 				ListOptions: github.ListOptions{PerPage: 10},
 			}
 			}
-			owner.fetchOrgRepos(orgOpt, gitClient, ctx)
+			err = owner.fetchOrgRepos(orgOpt, gitClient, ctx)
 		} else {
 		} else {
 			// user account type
 			// user account type
 			userOpt := &github.RepositoryListOptions{
 			userOpt := &github.RepositoryListOptions{
 				ListOptions: github.ListOptions{PerPage: 10},
 				ListOptions: github.ListOptions{PerPage: 10},
 			}
 			}
-			owner.fetchUserRepos(userOpt, gitClient, ctx)
+			err = owner.fetchUserRepos(userOpt, gitClient, ctx)
 		}
 		}
 	}
 	}
+	return err
 }
 }
 
 
 // fetchOrgRepos used by fetchRepos is responsible for parsing github's org repo response. If no
 // fetchOrgRepos used by fetchRepos is responsible for parsing github's org repo response. If no
 // github token is available then fetchOrgRepos might run into a rate limit in which case owner will
 // github token is available then fetchOrgRepos might run into a rate limit in which case owner will
 // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
 // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
 func (owner *Owner) fetchOrgRepos(orgOpts *github.RepositoryListByOrgOptions, gitClient *github.Client,
 func (owner *Owner) fetchOrgRepos(orgOpts *github.RepositoryListByOrgOptions, gitClient *github.Client,
-	ctx context.Context) {
+	ctx context.Context) error {
 	var (
 	var (
-		githubRepos     []*github.Repository
-		resp 			 *github.Response
-		err 			error
+		githubRepos []*github.Repository
+		resp        *github.Response
+		err         error
 	)
 	)
 
 
 	for {
 	for {
@@ -102,16 +114,15 @@ func (owner *Owner) fetchOrgRepos(orgOpts *github.RepositoryListByOrgOptions, gi
 			ctx, owner.name, orgOpts)
 			ctx, owner.name, orgOpts)
 		owner.addRepos(githubRepos)
 		owner.addRepos(githubRepos)
 		if _, ok := err.(*github.RateLimitError); ok {
 		if _, ok := err.(*github.RateLimitError); ok {
-			log.Println("hit rate limit")
-			break
+			logger.Info("hit rate limit")
 		} else if err != nil {
 		} else if err != nil {
-			log.Println("other error")
-			break
+			return fmt.Errorf("failed fetching org repos, bad request")
 		} else if resp.NextPage == 0 {
 		} else if resp.NextPage == 0 {
 			break
 			break
 		}
 		}
 		orgOpts.Page = resp.NextPage
 		orgOpts.Page = resp.NextPage
 	}
 	}
+	return nil
 }
 }
 
 
 // fetchUserRepos used by fetchRepos is responsible for parsing github's user repo response. If no
 // fetchUserRepos used by fetchRepos is responsible for parsing github's user repo response. If no
@@ -119,74 +130,79 @@ func (owner *Owner) fetchOrgRepos(orgOpts *github.RepositoryListByOrgOptions, gi
 // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
 // log an error and gitleaks will exit. The rate limit for no token is 50 req/hour... not much.
 // sorry for the redundancy
 // sorry for the redundancy
 func (owner *Owner) fetchUserRepos(userOpts *github.RepositoryListOptions, gitClient *github.Client,
 func (owner *Owner) fetchUserRepos(userOpts *github.RepositoryListOptions, gitClient *github.Client,
-	ctx context.Context) {
+	ctx context.Context) error {
 	var (
 	var (
-		githubRepos     []*github.Repository
-		resp 			 *github.Response
-		err 			error
+		githubRepos []*github.Repository
+		resp        *github.Response
+		err         error
 	)
 	)
 	for {
 	for {
 		githubRepos, resp, err = gitClient.Repositories.List(
 		githubRepos, resp, err = gitClient.Repositories.List(
 			ctx, owner.name, userOpts)
 			ctx, owner.name, userOpts)
 		owner.addRepos(githubRepos)
 		owner.addRepos(githubRepos)
 		if _, ok := err.(*github.RateLimitError); ok {
 		if _, ok := err.(*github.RateLimitError); ok {
-			log.Println("hit rate limit")
+			logger.Info("hit rate limit")
 			break
 			break
 		} else if err != nil {
 		} else if err != nil {
-			log.Println("other error")
-			break
+			return fmt.Errorf("failed fetching user repos, bad request")
 		} else if resp.NextPage == 0 {
 		} else if resp.NextPage == 0 {
 			break
 			break
 		}
 		}
 		userOpts.Page = resp.NextPage
 		userOpts.Page = resp.NextPage
 	}
 	}
+	return nil
 }
 }
 
 
 // addRepos used by fetchUserRepos and fetchOrgRepos appends new repos from
 // addRepos used by fetchUserRepos and fetchOrgRepos appends new repos from
 // github's org/user response.
 // github's org/user response.
-func (owner *Owner) addRepos (githubRepos []*github.Repository) {
+func (owner *Owner) addRepos(githubRepos []*github.Repository) {
 	for _, repo := range githubRepos {
 	for _, repo := range githubRepos {
-		owner.repos = append(owner.repos, *newRepo(owner, *repo.Name, *repo.CloneURL))
+		owner.repos = append(owner.repos, *newRepo(*repo.Name, *repo.CloneURL))
 	}
 	}
 }
 }
 
 
 // auditRepos
 // auditRepos
-func (owner *Owner) auditRepos(opts *Options) {
+func (owner *Owner) auditRepos() (int) {
+	exitCode := EXIT_CLEAN
 	for _, repo := range owner.repos {
 	for _, repo := range owner.repos {
-		err := repo.audit(owner, opts)
+		leaksPst, err := repo.audit(owner)
 		if err != nil {
 		if err != nil {
-			owner.failf()
+			failF("%v\n", err)
+		}
+		if leaksPst{
+			exitCode = EXIT_LEAKS
 		}
 		}
 	}
 	}
+	return exitCode
 }
 }
 
 
-// failf
-func (owner *Owner) failf() {
-	// TODO
-}
-
-// exitNow
-func (owner *Owner) exitNow() {
-
+// failF prints a failure message out to stderr
+// and exits with a exit code 2
+func (owner *Owner) failF(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	os.Exit(EXIT_FAILURE)
 }
 }
 
 
 // setupDir sets up the owner's directory for clones and reports.
 // setupDir sets up the owner's directory for clones and reports.
 // If the temporary option is set then a temporary directory will be
 // If the temporary option is set then a temporary directory will be
 // used for the owner repo clones.
 // used for the owner repo clones.
-func (owner *Owner) setupDir(opts *Options) {
+func (owner *Owner) setupDir() error {
 	if opts.Tmp {
 	if opts.Tmp {
 		dir, err := ioutil.TempDir("", owner.name)
 		dir, err := ioutil.TempDir("", owner.name)
 		if err != nil {
 		if err != nil {
-			owner.failf()
+			return err
+			owner.failF("Unabled to create temp directories for cloning")
 		}
 		}
 		owner.path = dir
 		owner.path = dir
 	} else {
 	} else {
-		owner.path = filepath.Join(gitLeaksClonePath, owner.name)
-		if _, err := os.Stat(owner.path); os.IsNotExist(err) {
+		if _, err := os.Stat(opts.ClonePath); os.IsNotExist(err) {
 			os.Mkdir(owner.path, os.ModePerm)
 			os.Mkdir(owner.path, os.ModePerm)
 		}
 		}
 	}
 	}
-	owner.reportPath = filepath.Join(gitLeaksPath, "report", owner.name)
+	return nil
+
+	// TODO could be handled via option
+	// owner.reportPath = filepath.Join(gitLeaksPath, "report", owner.name)
 }
 }
 
 
 // rmTmp removes the temporary repo
 // rmTmp removes the temporary repo
@@ -198,11 +214,10 @@ func (owner *Owner) rmTmp() {
 // ownerType returns the owner type extracted from opts.
 // ownerType returns the owner type extracted from opts.
 // If no owner type is provided, gitleaks assumes the owner is ambiguous
 // If no owner type is provided, gitleaks assumes the owner is ambiguous
 // and the user is running gitleaks on a single repo
 // and the user is running gitleaks on a single repo
-func ownerType(opts *Options) string {
-	if opts.OrgURL != "" {
+func ownerType() string {
+	if opts.OrgMode {
 		return "org"
 		return "org"
-
-	} else if opts.UserURL != "" {
+	} else if opts.UserMode {
 		return "user"
 		return "user"
 	}
 	}
 	return ""
 	return ""
@@ -211,28 +226,24 @@ func ownerType(opts *Options) string {
 // ownerName returns the owner name extracted from the urls provided in opts.
 // ownerName returns the owner name extracted from the urls provided in opts.
 // If no RepoURL, OrgURL, or UserURL is provided, then owner will log an error
 // If no RepoURL, OrgURL, or UserURL is provided, then owner will log an error
 // and gitleaks will exit.
 // and gitleaks will exit.
-func ownerName(opts *Options) (string, error) {
-	if opts.RepoURL != "" {
-		splitSlashes := strings.Split(opts.RepoURL, "/")
-		return splitSlashes[len(splitSlashes)-2], nil
-	} else if opts.UserURL != "" {
-		_, ownerName := path.Split(opts.UserURL)
-		return ownerName, nil
-	} else if opts.OrgURL != "" {
-		_, ownerName := path.Split(opts.OrgURL)
-		return ownerName, nil
+func ownerName() (string) {
+	if opts.RepoMode {
+		splitSlashes := strings.Split(opts.URL, "/")
+		return splitSlashes[len(splitSlashes)-2]
+	} else if opts.UserMode|| opts.OrgMode {
+		_, ownerName := path.Split(opts.URL)
+		return ownerName
 	}
 	}
-
-	// TODO error
-	return "", nil
+	// local repo
+	return ""
 }
 }
 
 
 // githubTokenClient creates an oauth client from your github access token.
 // githubTokenClient creates an oauth client from your github access token.
 // Gitleaks will attempt to retrieve your github access token from a cli argument
 // Gitleaks will attempt to retrieve your github access token from a cli argument
 // or an env var - "GITHUB_TOKEN".
 // or an env var - "GITHUB_TOKEN".
-// Might be good to eventually parse the token from a config or creds file in
+// Might be good to eventually parse the token from a Config or creds file in
 // $GITLEAKS_HOME
 // $GITLEAKS_HOME
-func githubTokenClient(opts *Options) *http.Client {
+func githubTokenClient() *http.Client {
 	var token string
 	var token string
 	if opts.Token != "" {
 	if opts.Token != "" {
 		token = opts.Token
 		token = opts.Token

+ 69 - 47
repo.go

@@ -1,25 +1,24 @@
 package main
 package main
 
 
 import (
 import (
-	"path/filepath"
-	"os"
-	"fmt"
-	"os/exec"
-	"sync"
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
-	"log"
-	"strings"
+	"fmt"
+	"go.uber.org/zap"
 	"io/ioutil"
 	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sync"
+	"path"
 )
 )
 
 
 type Repo struct {
 type Repo struct {
-	name  string
-	url   string
-	path  string
+	name   string
+	url    string
+	path   string
 	status string // TODO
 	status string // TODO
 	leaks  []Leak
 	leaks  []Leak
-	owner *Owner
 }
 }
 
 
 type Leak struct {
 type Leak struct {
@@ -41,13 +40,33 @@ type Commit struct {
 	Msg    string
 	Msg    string
 }
 }
 
 
-func newRepo(owner *Owner, name string, url string) *Repo {
+// running gitleaks on local repo
+func newLocalRepo(repoPath string) *Repo {
+	_, name := path.Split(repoPath)
 	repo := &Repo{
 	repo := &Repo{
 		name: name,
 		name: name,
-		url: url,
-		path: owner.path + "/" + name,
+		path: repoPath,
 	}
 	}
 	return repo
 	return repo
+
+}
+
+func newRepo(name string, url string) *Repo {
+	repo := &Repo{
+		name:  name,
+		url:   url,
+		// TODO handle existing one
+		path:  opts.ClonePath + "/" + name,
+	}
+	return repo
+}
+
+func (repo *Repo) rLog(msg string) {
+	// logger should have these infos: msg, repo, owner, time
+	logger.Debug("Beginning audit",
+		zap.String("repo", repo.name),
+		zap.String("repo_path", repo.path),
+	)
 }
 }
 
 
 // Audit operates on a single repo and searches the full or partial history of the repo.
 // Audit operates on a single repo and searches the full or partial history of the repo.
@@ -56,7 +75,7 @@ func newRepo(owner *Owner, name string, url string) *Repo {
 // commands so that users could opt for doing all clones/diffs in memory.
 // commands so that users could opt for doing all clones/diffs in memory.
 // Audit also declares two WaitGroups, one for distributing regex/entropy checks, and one for receiving
 // Audit also declares two WaitGroups, one for distributing regex/entropy checks, and one for receiving
 // the leaks if there are any. This could be done a little more elegantly in the future.
 // the leaks if there are any. This could be done a little more elegantly in the future.
-func (repo *Repo) audit(owner *Owner, opts *Options) error {
+func (repo *Repo) audit(owner *Owner) (bool, error) {
 	var (
 	var (
 		out               []byte
 		out               []byte
 		err               error
 		err               error
@@ -65,6 +84,7 @@ func (repo *Repo) audit(owner *Owner, opts *Options) error {
 		gitLeaksChan      = make(chan Leak)
 		gitLeaksChan      = make(chan Leak)
 		leaks             []Leak
 		leaks             []Leak
 		semaphoreChan     = make(chan struct{}, opts.Concurrency)
 		semaphoreChan     = make(chan struct{}, opts.Concurrency)
+		leaksPst 		  bool
 	)
 	)
 
 
 	dotGitPath := filepath.Join(repo.path, ".git")
 	dotGitPath := filepath.Join(repo.path, ".git")
@@ -72,22 +92,26 @@ func (repo *Repo) audit(owner *Owner, opts *Options) error {
 	// Navigate to proper location to being audit. Clone repo
 	// Navigate to proper location to being audit. Clone repo
 	// if not present, otherwise fetch for new changes.
 	// if not present, otherwise fetch for new changes.
 	if _, err := os.Stat(dotGitPath); os.IsNotExist(err) {
 	if _, err := os.Stat(dotGitPath); os.IsNotExist(err) {
+		if opts.LocalMode {
+			return false, fmt.Errorf("%s does not exist", repo.path)
+		}
 		// no repo present, clone it
 		// no repo present, clone it
 		fmt.Printf("Cloning \x1b[37;1m%s\x1b[0m into %s...\n", repo.url, repo.path)
 		fmt.Printf("Cloning \x1b[37;1m%s\x1b[0m into %s...\n", repo.url, repo.path)
 		err = exec.Command("git", "clone", repo.url, repo.path).Run()
 		err = exec.Command("git", "clone", repo.url, repo.path).Run()
-		if err != nil{
-			fmt.Println("can run clonse")
+		if err != nil {
+			return false, fmt.Errorf("cannot clone %s into %s", repo.url, repo.path)
 		}
 		}
 	} else {
 	} else {
 		fmt.Printf("Checking \x1b[37;1m%s\x1b[0m from %s...\n", repo.url, repo.path)
 		fmt.Printf("Checking \x1b[37;1m%s\x1b[0m from %s...\n", repo.url, repo.path)
 		err = exec.Command("git", "fetch").Run()
 		err = exec.Command("git", "fetch").Run()
-		if err != nil{
-			fmt.Println("can run fetch")
+		if err != nil {
+			return false, fmt.Errorf("cannot fetch %s from %s", repo.url, repo.path)
 		}
 		}
 	}
 	}
 
 
-	if err := os.Chdir(fmt.Sprintf(repo.path)); err != nil {
-		fmt.Println("cant chdir")
+	err = os.Chdir(fmt.Sprintf(repo.path))
+	if err != nil {
+		return false, fmt.Errorf("cannot navigate to %s", repo.path)
 	}
 	}
 
 
 	gitFormat := "--format=%H%n%an%n%s%n%ci"
 	gitFormat := "--format=%H%n%an%n%s%n%ci"
@@ -95,7 +119,7 @@ func (repo *Repo) audit(owner *Owner, opts *Options) error {
 		"--remotes", "--topo-order", gitFormat).Output()
 		"--remotes", "--topo-order", gitFormat).Output()
 
 
 	if err != nil {
 	if err != nil {
-		fmt.Println("problem with rev list")
+		return false, fmt.Errorf("could not retreive rev-list from %s", repo.name)
 	}
 	}
 
 
 	revListLines := bytes.Split(out, []byte("\n"))
 	revListLines := bytes.Split(out, []byte("\n"))
@@ -107,7 +131,7 @@ func (repo *Repo) audit(owner *Owner, opts *Options) error {
 		}
 		}
 
 
 		commitWG.Add(1)
 		commitWG.Add(1)
-		go auditDiff(commit, repo, &commitWG, &gitLeakReceiverWG, opts,
+		go auditDiff(commit, repo, &commitWG, &gitLeakReceiverWG,
 			semaphoreChan, gitLeaksChan)
 			semaphoreChan, gitLeaksChan)
 
 
 		if commit.Hash == opts.SinceCommit {
 		if commit.Hash == opts.SinceCommit {
@@ -117,37 +141,37 @@ func (repo *Repo) audit(owner *Owner, opts *Options) error {
 	go reportAggregator(&gitLeakReceiverWG, gitLeaksChan, &leaks)
 	go reportAggregator(&gitLeakReceiverWG, gitLeaksChan, &leaks)
 	commitWG.Wait()
 	commitWG.Wait()
 	gitLeakReceiverWG.Wait()
 	gitLeakReceiverWG.Wait()
-
-	// repo audit has finished
-	repo.leaks = leaks
-
-	if opts.EnableJSON && len(leaks) != 0 {
-		repo.writeReport(owner)
+	if len(leaks) != 0{
+		leaksPst = true
 	}
 	}
 
 
-	return nil
-}
-
-func (repo *Repo) log() {
-
+	if opts.ReportPath != "" && len(leaks) != 0 {
+		err = repo.writeReport()
+		if err != nil {
+			return leaksPst, fmt.Errorf("could not write report to %s", opts.ReportPath)
+		}
+	}
+	return leaksPst, nil
 }
 }
 
 
 // Used by audit, writeReport will generate a report and write it out to
 // Used by audit, writeReport will generate a report and write it out to
 // $GITLEAKS_HOME/report/<owner>/<repo>. No report will be generated if
 // $GITLEAKS_HOME/report/<owner>/<repo>. No report will be generated if
 // no leaks have been found
 // no leaks have been found
-func (repo *Repo) writeReport(owner *Owner) {
+func (repo *Repo) writeReport() error {
 	reportJSON, _ := json.MarshalIndent(repo.leaks, "", "\t")
 	reportJSON, _ := json.MarshalIndent(repo.leaks, "", "\t")
-	if _, err := os.Stat(owner.reportPath); os.IsNotExist(err) {
-		os.Mkdir(repo.owner.reportPath, os.ModePerm)
+
+	if _, err := os.Stat(opts.ReportPath); os.IsNotExist(err) {
+		os.Mkdir(opts.ReportPath, os.ModePerm)
 	}
 	}
 
 
 	reportFileName := fmt.Sprintf("%s_leaks.json", repo.name)
 	reportFileName := fmt.Sprintf("%s_leaks.json", repo.name)
-	reportFile := filepath.Join(owner.reportPath, reportFileName)
+	reportFile := filepath.Join(opts.ReportPath, reportFileName)
 	err := ioutil.WriteFile(reportFile, reportJSON, 0644)
 	err := ioutil.WriteFile(reportFile, reportJSON, 0644)
 	if err != nil {
 	if err != nil {
-		fmt.Println("cant write report")
+		return err
 	}
 	}
 	fmt.Printf("Report written to %s\n", reportFile)
 	fmt.Printf("Report written to %s\n", reportFile)
+	return nil
 }
 }
 
 
 // parseRevList is responsible for parsing the output of
 // parseRevList is responsible for parsing the output of
@@ -179,6 +203,7 @@ func reportAggregator(gitLeakReceiverWG *sync.WaitGroup, gitLeaks chan Leak, lea
 	for gitLeak := range gitLeaks {
 	for gitLeak := range gitLeaks {
 		b, err := json.MarshalIndent(gitLeak, "", "   ")
 		b, err := json.MarshalIndent(gitLeak, "", "   ")
 		if err != nil {
 		if err != nil {
+			// make waitgroup errArray
 			fmt.Println("failed to output leak:", err)
 			fmt.Println("failed to output leak:", err)
 		}
 		}
 		fmt.Println(string(b))
 		fmt.Println(string(b))
@@ -191,13 +216,14 @@ func reportAggregator(gitLeakReceiverWG *sync.WaitGroup, gitLeaks chan Leak, lea
 // Three channels are input here: 1. a semaphore to bind gitleaks, 2. a leak stream, 3. error handling (TODO)
 // Three channels are input here: 1. a semaphore to bind gitleaks, 2. a leak stream, 3. error handling (TODO)
 // This func performs a diff and runs regexes checks on each line of the diff.
 // This func performs a diff and runs regexes checks on each line of the diff.
 func auditDiff(currCommit Commit, repo *Repo, commitWG *sync.WaitGroup,
 func auditDiff(currCommit Commit, repo *Repo, commitWG *sync.WaitGroup,
-	gitLeakReceiverWG *sync.WaitGroup, opts *Options, semaphoreChan chan struct{},
+	gitLeakReceiverWG *sync.WaitGroup, semaphoreChan chan struct{},
 	gitLeaks chan Leak) {
 	gitLeaks chan Leak) {
 	// signal to WG this diff is done being audited
 	// signal to WG this diff is done being audited
 	defer commitWG.Done()
 	defer commitWG.Done()
 
 
 	if err := os.Chdir(fmt.Sprintf(repo.path)); err != nil {
 	if err := os.Chdir(fmt.Sprintf(repo.path)); err != nil {
-		log.Fatal(err)
+		// TODO handle this better
+		os.Exit(EXIT_FAILURE)
 	}
 	}
 
 
 	commitCmp := fmt.Sprintf("%s^!", currCommit.Hash)
 	commitCmp := fmt.Sprintf("%s^!", currCommit.Hash)
@@ -206,13 +232,10 @@ func auditDiff(currCommit Commit, repo *Repo, commitWG *sync.WaitGroup,
 	<-semaphoreChan
 	<-semaphoreChan
 
 
 	if err != nil {
 	if err != nil {
-		// TODO
-		if strings.Contains(err.Error(), "too many files open") {
-			log.Printf("error retrieving diff for commit %s. Try turning concurrency down. %v\n", currCommit, err)
-		}
+		os.Exit(EXIT_FAILURE)
 	}
 	}
 
 
-	leaks := doChecks(string(out), currCommit, opts, repo)
+	leaks := doChecks(string(out), currCommit, repo)
 	if len(leaks) == 0 {
 	if len(leaks) == 0 {
 		return
 		return
 	}
 	}
@@ -221,4 +244,3 @@ func auditDiff(currCommit Commit, repo *Repo, commitWG *sync.WaitGroup,
 		gitLeaks <- leak
 		gitLeaks <- leak
 	}
 	}
 }
 }
-