| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- package main
- import (
- "fmt"
- "os"
- "regexp"
- "strconv"
- "strings"
- "path/filepath"
- )
- const usage = `usage: gitleaks [options] <URL>/<path_to_repo>
- Options:
- 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
- -h --help Display this message
- --token=<STR> Github API token
- --stopwords Enables stopwords
- --pretty Enables pretty printing for humans, otherwise you'll get logs'
- `
- // Options for gitleaks. need to support remote repo/owner
- // and local repo/owner mode
- type Options struct {
- URL string
- RepoPath string
- ReportPath string
- Concurrency int
- B64EntropyCutoff int
- HexEntropyCutoff int
- // MODES
- UserMode bool
- OrgMode bool
- RepoMode bool
- LocalMode bool
- // OPTS
- Strict bool
- Entropy bool
- SinceCommit string
- Persist bool
- IncludeForks bool
- Tmp bool
- ReportOut bool
- Token string
- // LOGS/REPORT
- LogLevel int
- PrettyPrint bool
- }
- // help prints the usage string and exits
- func help() {
- os.Stderr.WriteString(usage)
- }
- // optionsNextInt is a parseOptions helper that returns the value (int) of an option if valid
- func (opts *Options) nextInt(args []string, i *int) int {
- if len(args) > *i+1 {
- *i++
- } else {
- help()
- }
- argInt, err := strconv.Atoi(args[*i])
- if err != nil {
- opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
- }
- return argInt
- }
- // optionsNextString is a parseOptions helper that returns the value (string) of an option if valid
- func (opts *Options) nextString(args []string, i *int) string {
- if len(args) > *i+1 {
- *i++
- } else {
- opts.failF("Invalid %s option: %s\n", args[*i-1], args[*i])
- }
- return args[*i]
- }
- // 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)
- }
- 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,
- B64EntropyCutoff: 70,
- HexEntropyCutoff: 40,
- }, nil
- }
- // parseOptions
- func (opts *Options) parseOptions(args []string) error {
- if len(args) == 0 {
- opts.LocalMode = true
- opts.RepoPath, _ = os.Getwd()
- }
- for i := 0; i < len(args); i++ {
- arg := args[i]
- switch arg {
- case "-s":
- opts.SinceCommit = opts.nextString(args, &i)
- case "--strict":
- opts.Strict = true
- case "-b", "--b64Entropy":
- opts.B64EntropyCutoff = opts.nextInt(args, &i)
- case "-x", "--hexEntropy":
- opts.HexEntropyCutoff = opts.nextInt(args, &i)
- case "-e", "--entropy":
- opts.Entropy = true
- case "-c":
- opts.Concurrency = opts.nextInt(args, &i)
- case "-o", "--org":
- opts.OrgMode = true
- case "-u", "--user":
- opts.UserMode = true
- case "-r", "--repo":
- opts.RepoMode = true
- case "-l", "--local":
- opts.LocalMode = true
- case "--report-out":
- opts.ReportOut = true
- case "--pretty":
- opts.PrettyPrint = true
- case "-t", "--temp":
- opts.Tmp = true
- case "-ll":
- opts.LogLevel = opts.nextInt(args, &i)
- case "-h", "--help":
- help()
- os.Exit(EXIT_CLEAN)
- default:
- // TARGETS
- if i == len(args)-1 {
- if opts.LocalMode {
- opts.RepoPath = filepath.Clean(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.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 {
- fmt.Printf("Unknown option %s\n\n", arg)
- help()
- return fmt.Errorf("Unknown option %s\n\n", arg)
- }
- }
- }
- err := opts.guards()
- if !opts.RepoMode && !opts.UserMode && !opts.OrgMode && !opts.LocalMode {
- opts.RepoMode = true
- }
- return err
- }
- // 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) && opts.LocalMode {
- return fmt.Errorf("Cannot run Gitleaks on repo/user/org mode and local mode\n")
- } else 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.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 nil
- }
- // isGithubTarget checks if url is a valid github target
- func isGithubTarget(url string) bool {
- re := regexp.MustCompile("github.com")
- return re.MatchString(url)
- }
|