diff --git a/cli/.infisicalignore b/cli/.infisicalignore new file mode 100644 index 0000000000..e5dfe29bca --- /dev/null +++ b/cli/.infisicalignore @@ -0,0 +1,3 @@ +bea0ff6e05a4de73a5db625d4ae181a015b50855:frontend/components/utilities/attemptLogin.js:stripe-access-token:147 +bea0ff6e05a4de73a5db625d4ae181a015b50855:backend/src/json/integrations.json:generic-api-key:5 +1961b92340e5d2613acae528b886c842427ce5d0:frontend/components/utilities/attemptLogin.js:stripe-access-token:148 diff --git a/cli/config/allowlist.go b/cli/config/allowlist.go new file mode 100644 index 0000000000..3733257588 --- /dev/null +++ b/cli/config/allowlist.go @@ -0,0 +1,85 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package config + +import ( + "regexp" + "strings" +) + +// Allowlist allows a rule to be ignored for specific +// regexes, paths, and/or commits +type Allowlist struct { + // Short human readable description of the allowlist. + Description string + + // Regexes is slice of content regular expressions that are allowed to be ignored. + Regexes []*regexp.Regexp + + // RegexTarget + RegexTarget string + + // Paths is a slice of path regular expressions that are allowed to be ignored. + Paths []*regexp.Regexp + + // Commits is a slice of commit SHAs that are allowed to be ignored. + Commits []string + + // StopWords is a slice of stop words that are allowed to be ignored. + // This targets the _secret_, not the content of the regex match like the + // Regexes slice. + StopWords []string +} + +// CommitAllowed returns true if the commit is allowed to be ignored. +func (a *Allowlist) CommitAllowed(c string) bool { + if c == "" { + return false + } + for _, commit := range a.Commits { + if commit == c { + return true + } + } + return false +} + +// PathAllowed returns true if the path is allowed to be ignored. +func (a *Allowlist) PathAllowed(path string) bool { + return anyRegexMatch(path, a.Paths) +} + +// RegexAllowed returns true if the regex is allowed to be ignored. +func (a *Allowlist) RegexAllowed(s string) bool { + return anyRegexMatch(s, a.Regexes) +} + +func (a *Allowlist) ContainsStopWord(s string) bool { + s = strings.ToLower(s) + for _, stopWord := range a.StopWords { + if strings.Contains(s, strings.ToLower(stopWord)) { + return true + } + } + return false +} diff --git a/cli/config/allowlist_test.go b/cli/config/allowlist_test.go new file mode 100644 index 0000000000..52766e3cde --- /dev/null +++ b/cli/config/allowlist_test.go @@ -0,0 +1,115 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package config + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommitAllowed(t *testing.T) { + tests := []struct { + allowlist Allowlist + commit string + commitAllowed bool + }{ + { + allowlist: Allowlist{ + Commits: []string{"commitA"}, + }, + commit: "commitA", + commitAllowed: true, + }, + { + allowlist: Allowlist{ + Commits: []string{"commitB"}, + }, + commit: "commitA", + commitAllowed: false, + }, + { + allowlist: Allowlist{ + Commits: []string{"commitB"}, + }, + commit: "", + commitAllowed: false, + }, + } + for _, tt := range tests { + assert.Equal(t, tt.commitAllowed, tt.allowlist.CommitAllowed(tt.commit)) + } +} + +func TestRegexAllowed(t *testing.T) { + tests := []struct { + allowlist Allowlist + secret string + regexAllowed bool + }{ + { + allowlist: Allowlist{ + Regexes: []*regexp.Regexp{regexp.MustCompile("matchthis")}, + }, + secret: "a secret: matchthis, done", + regexAllowed: true, + }, + { + allowlist: Allowlist{ + Regexes: []*regexp.Regexp{regexp.MustCompile("matchthis")}, + }, + secret: "a secret", + regexAllowed: false, + }, + } + for _, tt := range tests { + assert.Equal(t, tt.regexAllowed, tt.allowlist.RegexAllowed(tt.secret)) + } +} + +func TestPathAllowed(t *testing.T) { + tests := []struct { + allowlist Allowlist + path string + pathAllowed bool + }{ + { + allowlist: Allowlist{ + Paths: []*regexp.Regexp{regexp.MustCompile("path")}, + }, + path: "a path", + pathAllowed: true, + }, + { + allowlist: Allowlist{ + Paths: []*regexp.Regexp{regexp.MustCompile("path")}, + }, + path: "a ???", + pathAllowed: false, + }, + } + for _, tt := range tests { + assert.Equal(t, tt.pathAllowed, tt.allowlist.PathAllowed(tt.path)) + } +} diff --git a/cli/config/config.go b/cli/config/config.go new file mode 100644 index 0000000000..b071c7d4fb --- /dev/null +++ b/cli/config/config.go @@ -0,0 +1,275 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package config + +import ( + _ "embed" + "fmt" + "regexp" + "strings" + + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +//go:embed gitleaks.toml +var DefaultConfig string + +// use to keep track of how many configs we can extend +// yea I know, globals bad +var extendDepth int + +const maxExtendDepth = 2 + +// ViperConfig is the config struct used by the Viper config package +// to parse the config file. This struct does not include regular expressions. +// It is used as an intermediary to convert the Viper config to the Config struct. +type ViperConfig struct { + Description string + Extend Extend + Rules []struct { + ID string + Description string + Entropy float64 + SecretGroup int + Regex string + Keywords []string + Path string + Tags []string + + Allowlist struct { + RegexTarget string + Regexes []string + Paths []string + Commits []string + StopWords []string + } + } + Allowlist struct { + RegexTarget string + Regexes []string + Paths []string + Commits []string + StopWords []string + } +} + +// Config is a configuration struct that contains rules and an allowlist if present. +type Config struct { + Extend Extend + Path string + Description string + Rules map[string]Rule + Allowlist Allowlist + Keywords []string + + // used to keep sarif results consistent + orderedRules []string +} + +// Extend is a struct that allows users to define how they want their +// configuration extended by other configuration files. +type Extend struct { + Path string + URL string + UseDefault bool +} + +func (vc *ViperConfig) Translate() (Config, error) { + var ( + keywords []string + orderedRules []string + ) + rulesMap := make(map[string]Rule) + + for _, r := range vc.Rules { + var allowlistRegexes []*regexp.Regexp + for _, a := range r.Allowlist.Regexes { + allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a)) + } + var allowlistPaths []*regexp.Regexp + for _, a := range r.Allowlist.Paths { + allowlistPaths = append(allowlistPaths, regexp.MustCompile(a)) + } + + if r.Keywords == nil { + r.Keywords = []string{} + } else { + for _, k := range r.Keywords { + keywords = append(keywords, strings.ToLower(k)) + } + } + + if r.Tags == nil { + r.Tags = []string{} + } + + var configRegex *regexp.Regexp + var configPathRegex *regexp.Regexp + if r.Regex == "" { + configRegex = nil + } else { + configRegex = regexp.MustCompile(r.Regex) + } + if r.Path == "" { + configPathRegex = nil + } else { + configPathRegex = regexp.MustCompile(r.Path) + } + r := Rule{ + Description: r.Description, + RuleID: r.ID, + Regex: configRegex, + Path: configPathRegex, + SecretGroup: r.SecretGroup, + Entropy: r.Entropy, + Tags: r.Tags, + Keywords: r.Keywords, + Allowlist: Allowlist{ + RegexTarget: r.Allowlist.RegexTarget, + Regexes: allowlistRegexes, + Paths: allowlistPaths, + Commits: r.Allowlist.Commits, + StopWords: r.Allowlist.StopWords, + }, + } + orderedRules = append(orderedRules, r.RuleID) + + if r.Regex != nil && r.SecretGroup > r.Regex.NumSubexp() { + return Config{}, fmt.Errorf("%s invalid regex secret group %d, max regex secret group %d", r.Description, r.SecretGroup, r.Regex.NumSubexp()) + } + rulesMap[r.RuleID] = r + } + var allowlistRegexes []*regexp.Regexp + for _, a := range vc.Allowlist.Regexes { + allowlistRegexes = append(allowlistRegexes, regexp.MustCompile(a)) + } + var allowlistPaths []*regexp.Regexp + for _, a := range vc.Allowlist.Paths { + allowlistPaths = append(allowlistPaths, regexp.MustCompile(a)) + } + c := Config{ + Description: vc.Description, + Extend: vc.Extend, + Rules: rulesMap, + Allowlist: Allowlist{ + RegexTarget: vc.Allowlist.RegexTarget, + Regexes: allowlistRegexes, + Paths: allowlistPaths, + Commits: vc.Allowlist.Commits, + StopWords: vc.Allowlist.StopWords, + }, + Keywords: keywords, + orderedRules: orderedRules, + } + + if maxExtendDepth != extendDepth { + // disallow both usedefault and path from being set + if c.Extend.Path != "" && c.Extend.UseDefault { + log.Fatal().Msg("unable to load config due to extend.path and extend.useDefault being set") + } + if c.Extend.UseDefault { + c.extendDefault() + } else if c.Extend.Path != "" { + c.extendPath() + } + + } + + return c, nil +} + +func (c *Config) OrderedRules() []Rule { + var orderedRules []Rule + for _, id := range c.orderedRules { + if _, ok := c.Rules[id]; ok { + orderedRules = append(orderedRules, c.Rules[id]) + } + } + return orderedRules +} + +func (c *Config) extendDefault() { + extendDepth++ + viper.SetConfigType("toml") + if err := viper.ReadConfig(strings.NewReader(DefaultConfig)); err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + defaultViperConfig := ViperConfig{} + if err := viper.Unmarshal(&defaultViperConfig); err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + cfg, err := defaultViperConfig.Translate() + if err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + log.Debug().Msg("extending config with default config") + c.extend(cfg) + +} + +func (c *Config) extendPath() { + extendDepth++ + viper.SetConfigFile(c.Extend.Path) + if err := viper.ReadInConfig(); err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + extensionViperConfig := ViperConfig{} + if err := viper.Unmarshal(&extensionViperConfig); err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + cfg, err := extensionViperConfig.Translate() + if err != nil { + log.Fatal().Msgf("failed to load extended config, err: %s", err) + return + } + log.Debug().Msgf("extending config with %s", c.Extend.Path) + c.extend(cfg) +} + +func (c *Config) extendURL() { + // TODO +} + +func (c *Config) extend(extensionConfig Config) { + for ruleID, rule := range extensionConfig.Rules { + if _, ok := c.Rules[ruleID]; !ok { + log.Trace().Msgf("adding %s to base config", ruleID) + c.Rules[ruleID] = rule + c.Keywords = append(c.Keywords, rule.Keywords...) + } + } + + // append allowlists, not attempting to merge + c.Allowlist.Commits = append(c.Allowlist.Commits, + extensionConfig.Allowlist.Commits...) + c.Allowlist.Paths = append(c.Allowlist.Paths, + extensionConfig.Allowlist.Paths...) + c.Allowlist.Regexes = append(c.Allowlist.Regexes, + extensionConfig.Allowlist.Regexes...) +} diff --git a/cli/config/config_test.go b/cli/config/config_test.go new file mode 100644 index 0000000000..e8f4d47b13 --- /dev/null +++ b/cli/config/config_test.go @@ -0,0 +1,170 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package config + +import ( + "fmt" + "regexp" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +const configPath = "../testdata/config/" + +func TestTranslate(t *testing.T) { + tests := []struct { + cfgName string + cfg Config + wantError error + }{ + { + cfgName: "allow_aws_re", + cfg: Config{ + Rules: map[string]Rule{"aws-access-key": { + Description: "AWS Access Key", + Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-access-key", + Allowlist: Allowlist{ + Regexes: []*regexp.Regexp{ + regexp.MustCompile("AKIALALEMEL33243OLIA"), + }, + }, + }, + }, + }, + }, + { + cfgName: "allow_commit", + cfg: Config{ + Rules: map[string]Rule{"aws-access-key": { + Description: "AWS Access Key", + Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-access-key", + Allowlist: Allowlist{ + Commits: []string{"allowthiscommit"}, + }, + }, + }, + }, + }, + { + cfgName: "allow_path", + cfg: Config{ + Rules: map[string]Rule{"aws-access-key": { + Description: "AWS Access Key", + Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-access-key", + Allowlist: Allowlist{ + Paths: []*regexp.Regexp{ + regexp.MustCompile(".go"), + }, + }, + }, + }, + }, + }, + { + cfgName: "entropy_group", + cfg: Config{ + Rules: map[string]Rule{"discord-api-key": { + Description: "Discord API key", + Regex: regexp.MustCompile(`(?i)(discord[a-z0-9_ .\-,]{0,25})(=|>|:=|\|\|:|<=|=>|:).{0,5}['\"]([a-h0-9]{64})['\"]`), + RuleID: "discord-api-key", + Allowlist: Allowlist{}, + Entropy: 3.5, + SecretGroup: 3, + Tags: []string{}, + Keywords: []string{}, + }, + }, + }, + }, + { + cfgName: "bad_entropy_group", + cfg: Config{}, + wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"), + }, + { + cfgName: "base", + cfg: Config{ + Rules: map[string]Rule{ + "aws-access-key": { + Description: "AWS Access Key", + Regex: regexp.MustCompile("(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-access-key", + }, + "aws-secret-key": { + Description: "AWS Secret Key", + Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-secret-key", + }, + "aws-secret-key-again": { + Description: "AWS Secret Key", + Regex: regexp.MustCompile(`(?i)aws_(.{0,20})?=?.[\'\"0-9a-zA-Z\/+]{40}`), + Tags: []string{"key", "AWS"}, + Keywords: []string{}, + RuleID: "aws-secret-key-again", + }, + }, + }, + }, + } + + for _, tt := range tests { + viper.Reset() + viper.AddConfigPath(configPath) + viper.SetConfigName(tt.cfgName) + viper.SetConfigType("toml") + err := viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, err := vc.Translate() + if tt.wantError != nil { + if err == nil { + t.Errorf("expected error") + } + assert.Equal(t, tt.wantError, err) + } + + assert.Equal(t, cfg.Rules, tt.cfg.Rules) + } +} diff --git a/cli/config/gitleaks.toml b/cli/config/gitleaks.toml new file mode 100644 index 0000000000..6d0f55f2ab --- /dev/null +++ b/cli/config/gitleaks.toml @@ -0,0 +1,2803 @@ + +# This file has been auto-generated. Do not edit manually. +# If you would like to contribute new rules, please use +# cmd/generate/config/main.go and follow the contributing guidelines +# at https://github.com/zricethezav/gitleaks/blob/master/CONTRIBUTING.md + +# This is the default gitleaks configuration file. +# Rules and allowlists are defined within this file. +# Rules instruct gitleaks on what should be considered a secret. +# Allowlists instruct gitleaks on what is allowed, i.e. not a secret. + +title = "gitleaks config" + +[allowlist] +description = "global allow lists" +paths = [ + '''gitleaks.toml''', + '''(.*?)(jpg|gif|doc|docx|zip|xls|pdf|bin|svg|socket)$''', + '''(go.mod|go.sum)$''', + '''gradle.lockfile''', + '''node_modules''', + '''package-lock.json''', + '''pnpm-lock.yaml''', + '''Database.refactorlog''', + '''vendor''', +] + +[[rules]] +description = "Adafruit API Key" +id = "adafruit-api-key" +regex = '''(?i)(?:adafruit)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "adafruit", +] + +[[rules]] +description = "Adobe Client ID (OAuth Web)" +id = "adobe-client-id" +regex = '''(?i)(?:adobe)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "adobe", +] + +[[rules]] +description = "Adobe Client Secret" +id = "adobe-client-secret" +regex = '''(?i)\b((p8e-)(?i)[a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "p8e-", +] + +[[rules]] +description = "Age secret key" +id = "age secret key" +regex = '''AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}''' +keywords = [ + "age-secret-key-1", +] + +[[rules]] +description = "Airtable API Key" +id = "airtable-api-key" +regex = '''(?i)(?:airtable)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{17})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "airtable", +] + +[[rules]] +description = "Algolia API Key" +id = "algolia-api-key" +regex = '''(?i)(?:algolia)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "algolia", +] + +[[rules]] +description = "Alibaba AccessKey ID" +id = "alibaba-access-key-id" +regex = '''(?i)\b((LTAI)(?i)[a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "ltai", +] + +[[rules]] +description = "Alibaba Secret Key" +id = "alibaba-secret-key" +regex = '''(?i)(?:alibaba)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "alibaba", +] + +[[rules]] +description = "Asana Client ID" +id = "asana-client-id" +regex = '''(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "asana", +] + +[[rules]] +description = "Asana Client Secret" +id = "asana-client-secret" +regex = '''(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "asana", +] + +[[rules]] +description = "Atlassian API token" +id = "atlassian-api-token" +regex = '''(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "atlassian","confluence","jira", +] + +[[rules]] +description = "Authress Service Client Access Key" +id = "authress-service-client-access-key" +regex = '''(?i)\b((?:sc|ext|scauth|authress)_[a-z0-9]{5,30}\.[a-z0-9]{4,6}\.acc_[a-z0-9-]{10,32}\.[a-z0-9+/_=-]{30,120})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sc_","ext_","scauth_","authress_", +] + +[[rules]] +description = "AWS" +id = "aws-access-token" +regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}''' +keywords = [ + "akia","agpa","aida","aroa","aipa","anpa","anva","asia", +] + +[[rules]] +description = "Beamer API token" +id = "beamer-api-token" +regex = '''(?i)(?:beamer)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(b_[a-z0-9=_\-]{44})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "beamer", +] + +[[rules]] +description = "Bitbucket Client ID" +id = "bitbucket-client-id" +regex = '''(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "bitbucket", +] + +[[rules]] +description = "Bitbucket Client Secret" +id = "bitbucket-client-secret" +regex = '''(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "bitbucket", +] + +[[rules]] +description = "Bittrex Access Key" +id = "bittrex-access-key" +regex = '''(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "bittrex", +] + +[[rules]] +description = "Bittrex Secret Key" +id = "bittrex-secret-key" +regex = '''(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "bittrex", +] + +[[rules]] +description = "Clojars API token" +id = "clojars-api-token" +regex = '''(?i)(CLOJARS_)[a-z0-9]{60}''' +keywords = [ + "clojars", +] + +[[rules]] +description = "Codecov Access Token" +id = "codecov-access-token" +regex = '''(?i)(?:codecov)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "codecov", +] + +[[rules]] +description = "Coinbase Access Token" +id = "coinbase-access-token" +regex = '''(?i)(?:coinbase)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "coinbase", +] + +[[rules]] +description = "Confluent Access Token" +id = "confluent-access-token" +regex = '''(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "confluent", +] + +[[rules]] +description = "Confluent Secret Key" +id = "confluent-secret-key" +regex = '''(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "confluent", +] + +[[rules]] +description = "Contentful delivery API token" +id = "contentful-delivery-api-token" +regex = '''(?i)(?:contentful)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "contentful", +] + +[[rules]] +description = "Databricks API token" +id = "databricks-api-token" +regex = '''(?i)\b(dapi[a-h0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "dapi", +] + +[[rules]] +description = "Datadog Access Token" +id = "datadog-access-token" +regex = '''(?i)(?:datadog)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "datadog", +] + +[[rules]] +description = "Defined Networking API token" +id = "defined-networking-api-token" +regex = '''(?i)(?:dnkey)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(dnkey-[a-z0-9=_\-]{26}-[a-z0-9=_\-]{52})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "dnkey", +] + +[[rules]] +description = "DigitalOcean OAuth Access Token" +id = "digitalocean-access-token" +regex = '''(?i)\b(doo_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "doo_v1_", +] + +[[rules]] +description = "DigitalOcean Personal Access Token" +id = "digitalocean-pat" +regex = '''(?i)\b(dop_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "dop_v1_", +] + +[[rules]] +description = "DigitalOcean OAuth Refresh Token" +id = "digitalocean-refresh-token" +regex = '''(?i)\b(dor_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "dor_v1_", +] + +[[rules]] +description = "Discord API key" +id = "discord-api-token" +regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "discord", +] + +[[rules]] +description = "Discord client ID" +id = "discord-client-id" +regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9]{18})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "discord", +] + +[[rules]] +description = "Discord client secret" +id = "discord-client-secret" +regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "discord", +] + +[[rules]] +description = "Doppler API token" +id = "doppler-api-token" +regex = '''(dp\.pt\.)(?i)[a-z0-9]{43}''' +keywords = [ + "doppler", +] + +[[rules]] +description = "Droneci Access Token" +id = "droneci-access-token" +regex = '''(?i)(?:droneci)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "droneci", +] + +[[rules]] +description = "Dropbox API secret" +id = "dropbox-api-token" +regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{15})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "dropbox", +] + +[[rules]] +description = "Dropbox long lived API token" +id = "dropbox-long-lived-api-token" +regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\-_=]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "dropbox", +] + +[[rules]] +description = "Dropbox short lived API token" +id = "dropbox-short-lived-api-token" +regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(sl\.[a-z0-9\-=_]{135})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "dropbox", +] + +[[rules]] +description = "Duffel API token" +id = "duffel-api-token" +regex = '''duffel_(test|live)_(?i)[a-z0-9_\-=]{43}''' +keywords = [ + "duffel", +] + +[[rules]] +description = "Dynatrace API token" +id = "dynatrace-api-token" +regex = '''dt0c01\.(?i)[a-z0-9]{24}\.[a-z0-9]{64}''' +keywords = [ + "dynatrace", +] + +[[rules]] +description = "EasyPost API token" +id = "easypost-api-token" +regex = '''\bEZAK(?i)[a-z0-9]{54}''' +keywords = [ + "ezak", +] + +[[rules]] +description = "EasyPost test API token" +id = "easypost-test-api-token" +regex = '''\bEZTK(?i)[a-z0-9]{54}''' +keywords = [ + "eztk", +] + +[[rules]] +description = "Etsy Access Token" +id = "etsy-access-token" +regex = '''(?i)(?:etsy)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "etsy", +] + +[[rules]] +description = "Facebook Access Token" +id = "facebook" +regex = '''(?i)(?:facebook)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "facebook", +] + +[[rules]] +description = "Fastly API key" +id = "fastly-api-token" +regex = '''(?i)(?:fastly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "fastly", +] + +[[rules]] +description = "Finicity API token" +id = "finicity-api-token" +regex = '''(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "finicity", +] + +[[rules]] +description = "Finicity Client Secret" +id = "finicity-client-secret" +regex = '''(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "finicity", +] + +[[rules]] +description = "Finnhub Access Token" +id = "finnhub-access-token" +regex = '''(?i)(?:finnhub)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "finnhub", +] + +[[rules]] +description = "Flickr Access Token" +id = "flickr-access-token" +regex = '''(?i)(?:flickr)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "flickr", +] + +[[rules]] +description = "Flutterwave Encryption Key" +id = "flutterwave-encryption-key" +regex = '''FLWSECK_TEST-(?i)[a-h0-9]{12}''' +keywords = [ + "flwseck_test", +] + +[[rules]] +description = "Finicity Public Key" +id = "flutterwave-public-key" +regex = '''FLWPUBK_TEST-(?i)[a-h0-9]{32}-X''' +keywords = [ + "flwpubk_test", +] + +[[rules]] +description = "Flutterwave Secret Key" +id = "flutterwave-secret-key" +regex = '''FLWSECK_TEST-(?i)[a-h0-9]{32}-X''' +keywords = [ + "flwseck_test", +] + +[[rules]] +description = "Frame.io API token" +id = "frameio-api-token" +regex = '''fio-u-(?i)[a-z0-9\-_=]{64}''' +keywords = [ + "fio-u-", +] + +[[rules]] +description = "Freshbooks Access Token" +id = "freshbooks-access-token" +regex = '''(?i)(?:freshbooks)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "freshbooks", +] + +[[rules]] +description = "GCP API key" +id = "gcp-api-key" +regex = '''(?i)\b(AIza[0-9A-Za-z\\-_]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "aiza", +] + +[[rules]] +description = "Generic API Key" +id = "generic-api-key" +regex = '''(?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-z\-_.=]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +entropy = 3.5 +keywords = [ + "key","api","token","secret","client","passwd","password","auth","access", +] +[rules.allowlist] +stopwords= [ + "client", + "endpoint", + "vpn", + "_ec2_", + "aws_", + "authorize", + "author", + "define", + "config", + "credential", + "setting", + "sample", + "xxxxxx", + "000000", + "buffer", + "delete", + "aaaaaa", + "fewfwef", + "getenv", + "env_", + "system", + "example", + "ecdsa", + "sha256", + "sha1", + "sha2", + "md5", + "alert", + "wizard", + "target", + "onboard", + "welcome", + "page", + "exploit", + "experiment", + "expire", + "rabbitmq", + "scraper", + "widget", + "music", + "dns_", + "dns-", + "yahoo", + "want", + "json", + "action", + "script", + "fix_", + "fix-", + "develop", + "compas", + "stripe", + "service", + "master", + "metric", + "tech", + "gitignore", + "rich", + "open", + "stack", + "irc_", + "irc-", + "sublime", + "kohana", + "has_", + "has-", + "fabric", + "wordpres", + "role", + "osx_", + "osx-", + "boost", + "addres", + "queue", + "working", + "sandbox", + "internet", + "print", + "vision", + "tracking", + "being", + "generator", + "traffic", + "world", + "pull", + "rust", + "watcher", + "small", + "auth", + "full", + "hash", + "more", + "install", + "auto", + "complete", + "learn", + "paper", + "installer", + "research", + "acces", + "last", + "binding", + "spine", + "into", + "chat", + "algorithm", + "resource", + "uploader", + "video", + "maker", + "next", + "proc", + "lock", + "robot", + "snake", + "patch", + "matrix", + "drill", + "terminal", + "term", + "stuff", + "genetic", + "generic", + "identity", + "audit", + "pattern", + "audio", + "web_", + "web-", + "crud", + "problem", + "statu", + "cms-", + "cms_", + "arch", + "coffee", + "workflow", + "changelog", + "another", + "uiview", + "content", + "kitchen", + "gnu_", + "gnu-", + "gnu.", + "conf", + "couchdb", + "client", + "opencv", + "rendering", + "update", + "concept", + "varnish", + "gui_", + "gui-", + "gui.", + "version", + "shared", + "extra", + "product", + "still", + "not_", + "not-", + "not.", + "drop", + "ring", + "png_", + "png-", + "png.", + "actively", + "import", + "output", + "backup", + "start", + "embedded", + "registry", + "pool", + "semantic", + "instagram", + "bash", + "system", + "ninja", + "drupal", + "jquery", + "polyfill", + "physic", + "league", + "guide", + "pack", + "synopsi", + "sketch", + "injection", + "svg_", + "svg-", + "svg.", + "friendly", + "wave", + "convert", + "manage", + "camera", + "link", + "slide", + "timer", + "wrapper", + "gallery", + "url_", + "url-", + "url.", + "todomvc", + "requirej", + "party", + "http", + "payment", + "async", + "library", + "home", + "coco", + "gaia", + "display", + "universal", + "func", + "metadata", + "hipchat", + "under", + "room", + "config", + "personal", + "realtime", + "resume", + "database", + "testing", + "tiny", + "basic", + "forum", + "meetup", + "yet_", + "yet-", + "yet.", + "cento", + "dead", + "fluentd", + "editor", + "utilitie", + "run_", + "run-", + "run.", + "box_", + "box-", + "box.", + "bot_", + "bot-", + "bot.", + "making", + "sample", + "group", + "monitor", + "ajax", + "parallel", + "cassandra", + "ultimate", + "site", + "get_", + "get-", + "get.", + "gen_", + "gen-", + "gen.", + "gem_", + "gem-", + "gem.", + "extended", + "image", + "knife", + "asset", + "nested", + "zero", + "plugin", + "bracket", + "mule", + "mozilla", + "number", + "act_", + "act-", + "act.", + "map_", + "map-", + "map.", + "micro", + "debug", + "openshift", + "chart", + "expres", + "backend", + "task", + "source", + "translate", + "jbos", + "composer", + "sqlite", + "profile", + "mustache", + "mqtt", + "yeoman", + "have", + "builder", + "smart", + "like", + "oauth", + "school", + "guideline", + "captcha", + "filter", + "bitcoin", + "bridge", + "color", + "toolbox", + "discovery", + "new_", + "new-", + "new.", + "dashboard", + "when", + "setting", + "level", + "post", + "standard", + "port", + "platform", + "yui_", + "yui-", + "yui.", + "grunt", + "animation", + "haskell", + "icon", + "latex", + "cheat", + "lua_", + "lua-", + "lua.", + "gulp", + "case", + "author", + "without", + "simulator", + "wifi", + "directory", + "lisp", + "list", + "flat", + "adventure", + "story", + "storm", + "gpu_", + "gpu-", + "gpu.", + "store", + "caching", + "attention", + "solr", + "logger", + "demo", + "shortener", + "hadoop", + "finder", + "phone", + "pipeline", + "range", + "textmate", + "showcase", + "app_", + "app-", + "app.", + "idiomatic", + "edit", + "our_", + "our-", + "our.", + "out_", + "out-", + "out.", + "sentiment", + "linked", + "why_", + "why-", + "why.", + "local", + "cube", + "gmail", + "job_", + "job-", + "job.", + "rpc_", + "rpc-", + "rpc.", + "contest", + "tcp_", + "tcp-", + "tcp.", + "usage", + "buildout", + "weather", + "transfer", + "automated", + "sphinx", + "issue", + "sas_", + "sas-", + "sas.", + "parallax", + "jasmine", + "addon", + "machine", + "solution", + "dsl_", + "dsl-", + "dsl.", + "episode", + "menu", + "theme", + "best", + "adapter", + "debugger", + "chrome", + "tutorial", + "life", + "step", + "people", + "joomla", + "paypal", + "developer", + "solver", + "team", + "current", + "love", + "visual", + "date", + "data", + "canva", + "container", + "future", + "xml_", + "xml-", + "xml.", + "twig", + "nagio", + "spatial", + "original", + "sync", + "archived", + "refinery", + "science", + "mapping", + "gitlab", + "play", + "ext_", + "ext-", + "ext.", + "session", + "impact", + "set_", + "set-", + "set.", + "see_", + "see-", + "see.", + "migration", + "commit", + "community", + "shopify", + "what'", + "cucumber", + "statamic", + "mysql", + "location", + "tower", + "line", + "code", + "amqp", + "hello", + "send", + "index", + "high", + "notebook", + "alloy", + "python", + "field", + "document", + "soap", + "edition", + "email", + "php_", + "php-", + "php.", + "command", + "transport", + "official", + "upload", + "study", + "secure", + "angularj", + "akka", + "scalable", + "package", + "request", + "con_", + "con-", + "con.", + "flexible", + "security", + "comment", + "module", + "flask", + "graph", + "flash", + "apache", + "change", + "window", + "space", + "lambda", + "sheet", + "bookmark", + "carousel", + "friend", + "objective", + "jekyll", + "bootstrap", + "first", + "article", + "gwt_", + "gwt-", + "gwt.", + "classic", + "media", + "websocket", + "touch", + "desktop", + "real", + "read", + "recorder", + "moved", + "storage", + "validator", + "add-on", + "pusher", + "scs_", + "scs-", + "scs.", + "inline", + "asp_", + "asp-", + "asp.", + "timeline", + "base", + "encoding", + "ffmpeg", + "kindle", + "tinymce", + "pretty", + "jpa_", + "jpa-", + "jpa.", + "used", + "user", + "required", + "webhook", + "download", + "resque", + "espresso", + "cloud", + "mongo", + "benchmark", + "pure", + "cakephp", + "modx", + "mode", + "reactive", + "fuel", + "written", + "flickr", + "mail", + "brunch", + "meteor", + "dynamic", + "neo_", + "neo-", + "neo.", + "new_", + "new-", + "new.", + "net_", + "net-", + "net.", + "typo", + "type", + "keyboard", + "erlang", + "adobe", + "logging", + "ckeditor", + "message", + "iso_", + "iso-", + "iso.", + "hook", + "ldap", + "folder", + "reference", + "railscast", + "www_", + "www-", + "www.", + "tracker", + "azure", + "fork", + "form", + "digital", + "exporter", + "skin", + "string", + "template", + "designer", + "gollum", + "fluent", + "entity", + "language", + "alfred", + "summary", + "wiki", + "kernel", + "calendar", + "plupload", + "symfony", + "foundry", + "remote", + "talk", + "search", + "dev_", + "dev-", + "dev.", + "del_", + "del-", + "del.", + "token", + "idea", + "sencha", + "selector", + "interface", + "create", + "fun_", + "fun-", + "fun.", + "groovy", + "query", + "grail", + "red_", + "red-", + "red.", + "laravel", + "monkey", + "slack", + "supported", + "instant", + "value", + "center", + "latest", + "work", + "but_", + "but-", + "but.", + "bug_", + "bug-", + "bug.", + "virtual", + "tweet", + "statsd", + "studio", + "path", + "real-time", + "frontend", + "notifier", + "coding", + "tool", + "firmware", + "flow", + "random", + "mediawiki", + "bosh", + "been", + "beer", + "lightbox", + "theory", + "origin", + "redmine", + "hub_", + "hub-", + "hub.", + "require", + "pro_", + "pro-", + "pro.", + "ant_", + "ant-", + "ant.", + "any_", + "any-", + "any.", + "recipe", + "closure", + "mapper", + "event", + "todo", + "model", + "redi", + "provider", + "rvm_", + "rvm-", + "rvm.", + "program", + "memcached", + "rail", + "silex", + "foreman", + "activity", + "license", + "strategy", + "batch", + "streaming", + "fast", + "use_", + "use-", + "use.", + "usb_", + "usb-", + "usb.", + "impres", + "academy", + "slider", + "please", + "layer", + "cros", + "now_", + "now-", + "now.", + "miner", + "extension", + "own_", + "own-", + "own.", + "app_", + "app-", + "app.", + "debian", + "symphony", + "example", + "feature", + "serie", + "tree", + "project", + "runner", + "entry", + "leetcode", + "layout", + "webrtc", + "logic", + "login", + "worker", + "toolkit", + "mocha", + "support", + "back", + "inside", + "device", + "jenkin", + "contact", + "fake", + "awesome", + "ocaml", + "bit_", + "bit-", + "bit.", + "drive", + "screen", + "prototype", + "gist", + "binary", + "nosql", + "rest", + "overview", + "dart", + "dark", + "emac", + "mongoid", + "solarized", + "homepage", + "emulator", + "commander", + "django", + "yandex", + "gradle", + "xcode", + "writer", + "crm_", + "crm-", + "crm.", + "jade", + "startup", + "error", + "using", + "format", + "name", + "spring", + "parser", + "scratch", + "magic", + "try_", + "try-", + "try.", + "rack", + "directive", + "challenge", + "slim", + "counter", + "element", + "chosen", + "doc_", + "doc-", + "doc.", + "meta", + "should", + "button", + "packet", + "stream", + "hardware", + "android", + "infinite", + "password", + "software", + "ghost", + "xamarin", + "spec", + "chef", + "interview", + "hubot", + "mvc_", + "mvc-", + "mvc.", + "exercise", + "leaflet", + "launcher", + "air_", + "air-", + "air.", + "photo", + "board", + "boxen", + "way_", + "way-", + "way.", + "computing", + "welcome", + "notepad", + "portfolio", + "cat_", + "cat-", + "cat.", + "can_", + "can-", + "can.", + "magento", + "yaml", + "domain", + "card", + "yii_", + "yii-", + "yii.", + "checker", + "browser", + "upgrade", + "only", + "progres", + "aura", + "ruby_", + "ruby-", + "ruby.", + "polymer", + "util", + "lite", + "hackathon", + "rule", + "log_", + "log-", + "log.", + "opengl", + "stanford", + "skeleton", + "history", + "inspector", + "help", + "soon", + "selenium", + "lab_", + "lab-", + "lab.", + "scheme", + "schema", + "look", + "ready", + "leveldb", + "docker", + "game", + "minimal", + "logstash", + "messaging", + "within", + "heroku", + "mongodb", + "kata", + "suite", + "picker", + "win_", + "win-", + "win.", + "wip_", + "wip-", + "wip.", + "panel", + "started", + "starter", + "front-end", + "detector", + "deploy", + "editing", + "based", + "admin", + "capture", + "spree", + "page", + "bundle", + "goal", + "rpg_", + "rpg-", + "rpg.", + "setup", + "side", + "mean", + "reader", + "cookbook", + "mini", + "modern", + "seed", + "dom_", + "dom-", + "dom.", + "doc_", + "doc-", + "doc.", + "dot_", + "dot-", + "dot.", + "syntax", + "sugar", + "loader", + "website", + "make", + "kit_", + "kit-", + "kit.", + "protocol", + "human", + "daemon", + "golang", + "manager", + "countdown", + "connector", + "swagger", + "map_", + "map-", + "map.", + "mac_", + "mac-", + "mac.", + "man_", + "man-", + "man.", + "orm_", + "orm-", + "orm.", + "org_", + "org-", + "org.", + "little", + "zsh_", + "zsh-", + "zsh.", + "shop", + "show", + "workshop", + "money", + "grid", + "server", + "octopres", + "svn_", + "svn-", + "svn.", + "ember", + "embed", + "general", + "file", + "important", + "dropbox", + "portable", + "public", + "docpad", + "fish", + "sbt_", + "sbt-", + "sbt.", + "done", + "para", + "network", + "common", + "readme", + "popup", + "simple", + "purpose", + "mirror", + "single", + "cordova", + "exchange", + "object", + "design", + "gateway", + "account", + "lamp", + "intellij", + "math", + "mit_", + "mit-", + "mit.", + "control", + "enhanced", + "emitter", + "multi", + "add_", + "add-", + "add.", + "about", + "socket", + "preview", + "vagrant", + "cli_", + "cli-", + "cli.", + "powerful", + "top_", + "top-", + "top.", + "radio", + "watch", + "fluid", + "amazon", + "report", + "couchbase", + "automatic", + "detection", + "sprite", + "pyramid", + "portal", + "advanced", + "plu_", + "plu-", + "plu.", + "runtime", + "git_", + "git-", + "git.", + "uri_", + "uri-", + "uri.", + "haml", + "node", + "sql_", + "sql-", + "sql.", + "cool", + "core", + "obsolete", + "handler", + "iphone", + "extractor", + "array", + "copy", + "nlp_", + "nlp-", + "nlp.", + "reveal", + "pop_", + "pop-", + "pop.", + "engine", + "parse", + "check", + "html", + "nest", + "all_", + "all-", + "all.", + "chinese", + "buildpack", + "what", + "tag_", + "tag-", + "tag.", + "proxy", + "style", + "cookie", + "feed", + "restful", + "compiler", + "creating", + "prelude", + "context", + "java", + "rspec", + "mock", + "backbone", + "light", + "spotify", + "flex", + "related", + "shell", + "which", + "clas", + "webapp", + "swift", + "ansible", + "unity", + "console", + "tumblr", + "export", + "campfire", + "conway'", + "made", + "riak", + "hero", + "here", + "unix", + "unit", + "glas", + "smtp", + "how_", + "how-", + "how.", + "hot_", + "hot-", + "hot.", + "debug", + "release", + "diff", + "player", + "easy", + "right", + "old_", + "old-", + "old.", + "animate", + "time", + "push", + "explorer", + "course", + "training", + "nette", + "router", + "draft", + "structure", + "note", + "salt", + "where", + "spark", + "trello", + "power", + "method", + "social", + "via_", + "via-", + "via.", + "vim_", + "vim-", + "vim.", + "select", + "webkit", + "github", + "ftp_", + "ftp-", + "ftp.", + "creator", + "mongoose", + "led_", + "led-", + "led.", + "movie", + "currently", + "pdf_", + "pdf-", + "pdf.", + "load", + "markdown", + "phalcon", + "input", + "custom", + "atom", + "oracle", + "phonegap", + "ubuntu", + "great", + "rdf_", + "rdf-", + "rdf.", + "popcorn", + "firefox", + "zip_", + "zip-", + "zip.", + "cuda", + "dotfile", + "static", + "openwrt", + "viewer", + "powered", + "graphic", + "les_", + "les-", + "les.", + "doe_", + "doe-", + "doe.", + "maven", + "word", + "eclipse", + "lab_", + "lab-", + "lab.", + "hacking", + "steam", + "analytic", + "option", + "abstract", + "archive", + "reality", + "switcher", + "club", + "write", + "kafka", + "arduino", + "angular", + "online", + "title", + "don't", + "contao", + "notice", + "analyzer", + "learning", + "zend", + "external", + "staging", + "busines", + "tdd_", + "tdd-", + "tdd.", + "scanner", + "building", + "snippet", + "modular", + "bower", + "stm_", + "stm-", + "stm.", + "lib_", + "lib-", + "lib.", + "alpha", + "mobile", + "clean", + "linux", + "nginx", + "manifest", + "some", + "raspberry", + "gnome", + "ide_", + "ide-", + "ide.", + "block", + "statistic", + "info", + "drag", + "youtube", + "koan", + "facebook", + "paperclip", + "art_", + "art-", + "art.", + "quality", + "tab_", + "tab-", + "tab.", + "need", + "dojo", + "shield", + "computer", + "stat", + "state", + "twitter", + "utility", + "converter", + "hosting", + "devise", + "liferay", + "updated", + "force", + "tip_", + "tip-", + "tip.", + "behavior", + "active", + "call", + "answer", + "deck", + "better", + "principle", + "ches", + "bar_", + "bar-", + "bar.", + "reddit", + "three", + "haxe", + "just", + "plug-in", + "agile", + "manual", + "tetri", + "super", + "beta", + "parsing", + "doctrine", + "minecraft", + "useful", + "perl", + "sharing", + "agent", + "switch", + "view", + "dash", + "channel", + "repo", + "pebble", + "profiler", + "warning", + "cluster", + "running", + "markup", + "evented", + "mod_", + "mod-", + "mod.", + "share", + "csv_", + "csv-", + "csv.", + "response", + "good", + "house", + "connect", + "built", + "build", + "find", + "ipython", + "webgl", + "big_", + "big-", + "big.", + "google", + "scala", + "sdl_", + "sdl-", + "sdl.", + "sdk_", + "sdk-", + "sdk.", + "native", + "day_", + "day-", + "day.", + "puppet", + "text", + "routing", + "helper", + "linkedin", + "crawler", + "host", + "guard", + "merchant", + "poker", + "over", + "writing", + "free", + "classe", + "component", + "craft", + "nodej", + "phoenix", + "longer", + "quick", + "lazy", + "memory", + "clone", + "hacker", + "middleman", + "factory", + "motion", + "multiple", + "tornado", + "hack", + "ssh_", + "ssh-", + "ssh.", + "review", + "vimrc", + "driver", + "driven", + "blog", + "particle", + "table", + "intro", + "importer", + "thrift", + "xmpp", + "framework", + "refresh", + "react", + "font", + "librarie", + "variou", + "formatter", + "analysi", + "karma", + "scroll", + "tut_", + "tut-", + "tut.", + "apple", + "tag_", + "tag-", + "tag.", + "tab_", + "tab-", + "tab.", + "category", + "ionic", + "cache", + "homebrew", + "reverse", + "english", + "getting", + "shipping", + "clojure", + "boot", + "book", + "branch", + "combination", + "combo", +] +[[rules]] +description = "GitHub App Token" +id = "github-app-token" +regex = '''(ghu|ghs)_[0-9a-zA-Z]{36}''' +keywords = [ + "ghu_","ghs_", +] + +[[rules]] +description = "GitHub Fine-Grained Personal Access Token" +id = "github-fine-grained-pat" +regex = '''github_pat_[0-9a-zA-Z_]{82}''' +keywords = [ + "github_pat_", +] + +[[rules]] +description = "GitHub OAuth Access Token" +id = "github-oauth" +regex = '''gho_[0-9a-zA-Z]{36}''' +keywords = [ + "gho_", +] + +[[rules]] +description = "GitHub Personal Access Token" +id = "github-pat" +regex = '''ghp_[0-9a-zA-Z]{36}''' +keywords = [ + "ghp_", +] + +[[rules]] +description = "GitHub Refresh Token" +id = "github-refresh-token" +regex = '''ghr_[0-9a-zA-Z]{36}''' +keywords = [ + "ghr_", +] + +[[rules]] +description = "GitLab Personal Access Token" +id = "gitlab-pat" +regex = '''glpat-[0-9a-zA-Z\-\_]{20}''' +keywords = [ + "glpat-", +] + +[[rules]] +description = "GitLab Pipeline Trigger Token" +id = "gitlab-ptt" +regex = '''glptt-[0-9a-f]{40}''' +keywords = [ + "glptt-", +] + +[[rules]] +description = "GitLab Runner Registration Token" +id = "gitlab-rrt" +regex = '''GR1348941[0-9a-zA-Z\-\_]{20}''' +keywords = [ + "gr1348941", +] + +[[rules]] +description = "Gitter Access Token" +id = "gitter-access-token" +regex = '''(?i)(?:gitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "gitter", +] + +[[rules]] +description = "GoCardless API token" +id = "gocardless-api-token" +regex = '''(?i)(?:gocardless)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(live_(?i)[a-z0-9\-_=]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "live_","gocardless", +] + +[[rules]] +description = "Grafana api key (or Grafana cloud api key)" +id = "grafana-api-key" +regex = '''(?i)\b(eyJrIjoi[A-Za-z0-9]{70,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "eyjrijoi", +] + +[[rules]] +description = "Grafana cloud api token" +id = "grafana-cloud-api-token" +regex = '''(?i)\b(glc_[A-Za-z0-9+/]{32,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "glc_", +] + +[[rules]] +description = "Grafana service account token" +id = "grafana-service-account-token" +regex = '''(?i)\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "glsa_", +] + +[[rules]] +description = "HashiCorp Terraform user/org API token" +id = "hashicorp-tf-api-token" +regex = '''(?i)[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}''' +keywords = [ + "atlasv1", +] + +[[rules]] +description = "Heroku API Key" +id = "heroku-api-key" +regex = '''(?i)(?:heroku)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "heroku", +] + +[[rules]] +description = "HubSpot API Token" +id = "hubspot-api-key" +regex = '''(?i)(?:hubspot)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "hubspot", +] + +[[rules]] +description = "Intercom API Token" +id = "intercom-api-key" +regex = '''(?i)(?:intercom)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{60})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "intercom", +] + +[[rules]] +description = "JSON Web Token" +id = "jwt" +regex = '''(?i)\b(ey[0-9a-z]{30,34}\.ey[0-9a-z-\/_]{30,500}\.[0-9a-zA-Z-\/_]{10,200}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "ey", +] + +[[rules]] +description = "Kraken Access Token" +id = "kraken-access-token" +regex = '''(?i)(?:kraken)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9\/=_\+\-]{80,90})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "kraken", +] + +[[rules]] +description = "Kucoin Access Token" +id = "kucoin-access-token" +regex = '''(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "kucoin", +] + +[[rules]] +description = "Kucoin Secret Key" +id = "kucoin-secret-key" +regex = '''(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "kucoin", +] + +[[rules]] +description = "Launchdarkly Access Token" +id = "launchdarkly-access-token" +regex = '''(?i)(?:launchdarkly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "launchdarkly", +] + +[[rules]] +description = "Linear API Token" +id = "linear-api-key" +regex = '''lin_api_(?i)[a-z0-9]{40}''' +keywords = [ + "lin_api_", +] + +[[rules]] +description = "Linear Client Secret" +id = "linear-client-secret" +regex = '''(?i)(?:linear)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "linear", +] + +[[rules]] +description = "LinkedIn Client ID" +id = "linkedin-client-id" +regex = '''(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{14})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "linkedin","linked-in", +] + +[[rules]] +description = "LinkedIn Client secret" +id = "linkedin-client-secret" +regex = '''(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "linkedin","linked-in", +] + +[[rules]] +description = "Lob API Key" +id = "lob-api-key" +regex = '''(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "test_","live_", +] + +[[rules]] +description = "Lob Publishable API Key" +id = "lob-pub-api-key" +regex = '''(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "test_pub","live_pub","_pub", +] + +[[rules]] +description = "Mailchimp API key" +id = "mailchimp-api-key" +regex = '''(?i)(?:mailchimp)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32}-us20)(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mailchimp", +] + +[[rules]] +description = "Mailgun private API token" +id = "mailgun-private-api-token" +regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(key-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mailgun", +] + +[[rules]] +description = "Mailgun public validation key" +id = "mailgun-pub-key" +regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mailgun", +] + +[[rules]] +description = "Mailgun webhook signing key" +id = "mailgun-signing-key" +regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mailgun", +] + +[[rules]] +description = "MapBox API token" +id = "mapbox-api-token" +regex = '''(?i)(?:mapbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(pk\.[a-z0-9]{60}\.[a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mapbox", +] + +[[rules]] +description = "Mattermost Access Token" +id = "mattermost-access-token" +regex = '''(?i)(?:mattermost)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{26})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "mattermost", +] + +[[rules]] +description = "MessageBird API token" +id = "messagebird-api-token" +regex = '''(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "messagebird","message-bird","message_bird", +] + +[[rules]] +description = "MessageBird client ID" +id = "messagebird-client-id" +regex = '''(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "messagebird","message-bird","message_bird", +] + +[[rules]] +description = "Microsoft Teams Webhook" +id = "microsoft-teams-webhook" +regex = '''https:\/\/[a-z0-9]+\.webhook\.office\.com\/webhookb2\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}@[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}\/IncomingWebhook\/[a-z0-9]{32}\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}''' +keywords = [ + "webhook.office.com","webhookb2","incomingwebhook", +] + +[[rules]] +description = "Netlify Access Token" +id = "netlify-access-token" +regex = '''(?i)(?:netlify)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40,46})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "netlify", +] + +[[rules]] +description = "New Relic ingest browser API token" +id = "new-relic-browser-api-token" +regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "nrjs-", +] + +[[rules]] +description = "New Relic user API ID" +id = "new-relic-user-api-id" +regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "new-relic","newrelic","new_relic", +] + +[[rules]] +description = "New Relic user API Key" +id = "new-relic-user-api-key" +regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "nrak", +] + +[[rules]] +description = "npm access token" +id = "npm-access-token" +regex = '''(?i)\b(npm_[a-z0-9]{36})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "npm_", +] + +[[rules]] +description = "Nytimes Access Token" +id = "nytimes-access-token" +regex = '''(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "nytimes","new-york-times","newyorktimes", +] + +[[rules]] +description = "Okta Access Token" +id = "okta-access-token" +regex = '''(?i)(?:okta)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{42})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "okta", +] + +[[rules]] +description = "Plaid API Token" +id = "plaid-api-token" +regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "plaid", +] + +[[rules]] +description = "Plaid Client ID" +id = "plaid-client-id" +regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "plaid", +] + +[[rules]] +description = "Plaid Secret key" +id = "plaid-secret-key" +regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "plaid", +] + +[[rules]] +description = "PlanetScale API token" +id = "planetscale-api-token" +regex = '''(?i)\b(pscale_tkn_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pscale_tkn_", +] + +[[rules]] +description = "PlanetScale OAuth token" +id = "planetscale-oauth-token" +regex = '''(?i)\b(pscale_oauth_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pscale_oauth_", +] + +[[rules]] +description = "PlanetScale password" +id = "planetscale-password" +regex = '''(?i)\b(pscale_pw_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pscale_pw_", +] + +[[rules]] +description = "Postman API token" +id = "postman-api-token" +regex = '''(?i)\b(PMAK-(?i)[a-f0-9]{24}\-[a-f0-9]{34})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pmak-", +] + +[[rules]] +description = "Prefect API token" +id = "prefect-api-token" +regex = '''(?i)\b(pnu_[a-z0-9]{36})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pnu_", +] + +[[rules]] +description = "Private Key" +id = "private-key" +regex = '''(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY( BLOCK)?-----[\s\S-]*KEY( BLOCK)?----''' +keywords = [ + "-----begin", +] + +[[rules]] +description = "Pulumi API token" +id = "pulumi-api-token" +regex = '''(?i)\b(pul-[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "pul-", +] + +[[rules]] +description = "PyPI upload token" +id = "pypi-upload-token" +regex = '''pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}''' +keywords = [ + "pypi-ageichlwas5vcmc", +] + +[[rules]] +description = "RapidAPI Access Token" +id = "rapidapi-access-token" +regex = '''(?i)(?:rapidapi)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "rapidapi", +] + +[[rules]] +description = "Readme API token" +id = "readme-api-token" +regex = '''(?i)\b(rdme_[a-z0-9]{70})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "rdme_", +] + +[[rules]] +description = "Rubygem API token" +id = "rubygems-api-token" +regex = '''(?i)\b(rubygems_[a-f0-9]{48})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "rubygems_", +] + +[[rules]] +description = "Sendbird Access ID" +id = "sendbird-access-id" +regex = '''(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sendbird", +] + +[[rules]] +description = "Sendbird Access Token" +id = "sendbird-access-token" +regex = '''(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sendbird", +] + +[[rules]] +description = "SendGrid API token" +id = "sendgrid-api-token" +regex = '''(?i)\b(SG\.(?i)[a-z0-9=_\-\.]{66})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sg.", +] + +[[rules]] +description = "Sendinblue API token" +id = "sendinblue-api-token" +regex = '''(?i)\b(xkeysib-[a-f0-9]{64}\-(?i)[a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "xkeysib-", +] + +[[rules]] +description = "Sentry Access Token" +id = "sentry-access-token" +regex = '''(?i)(?:sentry)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sentry", +] + +[[rules]] +description = "Shippo API token" +id = "shippo-api-token" +regex = '''(?i)\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "shippo_", +] + +[[rules]] +description = "Shopify access token" +id = "shopify-access-token" +regex = '''shpat_[a-fA-F0-9]{32}''' +keywords = [ + "shpat_", +] + +[[rules]] +description = "Shopify custom access token" +id = "shopify-custom-access-token" +regex = '''shpca_[a-fA-F0-9]{32}''' +keywords = [ + "shpca_", +] + +[[rules]] +description = "Shopify private app access token" +id = "shopify-private-app-access-token" +regex = '''shppa_[a-fA-F0-9]{32}''' +keywords = [ + "shppa_", +] + +[[rules]] +description = "Shopify shared secret" +id = "shopify-shared-secret" +regex = '''shpss_[a-fA-F0-9]{32}''' +keywords = [ + "shpss_", +] + +[[rules]] +description = "Sidekiq Secret" +id = "sidekiq-secret" +regex = '''(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "bundle_enterprise__contribsys__com","bundle_gems__contribsys__com", +] + +[[rules]] +description = "Sidekiq Sensitive URL" +id = "sidekiq-sensitive-url" +regex = '''(?i)\b(http(?:s??):\/\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\/|\#|\?|:]|$)''' +secretGroup = 2 +keywords = [ + "gems.contribsys.com","enterprise.contribsys.com", +] + +[[rules]] +description = "Slack token" +id = "slack-access-token" +regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})''' +keywords = [ + "xoxb","xoxa","xoxp","xoxr","xoxs", +] + +[[rules]] +description = "Slack Webhook" +id = "slack-web-hook" +regex = '''https:\/\/hooks.slack.com\/(services|workflows)\/[A-Za-z0-9+\/]{44,46}''' +keywords = [ + "hooks.slack.com", +] + +[[rules]] +description = "Square Access Token" +id = "square-access-token" +regex = '''(?i)\b(sq0atp-[0-9A-Za-z\-_]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "sq0atp-", +] + +[[rules]] +description = "Squarespace Access Token" +id = "squarespace-access-token" +regex = '''(?i)(?:squarespace)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "squarespace", +] + +[[rules]] +description = "Stripe Access Token" +id = "stripe-access-token" +regex = '''(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}''' +keywords = [ + "sk_test","pk_test","sk_live","pk_live", +] + +[[rules]] +description = "SumoLogic Access ID" +id = "sumologic-access-id" +regex = '''(?i)(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{14})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sumo", +] + +[[rules]] +description = "SumoLogic Access Token" +id = "sumologic-access-token" +regex = '''(?i)(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "sumo", +] + +[[rules]] +description = "Telegram Bot API Token" +id = "telegram-bot-api-token" +regex = '''(?i)(?:^|[^0-9])([0-9]{5,16}:A[a-zA-Z0-9_\-]{34})(?:$|[^a-zA-Z0-9_\-])''' +secretGroup = 1 +keywords = [ + "telegram","api","bot","token","url", +] + +[[rules]] +description = "Travis CI Access Token" +id = "travisci-access-token" +regex = '''(?i)(?:travis)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "travis", +] + +[[rules]] +description = "Twilio API Key" +id = "twilio-api-key" +regex = '''SK[0-9a-fA-F]{32}''' +keywords = [ + "twilio", +] + +[[rules]] +description = "Twitch API token" +id = "twitch-api-token" +regex = '''(?i)(?:twitch)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitch", +] + +[[rules]] +description = "Twitter Access Secret" +id = "twitter-access-secret" +regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{45})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitter", +] + +[[rules]] +description = "Twitter Access Token" +id = "twitter-access-token" +regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitter", +] + +[[rules]] +description = "Twitter API Key" +id = "twitter-api-key" +regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitter", +] + +[[rules]] +description = "Twitter API Secret" +id = "twitter-api-secret" +regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitter", +] + +[[rules]] +description = "Twitter Bearer Token" +id = "twitter-bearer-token" +regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "twitter", +] + +[[rules]] +description = "Typeform API token" +id = "typeform-api-token" +regex = '''(?i)(?:typeform)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(tfp_[a-z0-9\-_\.=]{59})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "tfp_", +] + +[[rules]] +description = "Vault Batch Token" +id = "vault-batch-token" +regex = '''(?i)\b(hvb\.[a-z0-9_-]{138,212})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "hvb", +] + +[[rules]] +description = "Vault Service Token" +id = "vault-service-token" +regex = '''(?i)\b(hvs\.[a-z0-9_-]{90,100})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +keywords = [ + "hvs", +] + +[[rules]] +description = "Yandex Access Token" +id = "yandex-access-token" +regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(t1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "yandex", +] + +[[rules]] +description = "Yandex API Key" +id = "yandex-api-key" +regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(AQVN[A-Za-z0-9_\-]{35,38})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "yandex", +] + +[[rules]] +description = "Yandex AWS Access Token" +id = "yandex-aws-access-token" +regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}(YC[a-zA-Z0-9_\-]{38})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "yandex", +] + +[[rules]] +description = "Zendesk Secret Key" +id = "zendesk-secret-key" +regex = '''(?i)(?:zendesk)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +secretGroup = 1 +keywords = [ + "zendesk", +] + + diff --git a/cli/config/rule.go b/cli/config/rule.go new file mode 100644 index 0000000000..b7c8c15185 --- /dev/null +++ b/cli/config/rule.go @@ -0,0 +1,43 @@ +package config + +import ( + "regexp" +) + +// Rules contain information that define details on how to detect secrets +type Rule struct { + // Description is the description of the rule. + Description string + + // RuleID is a unique identifier for this rule + RuleID string + + // Entropy is a float representing the minimum shannon + // entropy a regex group must have to be considered a secret. + Entropy float64 + + // SecretGroup is an int used to extract secret from regex + // match and used as the group that will have its entropy + // checked if `entropy` is set. + SecretGroup int + + // Regex is a golang regular expression used to detect secrets. + Regex *regexp.Regexp + + // Path is a golang regular expression used to + // filter secrets by path + Path *regexp.Regexp + + // Tags is an array of strings used for metadata + // and reporting purposes. + Tags []string + + // Keywords are used for pre-regex check filtering. Rules that contain + // keywords will perform a quick string compare check to make sure the + // keyword(s) are in the content being scanned. + Keywords []string + + // Allowlist allows a rule to be ignored for specific + // regexes, paths, and/or commits + Allowlist Allowlist +} diff --git a/cli/config/utils.go b/cli/config/utils.go new file mode 100644 index 0000000000..ada6ff0fe2 --- /dev/null +++ b/cli/config/utils.go @@ -0,0 +1,24 @@ +package config + +import ( + "regexp" +) + +func anyRegexMatch(f string, res []*regexp.Regexp) bool { + for _, re := range res { + if regexMatched(f, re) { + return true + } + } + return false +} + +func regexMatched(f string, re *regexp.Regexp) bool { + if re == nil { + return false + } + if re.FindString(f) != "" { + return true + } + return false +} diff --git a/cli/detect/baseline.go b/cli/detect/baseline.go new file mode 100644 index 0000000000..bd4c256657 --- /dev/null +++ b/cli/detect/baseline.go @@ -0,0 +1,87 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/rs/zerolog/log" + + "github.com/Infisical/infisical-merge/report" +) + +func IsNew(finding report.Finding, baseline []report.Finding) bool { + // Explicitly testing each property as it gives significantly better performance in comparison to cmp.Equal(). Drawback is that + // the code requires maintanance if/when the Finding struct changes + for _, b := range baseline { + + if finding.Author == b.Author && + finding.Commit == b.Commit && + finding.Date == b.Date && + finding.Description == b.Description && + finding.Email == b.Email && + finding.EndColumn == b.EndColumn && + finding.EndLine == b.EndLine && + finding.Entropy == b.Entropy && + finding.File == b.File && + // Omit checking finding.Fingerprint - if the format of the fingerprint changes, the users will see unexpected behaviour + finding.Match == b.Match && + finding.Message == b.Message && + finding.RuleID == b.RuleID && + finding.Secret == b.Secret && + finding.StartColumn == b.StartColumn && + finding.StartLine == b.StartLine { + return false + } + } + return true +} + +func LoadBaseline(baselinePath string) ([]report.Finding, error) { + var previousFindings []report.Finding + jsonFile, err := os.Open(baselinePath) + if err != nil { + return nil, fmt.Errorf("could not open %s", baselinePath) + } + + defer func() { + if cerr := jsonFile.Close(); cerr != nil { + log.Warn().Err(cerr).Msg("problem closing jsonFile handle") + } + }() + + bytes, err := io.ReadAll(jsonFile) + if err != nil { + return nil, fmt.Errorf("could not read data from the file %s", baselinePath) + } + + err = json.Unmarshal(bytes, &previousFindings) + if err != nil { + return nil, fmt.Errorf("the format of the file %s is not supported", baselinePath) + } + + return previousFindings, nil +} diff --git a/cli/detect/baseline_test.go b/cli/detect/baseline_test.go new file mode 100644 index 0000000000..91d2eb72ee --- /dev/null +++ b/cli/detect/baseline_test.go @@ -0,0 +1,160 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/Infisical/infisical-merge/report" +) + +func TestIsNew(t *testing.T) { + tests := []struct { + findings report.Finding + baseline []report.Finding + expect bool + }{ + { + findings: report.Finding{ + Author: "a", + Commit: "0000", + }, + baseline: []report.Finding{ + { + Author: "a", + Commit: "0000", + }, + }, + expect: false, + }, + { + findings: report.Finding{ + Author: "a", + Commit: "0000", + }, + baseline: []report.Finding{ + { + Author: "a", + Commit: "0002", + }, + }, + expect: true, + }, + { + findings: report.Finding{ + Author: "a", + Commit: "0000", + Tags: []string{"a", "b"}, + }, + baseline: []report.Finding{ + { + Author: "a", + Commit: "0000", + Tags: []string{"a", "c"}, + }, + }, + expect: false, // Updated tags doesn't make it a new finding + }, + } + for _, test := range tests { + assert.Equal(t, test.expect, IsNew(test.findings, test.baseline)) + } +} + +func TestFileLoadBaseline(t *testing.T) { + tests := []struct { + Filename string + ExpectedError error + }{ + { + Filename: "../testdata/baseline/baseline.csv", + ExpectedError: errors.New("the format of the file ../testdata/baseline/baseline.csv is not supported"), + }, + { + Filename: "../testdata/baseline/baseline.sarif", + ExpectedError: errors.New("the format of the file ../testdata/baseline/baseline.sarif is not supported"), + }, + { + Filename: "../testdata/baseline/notfound.json", + ExpectedError: errors.New("could not open ../testdata/baseline/notfound.json"), + }, + } + + for _, test := range tests { + _, err := LoadBaseline(test.Filename) + assert.Equal(t, test.ExpectedError.Error(), err.Error()) + } +} + +func TestIgnoreIssuesInBaseline(t *testing.T) { + tests := []struct { + findings []report.Finding + baseline []report.Finding + expectCount int + }{ + { + findings: []report.Finding{ + { + Author: "a", + Commit: "5", + }, + }, + baseline: []report.Finding{ + { + Author: "a", + Commit: "5", + }, + }, + expectCount: 0, + }, + { + findings: []report.Finding{ + { + Author: "a", + Commit: "5", + Fingerprint: "a", + }, + }, + baseline: []report.Finding{ + { + Author: "a", + Commit: "5", + Fingerprint: "b", + }, + }, + expectCount: 0, + }, + } + + for _, test := range tests { + d, _ := NewDetectorDefaultConfig() + d.baseline = test.baseline + for _, finding := range test.findings { + d.addFinding(finding) + } + assert.Equal(t, test.expectCount, len(d.findings)) + } +} diff --git a/cli/detect/detect.go b/cli/detect/detect.go new file mode 100644 index 0000000000..0b409af48a --- /dev/null +++ b/cli/detect/detect.go @@ -0,0 +1,652 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + "bufio" + "context" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + + "github.com/h2non/filetype" + + "github.com/Infisical/infisical-merge/config" + "github.com/Infisical/infisical-merge/detect/git" + "github.com/Infisical/infisical-merge/report" + + "github.com/fatih/semgroup" + "github.com/gitleaks/go-gitdiff/gitdiff" + ahocorasick "github.com/petar-dambovaliev/aho-corasick" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +// Type used to differentiate between git scan types: +// $ gitleaks detect +// $ gitleaks protect +// $ gitleaks protect staged +type GitScanType int + +const ( + DetectType GitScanType = iota + ProtectType + ProtectStagedType + + gitleaksAllowSignature = "gitleaks:allow" +) + +// Detector is the main detector struct +type Detector struct { + // Config is the configuration for the detector + Config config.Config + + // Redact is a flag to redact findings. This is exported + // so users using gitleaks as a library can set this flag + // without calling `detector.Start(cmd *cobra.Command)` + Redact bool + + // verbose is a flag to print findings + Verbose bool + + // files larger than this will be skipped + MaxTargetMegaBytes int + + // followSymlinks is a flag to enable scanning symlink files + FollowSymlinks bool + + // NoColor is a flag to disable color output + NoColor bool + + // commitMap is used to keep track of commits that have been scanned. + // This is only used for logging purposes and git scans. + commitMap map[string]bool + + // findingMutex is to prevent concurrent access to the + // findings slice when adding findings. + findingMutex *sync.Mutex + + // findings is a slice of report.Findings. This is the result + // of the detector's scan which can then be used to generate a + // report. + findings []report.Finding + + // prefilter is a ahocorasick struct used for doing efficient string + // matching given a set of words (keywords from the rules in the config) + prefilter ahocorasick.AhoCorasick + + // a list of known findings that should be ignored + baseline []report.Finding + + // path to baseline + baselinePath string + + // gitleaksIgnore + gitleaksIgnore map[string]bool +} + +// Fragment contains the data to be scanned +type Fragment struct { + // Raw is the raw content of the fragment + Raw string + + // FilePath is the path to the file if applicable + FilePath string + SymlinkFile string + + // CommitSHA is the SHA of the commit if applicable + CommitSHA string + + // newlineIndices is a list of indices of newlines in the raw content. + // This is used to calculate the line location of a finding + newlineIndices [][]int + + // keywords is a map of all the keywords contain within the contents + // of this fragment + keywords map[string]bool +} + +// NewDetector creates a new detector with the given config +func NewDetector(cfg config.Config) *Detector { + builder := ahocorasick.NewAhoCorasickBuilder(ahocorasick.Opts{ + AsciiCaseInsensitive: true, + MatchOnlyWholeWords: false, + MatchKind: ahocorasick.LeftMostLongestMatch, + DFA: true, + }) + + return &Detector{ + commitMap: make(map[string]bool), + gitleaksIgnore: make(map[string]bool), + findingMutex: &sync.Mutex{}, + findings: make([]report.Finding, 0), + Config: cfg, + prefilter: builder.Build(cfg.Keywords), + } +} + +// NewDetectorDefaultConfig creates a new detector with the default config +func NewDetectorDefaultConfig() (*Detector, error) { + viper.SetConfigType("toml") + err := viper.ReadConfig(strings.NewReader(config.DefaultConfig)) + if err != nil { + return nil, err + } + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + return nil, err + } + cfg, err := vc.Translate() + if err != nil { + return nil, err + } + return NewDetector(cfg), nil +} + +func (d *Detector) AddGitleaksIgnore(gitleaksIgnorePath string) error { + log.Debug().Msg("found .gitleaksignore file") + file, err := os.Open(gitleaksIgnorePath) + + if err != nil { + return err + } + + // https://github.com/securego/gosec/issues/512 + defer func() { + if err := file.Close(); err != nil { + log.Warn().Msgf("Error closing .gitleaksignore file: %s\n", err) + } + }() + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + d.gitleaksIgnore[scanner.Text()] = true + } + return nil +} + +func (d *Detector) AddBaseline(baselinePath string, source string) error { + if baselinePath != "" { + absoluteSource, err := filepath.Abs(source) + if err != nil { + return err + } + + absoluteBaseline, err := filepath.Abs(baselinePath) + if err != nil { + return err + } + + relativeBaseline, err := filepath.Rel(absoluteSource, absoluteBaseline) + if err != nil { + return err + } + + baseline, err := LoadBaseline(baselinePath) + if err != nil { + return err + } + + d.baseline = baseline + baselinePath = relativeBaseline + + } + + d.baselinePath = baselinePath + return nil +} + +// DetectBytes scans the given bytes and returns a list of findings +func (d *Detector) DetectBytes(content []byte) []report.Finding { + return d.DetectString(string(content)) +} + +// DetectString scans the given string and returns a list of findings +func (d *Detector) DetectString(content string) []report.Finding { + return d.Detect(Fragment{ + Raw: content, + }) +} + +// detectRule scans the given fragment for the given rule and returns a list of findings +func (d *Detector) detectRule(fragment Fragment, rule config.Rule) []report.Finding { + var findings []report.Finding + + // check if filepath or commit is allowed for this rule + if rule.Allowlist.CommitAllowed(fragment.CommitSHA) || + rule.Allowlist.PathAllowed(fragment.FilePath) { + return findings + } + + if rule.Path != nil && rule.Regex == nil { + // Path _only_ rule + if rule.Path.Match([]byte(fragment.FilePath)) { + finding := report.Finding{ + Description: rule.Description, + File: fragment.FilePath, + SymlinkFile: fragment.SymlinkFile, + RuleID: rule.RuleID, + Match: fmt.Sprintf("file detected: %s", fragment.FilePath), + Tags: rule.Tags, + } + return append(findings, finding) + } + } else if rule.Path != nil { + // if path is set _and_ a regex is set, then we need to check both + // so if the path does not match, then we should return early and not + // consider the regex + if !rule.Path.Match([]byte(fragment.FilePath)) { + return findings + } + } + + // if path only rule, skip content checks + if rule.Regex == nil { + return findings + } + + // If flag configure and raw data size bigger then the flag + if d.MaxTargetMegaBytes > 0 { + rawLength := len(fragment.Raw) / 1000000 + if rawLength > d.MaxTargetMegaBytes { + log.Debug().Msgf("skipping file: %s scan due to size: %d", fragment.FilePath, rawLength) + return findings + } + } + + matchIndices := rule.Regex.FindAllStringIndex(fragment.Raw, -1) + for _, matchIndex := range matchIndices { + // extract secret from match + secret := strings.Trim(fragment.Raw[matchIndex[0]:matchIndex[1]], "\n") + + // determine location of match. Note that the location + // in the finding will be the line/column numbers of the _match_ + // not the _secret_, which will be different if the secretGroup + // value is set for this rule + loc := location(fragment, matchIndex) + + if matchIndex[1] > loc.endLineIndex { + loc.endLineIndex = matchIndex[1] + } + + finding := report.Finding{ + Description: rule.Description, + File: fragment.FilePath, + SymlinkFile: fragment.SymlinkFile, + RuleID: rule.RuleID, + StartLine: loc.startLine, + EndLine: loc.endLine, + StartColumn: loc.startColumn, + EndColumn: loc.endColumn, + Secret: secret, + Match: secret, + Tags: rule.Tags, + Line: fragment.Raw[loc.startLineIndex:loc.endLineIndex], + } + + if strings.Contains(fragment.Raw[loc.startLineIndex:loc.endLineIndex], + gitleaksAllowSignature) { + continue + } + + // extract secret from secret group if set + if rule.SecretGroup != 0 { + groups := rule.Regex.FindStringSubmatch(secret) + if len(groups) <= rule.SecretGroup || len(groups) == 0 { + // Config validation should prevent this + continue + } + secret = groups[rule.SecretGroup] + finding.Secret = secret + } + + // check if the regexTarget is defined in the allowlist "regexes" entry + allowlistTarget := finding.Secret + switch rule.Allowlist.RegexTarget { + case "match": + allowlistTarget = finding.Match + case "line": + allowlistTarget = finding.Line + } + + globalAllowlistTarget := finding.Secret + switch d.Config.Allowlist.RegexTarget { + case "match": + globalAllowlistTarget = finding.Match + case "line": + globalAllowlistTarget = finding.Line + } + if rule.Allowlist.RegexAllowed(allowlistTarget) || + d.Config.Allowlist.RegexAllowed(globalAllowlistTarget) { + continue + } + + // check if the secret is in the list of stopwords + if rule.Allowlist.ContainsStopWord(finding.Secret) || + d.Config.Allowlist.ContainsStopWord(finding.Secret) { + continue + } + + // check entropy + entropy := shannonEntropy(finding.Secret) + finding.Entropy = float32(entropy) + if rule.Entropy != 0.0 { + if entropy <= rule.Entropy { + // entropy is too low, skip this finding + continue + } + // NOTE: this is a goofy hack to get around the fact there golang's regex engine + // does not support positive lookaheads. Ideally we would want to add a + // restriction on generic rules regex that requires the secret match group + // contains both numbers and alphabetical characters, not just alphabetical characters. + // What this bit of code does is check if the ruleid is prepended with "generic" and enforces the + // secret contains both digits and alphabetical characters. + // TODO: this should be replaced with stop words + if strings.HasPrefix(rule.RuleID, "generic") { + if !containsDigit(secret) { + continue + } + } + } + + findings = append(findings, finding) + } + return findings +} + +// GitScan accepts a *gitdiff.File channel which contents a git history generated from +// the output of `git log -p ...`. startGitScan will look at each file (patch) in the history +// and determine if the patch contains any findings. +func (d *Detector) DetectGit(source string, logOpts string, gitScanType GitScanType) ([]report.Finding, error) { + var ( + gitdiffFiles <-chan *gitdiff.File + err error + ) + switch gitScanType { + case DetectType: + gitdiffFiles, err = git.GitLog(source, logOpts) + if err != nil { + return d.findings, err + } + case ProtectType: + gitdiffFiles, err = git.GitDiff(source, false) + if err != nil { + return d.findings, err + } + case ProtectStagedType: + gitdiffFiles, err = git.GitDiff(source, true) + if err != nil { + return d.findings, err + } + } + + s := semgroup.NewGroup(context.Background(), 4) + + for gitdiffFile := range gitdiffFiles { + gitdiffFile := gitdiffFile + + // skip binary files + if gitdiffFile.IsBinary || gitdiffFile.IsDelete { + continue + } + + // Check if commit is allowed + commitSHA := "" + if gitdiffFile.PatchHeader != nil { + commitSHA = gitdiffFile.PatchHeader.SHA + if d.Config.Allowlist.CommitAllowed(gitdiffFile.PatchHeader.SHA) { + continue + } + } + d.addCommit(commitSHA) + + s.Go(func() error { + for _, textFragment := range gitdiffFile.TextFragments { + if textFragment == nil { + return nil + } + + fragment := Fragment{ + Raw: textFragment.Raw(gitdiff.OpAdd), + CommitSHA: commitSHA, + FilePath: gitdiffFile.NewName, + } + + for _, finding := range d.Detect(fragment) { + d.addFinding(augmentGitFinding(finding, textFragment, gitdiffFile)) + } + } + return nil + }) + } + + if err := s.Wait(); err != nil { + return d.findings, err + } + log.Info().Msgf("%d commits scanned.", len(d.commitMap)) + log.Debug().Msg("Note: this number might be smaller than expected due to commits with no additions") + if git.ErrEncountered { + return d.findings, fmt.Errorf("%s", "git error encountered, see logs") + } + return d.findings, nil +} + +type scanTarget struct { + Path string + Symlink string +} + +// DetectFiles accepts a path to a source directory or file and begins a scan of the +// file or directory. +func (d *Detector) DetectFiles(source string) ([]report.Finding, error) { + s := semgroup.NewGroup(context.Background(), 4) + paths := make(chan scanTarget) + s.Go(func() error { + defer close(paths) + return filepath.Walk(source, + func(path string, fInfo os.FileInfo, err error) error { + if err != nil { + return err + } + if fInfo.Name() == ".git" && fInfo.IsDir() { + return filepath.SkipDir + } + if fInfo.Size() == 0 { + return nil + } + if fInfo.Mode().IsRegular() { + paths <- scanTarget{ + Path: path, + Symlink: "", + } + } + if fInfo.Mode().Type() == fs.ModeSymlink && d.FollowSymlinks { + realPath, err := filepath.EvalSymlinks(path) + if err != nil { + return err + } + realPathFileInfo, _ := os.Stat(realPath) + if realPathFileInfo.IsDir() { + log.Debug().Msgf("found symlinked directory: %s -> %s [skipping]", path, realPath) + return nil + } + paths <- scanTarget{ + Path: realPath, + Symlink: path, + } + } + return nil + }) + }) + for pa := range paths { + p := pa + s.Go(func() error { + b, err := os.ReadFile(p.Path) + if err != nil { + return err + } + + mimetype, err := filetype.Match(b) + if err != nil { + return err + } + if mimetype.MIME.Type == "application" { + return nil // skip binary files + } + + fragment := Fragment{ + Raw: string(b), + FilePath: p.Path, + } + if p.Symlink != "" { + fragment.SymlinkFile = p.Symlink + } + for _, finding := range d.Detect(fragment) { + // need to add 1 since line counting starts at 1 + finding.EndLine++ + finding.StartLine++ + d.addFinding(finding) + } + + return nil + }) + } + + if err := s.Wait(); err != nil { + return d.findings, err + } + + return d.findings, nil +} + +// DetectReader accepts an io.Reader and a buffer size for the reader in KB +func (d *Detector) DetectReader(r io.Reader, bufSize int) ([]report.Finding, error) { + reader := bufio.NewReader(r) + buf := make([]byte, 0, 1000*bufSize) + findings := []report.Finding{} + + for { + n, err := reader.Read(buf[:cap(buf)]) + buf = buf[:n] + if err != nil { + if err != io.EOF { + return findings, err + } + break + } + + fragment := Fragment{ + Raw: string(buf), + } + for _, finding := range d.Detect(fragment) { + findings = append(findings, finding) + if d.Verbose { + printFinding(finding, d.NoColor) + } + } + } + + return findings, nil +} + +// Detect scans the given fragment and returns a list of findings +func (d *Detector) Detect(fragment Fragment) []report.Finding { + var findings []report.Finding + + // initiate fragment keywords + fragment.keywords = make(map[string]bool) + + // check if filepath is allowed + if fragment.FilePath != "" && (d.Config.Allowlist.PathAllowed(fragment.FilePath) || + fragment.FilePath == d.Config.Path || (d.baselinePath != "" && fragment.FilePath == d.baselinePath)) { + return findings + } + + // add newline indices for location calculation in detectRule + fragment.newlineIndices = regexp.MustCompile("\n").FindAllStringIndex(fragment.Raw, -1) + + // build keyword map for prefiltering rules + normalizedRaw := strings.ToLower(fragment.Raw) + matches := d.prefilter.FindAll(normalizedRaw) + for _, m := range matches { + fragment.keywords[normalizedRaw[m.Start():m.End()]] = true + } + + for _, rule := range d.Config.Rules { + if len(rule.Keywords) == 0 { + // if not keywords are associated with the rule always scan the + // fragment using the rule + findings = append(findings, d.detectRule(fragment, rule)...) + continue + } + fragmentContainsKeyword := false + // check if keywords are in the fragment + for _, k := range rule.Keywords { + if _, ok := fragment.keywords[strings.ToLower(k)]; ok { + fragmentContainsKeyword = true + } + } + if fragmentContainsKeyword { + findings = append(findings, d.detectRule(fragment, rule)...) + } + } + return filter(findings, d.Redact) +} + +// addFinding synchronously adds a finding to the findings slice +func (d *Detector) addFinding(finding report.Finding) { + if finding.Commit == "" { + finding.Fingerprint = fmt.Sprintf("%s:%s:%d", finding.File, finding.RuleID, finding.StartLine) + } else { + finding.Fingerprint = fmt.Sprintf("%s:%s:%s:%d", finding.Commit, finding.File, finding.RuleID, finding.StartLine) + } + // check if we should ignore this finding + if _, ok := d.gitleaksIgnore[finding.Fingerprint]; ok { + log.Debug().Msgf("ignoring finding with Fingerprint %s", + finding.Fingerprint) + return + } + + if d.baseline != nil && !IsNew(finding, d.baseline) { + log.Debug().Msgf("baseline duplicate -- ignoring finding with Fingerprint %s", finding.Fingerprint) + return + } + + d.findingMutex.Lock() + d.findings = append(d.findings, finding) + if d.Verbose { + printFinding(finding, d.NoColor) + } + d.findingMutex.Unlock() +} + +// addCommit synchronously adds a commit to the commit slice +func (d *Detector) addCommit(commit string) { + d.commitMap[commit] = true +} diff --git a/cli/detect/detect_test.go b/cli/detect/detect_test.go new file mode 100644 index 0000000000..945538adef --- /dev/null +++ b/cli/detect/detect_test.go @@ -0,0 +1,754 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + + "github.com/Infisical/infisical-merge/config" + "github.com/Infisical/infisical-merge/report" +) + +const configPath = "../testdata/config/" +const repoBasePath = "../testdata/repos/" + +func TestDetect(t *testing.T) { + tests := []struct { + cfgName string + baselinePath string + fragment Fragment + // NOTE: for expected findings, all line numbers will be 0 + // because line deltas are added _after_ the finding is created. + // I.e, if the finding is from a --no-git file, the line number will be + // increase by 1 in DetectFromFiles(). If the finding is from git, + // the line number will be increased by the patch delta. + expectedFindings []report.Finding + wantError error + }{ + { + cfgName: "simple", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // gitleaks:allow"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `awsToken := \ + + \"AKIALALEMEL33243OKIA\ // gitleaks:allow" + + `, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OKIA\" + + // gitleaks:allow" + + `, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + Secret: "AKIALALEMEL33243OKIA", + Match: "AKIALALEMEL33243OKIA", + File: "tmp.go", + Line: `awsToken := \"AKIALALEMEL33243OKIA\"`, + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + StartLine: 0, + EndLine: 0, + StartColumn: 15, + EndColumn: 34, + Entropy: 3.1464393, + }, + }, + }, + { + cfgName: "escaped_character_group", + fragment: Fragment{ + Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{ + { + Description: "PyPI upload token", + Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB", + Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB", + Line: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`, + File: "tmp.go", + RuleID: "pypi-upload-token", + Tags: []string{"key", "pypi"}, + StartLine: 0, + EndLine: 0, + StartColumn: 1, + EndColumn: 86, + Entropy: 1.9606875, + }, + }, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + Secret: "AKIALALEMEL33243OLIA", + Match: "AKIALALEMEL33243OLIA", + Line: `awsToken := \"AKIALALEMEL33243OLIA\"`, + File: "tmp.go", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + StartLine: 0, + EndLine: 0, + StartColumn: 15, + EndColumn: 34, + Entropy: 3.0841837, + }, + }, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`, + FilePath: "tmp.sh", + }, + expectedFindings: []report.Finding{ + { + Description: "Sidekiq Secret", + Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;", + Secret: "cafebabe:deadbeef", + Line: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`, + File: "tmp.sh", + RuleID: "sidekiq-secret", + Tags: []string{}, + Entropy: 2.6098502, + StartLine: 0, + EndLine: 0, + StartColumn: 8, + EndColumn: 60, + }, + }, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`, + FilePath: "tmp.sh", + }, + expectedFindings: []report.Finding{ + { + Description: "Sidekiq Secret", + Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=\"cafebabe:deadbeef\"", + Secret: "cafebabe:deadbeef", + File: "tmp.sh", + Line: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`, + RuleID: "sidekiq-secret", + Tags: []string{}, + Entropy: 2.6098502, + StartLine: 0, + EndLine: 0, + StartColumn: 21, + EndColumn: 74, + }, + }, + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true¶m2=false#heading1"`, + FilePath: "tmp.sh", + }, + expectedFindings: []report.Finding{ + { + Description: "Sidekiq Sensitive URL", + Match: "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:", + Secret: "cafeb4b3:d3adb33f", + File: "tmp.sh", + Line: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true¶m2=false#heading1"`, + RuleID: "sidekiq-sensitive-url", + Tags: []string{}, + Entropy: 2.984234, + StartLine: 0, + EndLine: 0, + StartColumn: 8, + EndColumn: 58, + }, + }, + }, + { + cfgName: "allow_aws_re", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "allow_path", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "allow_commit", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: "tmp.go", + CommitSHA: "allowthiscommit", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "entropy_group", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{ + { + Description: "Discord API key", + Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"", + Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5", + Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + File: "tmp.go", + RuleID: "discord-api-key", + Tags: []string{}, + Entropy: 3.7906237, + StartLine: 0, + EndLine: 0, + StartColumn: 7, + EndColumn: 93, + }, + }, + }, + { + cfgName: "generic_with_py_path", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "generic_with_py_path", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.py", + }, + expectedFindings: []report.Finding{ + { + Description: "Generic API Key", + Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"", + Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5", + Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + File: "tmp.py", + RuleID: "generic-api-key", + Tags: []string{}, + Entropy: 3.7906237, + StartLine: 0, + EndLine: 0, + StartColumn: 22, + EndColumn: 93, + }, + }, + }, + { + cfgName: "path_only", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.py", + }, + expectedFindings: []report.Finding{ + { + Description: "Python Files", + Match: "file detected: tmp.py", + File: "tmp.py", + RuleID: "python-files-only", + Tags: []string{}, + }, + }, + }, + { + cfgName: "bad_entropy_group", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"), + }, + { + cfgName: "simple", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: filepath.Join(configPath, "simple.toml"), + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "allow_global_aws_re", + fragment: Fragment{ + Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`, + FilePath: "tmp.go", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "generic_with_py_path", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: "tmp.py", + }, + expectedFindings: []report.Finding{}, + }, + { + cfgName: "path_only", + baselinePath: ".baseline.json", + fragment: Fragment{ + Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`, + FilePath: ".baseline.json", + }, + expectedFindings: []report.Finding{}, + }, + } + + for _, tt := range tests { + viper.Reset() + viper.AddConfigPath(configPath) + viper.SetConfigName(tt.cfgName) + viper.SetConfigType("toml") + err := viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, err := vc.Translate() + cfg.Path = filepath.Join(configPath, tt.cfgName+".toml") + if tt.wantError != nil { + if err == nil { + t.Errorf("expected error") + } + assert.Equal(t, tt.wantError, err) + } + d := NewDetector(cfg) + d.baselinePath = tt.baselinePath + + findings := d.Detect(tt.fragment) + assert.ElementsMatch(t, tt.expectedFindings, findings) + } +} + +// TestFromGit tests the FromGit function +func TestFromGit(t *testing.T) { + tests := []struct { + cfgName string + source string + logOpts string + expectedFindings []report.Finding + }{ + { + source: filepath.Join(repoBasePath, "small"), + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + StartLine: 20, + EndLine: 20, + StartColumn: 19, + EndColumn: 38, + Line: "\n awsToken := \"AKIALALEMEL33243OLIA\"", + Secret: "AKIALALEMEL33243OLIA", + Match: "AKIALALEMEL33243OLIA", + File: "main.go", + Date: "2021-11-02T23:37:53Z", + Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587", + Author: "Zachary Rice", + Email: "zricer@protonmail.com", + Message: "Accidentally add a secret", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + Entropy: 3.0841837, + Fingerprint: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587:main.go:aws-access-key:20", + }, + { + Description: "AWS Access Key", + StartLine: 9, + EndLine: 9, + StartColumn: 17, + EndColumn: 36, + Secret: "AKIALALEMEL33243OLIA", + Match: "AKIALALEMEL33243OLIA", + Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"", + File: "foo/foo.go", + Date: "2021-11-02T23:48:06Z", + Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca", + Author: "Zach Rice", + Email: "zricer@protonmail.com", + Message: "adding foo package with secret", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + Entropy: 3.0841837, + Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9", + }, + }, + }, + { + source: filepath.Join(repoBasePath, "small"), + logOpts: "--all foo...", + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + StartLine: 9, + EndLine: 9, + StartColumn: 17, + EndColumn: 36, + Secret: "AKIALALEMEL33243OLIA", + Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"", + Match: "AKIALALEMEL33243OLIA", + Date: "2021-11-02T23:48:06Z", + File: "foo/foo.go", + Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca", + Author: "Zach Rice", + Email: "zricer@protonmail.com", + Message: "adding foo package with secret", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + Entropy: 3.0841837, + Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9", + }, + }, + }, + } + + err := moveDotGit("dotGit", ".git") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := moveDotGit(".git", "dotGit"); err != nil { + t.Error(err) + } + }() + + for _, tt := range tests { + + viper.AddConfigPath(configPath) + viper.SetConfigName("simple") + viper.SetConfigType("toml") + err = viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, err := vc.Translate() + if err != nil { + t.Error(err) + } + detector := NewDetector(cfg) + findings, err := detector.DetectGit(tt.source, tt.logOpts, DetectType) + if err != nil { + t.Error(err) + } + + for _, f := range findings { + f.Match = "" // remove lines cause copying and pasting them has some wack formatting + } + assert.ElementsMatch(t, tt.expectedFindings, findings) + } +} +func TestFromGitStaged(t *testing.T) { + tests := []struct { + cfgName string + source string + logOpts string + expectedFindings []report.Finding + }{ + { + source: filepath.Join(repoBasePath, "staged"), + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + StartLine: 7, + EndLine: 7, + StartColumn: 18, + EndColumn: 37, + Line: "\n\taws_token2 := \"AKIALALEMEL33243OLIA\" // this one is not", + Match: "AKIALALEMEL33243OLIA", + Secret: "AKIALALEMEL33243OLIA", + File: "api/api.go", + SymlinkFile: "", + Commit: "", + Entropy: 3.0841837, + Author: "", + Email: "", + Date: "0001-01-01T00:00:00Z", + Message: "", + Tags: []string{ + "key", + "AWS", + }, + RuleID: "aws-access-key", + Fingerprint: "api/api.go:aws-access-key:7", + }, + }, + }, + } + + err := moveDotGit("dotGit", ".git") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := moveDotGit(".git", "dotGit"); err != nil { + t.Error(err) + } + }() + + for _, tt := range tests { + + viper.AddConfigPath(configPath) + viper.SetConfigName("simple") + viper.SetConfigType("toml") + err = viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, err := vc.Translate() + if err != nil { + t.Error(err) + } + detector := NewDetector(cfg) + detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore")) + findings, err := detector.DetectGit(tt.source, tt.logOpts, ProtectStagedType) + if err != nil { + t.Error(err) + } + + for _, f := range findings { + f.Match = "" // remove lines cause copying and pasting them has some wack formatting + } + assert.ElementsMatch(t, tt.expectedFindings, findings) + } +} + +// TestFromFiles tests the FromFiles function +func TestFromFiles(t *testing.T) { + tests := []struct { + cfgName string + source string + expectedFindings []report.Finding + }{ + { + source: filepath.Join(repoBasePath, "nogit"), + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + StartLine: 20, + EndLine: 20, + StartColumn: 16, + EndColumn: 35, + Match: "AKIALALEMEL33243OLIA", + Secret: "AKIALALEMEL33243OLIA", + Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"", + File: "../testdata/repos/nogit/main.go", + SymlinkFile: "", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + Entropy: 3.0841837, + Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20", + }, + }, + }, + { + source: filepath.Join(repoBasePath, "nogit", "main.go"), + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "AWS Access Key", + StartLine: 20, + EndLine: 20, + StartColumn: 16, + EndColumn: 35, + Match: "AKIALALEMEL33243OLIA", + Secret: "AKIALALEMEL33243OLIA", + Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"", + File: "../testdata/repos/nogit/main.go", + RuleID: "aws-access-key", + Tags: []string{"key", "AWS"}, + Entropy: 3.0841837, + Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20", + }, + }, + }, + } + + for _, tt := range tests { + viper.AddConfigPath(configPath) + viper.SetConfigName("simple") + viper.SetConfigType("toml") + err := viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, _ := vc.Translate() + detector := NewDetector(cfg) + detector.FollowSymlinks = true + findings, err := detector.DetectFiles(tt.source) + if err != nil { + t.Error(err) + } + + assert.ElementsMatch(t, tt.expectedFindings, findings) + } +} + +func TestDetectWithSymlinks(t *testing.T) { + tests := []struct { + cfgName string + source string + expectedFindings []report.Finding + }{ + { + source: filepath.Join(repoBasePath, "symlinks/file_symlink"), + cfgName: "simple", + expectedFindings: []report.Finding{ + { + Description: "Asymmetric Private Key", + StartLine: 1, + EndLine: 1, + StartColumn: 1, + EndColumn: 35, + Match: "-----BEGIN OPENSSH PRIVATE KEY-----", + Secret: "-----BEGIN OPENSSH PRIVATE KEY-----", + Line: "-----BEGIN OPENSSH PRIVATE KEY-----", + File: "../testdata/repos/symlinks/source_file/id_ed25519", + SymlinkFile: "../testdata/repos/symlinks/file_symlink/symlinked_id_ed25519", + RuleID: "apkey", + Tags: []string{"key", "AsymmetricPrivateKey"}, + Entropy: 3.587164, + Fingerprint: "../testdata/repos/symlinks/source_file/id_ed25519:apkey:1", + }, + }, + }, + } + + for _, tt := range tests { + viper.AddConfigPath(configPath) + viper.SetConfigName("simple") + viper.SetConfigType("toml") + err := viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + cfg, _ := vc.Translate() + detector := NewDetector(cfg) + detector.FollowSymlinks = true + findings, err := detector.DetectFiles(tt.source) + if err != nil { + t.Error(err) + } + assert.ElementsMatch(t, tt.expectedFindings, findings) + } +} + +func moveDotGit(from, to string) error { + repoDirs, err := os.ReadDir("../testdata/repos") + if err != nil { + return err + } + for _, dir := range repoDirs { + if to == ".git" { + _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit")) + if os.IsNotExist(err) { + // dont want to delete the only copy of .git accidentally + continue + } + os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git")) + } + if !dir.IsDir() { + continue + } + _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from)) + if os.IsNotExist(err) { + continue + } + + err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from), + fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to)) + if err != nil { + return err + } + } + return nil +} diff --git a/cli/detect/git/git.go b/cli/detect/git/git.go new file mode 100644 index 0000000000..6eb5e9d6de --- /dev/null +++ b/cli/detect/git/git.go @@ -0,0 +1,143 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package git + +import ( + "bufio" + "io" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/gitleaks/go-gitdiff/gitdiff" + "github.com/rs/zerolog/log" +) + +var ErrEncountered bool + +// GitLog returns a channel of gitdiff.File objects from the +// git log -p command for the given source. +func GitLog(source string, logOpts string) (<-chan *gitdiff.File, error) { + sourceClean := filepath.Clean(source) + var cmd *exec.Cmd + if logOpts != "" { + args := []string{"-C", sourceClean, "log", "-p", "-U0"} + args = append(args, strings.Split(logOpts, " ")...) + cmd = exec.Command("git", args...) + } else { + cmd = exec.Command("git", "-C", sourceClean, "log", "-p", "-U0", + "--full-history", "--all") + } + + 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 + } + // HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722 + time.Sleep(50 * time.Millisecond) + + return gitdiff.Parse(cmd, stdout) +} + +// GitDiff returns a channel of gitdiff.File objects from +// the git diff command for the given source. +func GitDiff(source string, staged bool) (<-chan *gitdiff.File, error) { + sourceClean := filepath.Clean(source) + var cmd *exec.Cmd + cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0", ".") + if staged { + cmd = exec.Command("git", "-C", sourceClean, "diff", "-U0", + "--staged", ".") + } + 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 + } + // HACK: to avoid https://github.com/zricethezav/gitleaks/issues/722 + time.Sleep(50 * time.Millisecond) + + return gitdiff.Parse(cmd, stdout) +} + +// listenForStdErr listens for stderr output from git and prints it to stdout +// then exits with exit code 1 +func listenForStdErr(stderr io.ReadCloser) { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + // if git throws one of the following errors: + // + // exhaustive rename detection was skipped due to too many files. + // you may want to set your diff.renameLimit variable to at least + // (some large number) and retry the command. + // + // inexact rename detection was skipped due to too many files. + // you may want to set your diff.renameLimit variable to at least + // (some large number) and retry the command. + // + // we skip exiting the program as git log -p/git diff will continue + // to send data to stdout and finish executing. This next bit of + // code prevents gitleaks from stopping mid scan if this error is + // encountered + if strings.Contains(scanner.Text(), + "exhaustive rename detection was skipped") || + strings.Contains(scanner.Text(), + "inexact rename detection was skipped") || + strings.Contains(scanner.Text(), + "you may want to set your diff.renameLimit") { + log.Warn().Msg(scanner.Text()) + } else { + log.Error().Msgf("[git] %s", scanner.Text()) + + // asynchronously set this error flag to true so that we can + // capture a log message and exit with a non-zero exit code + // This value should get set before the `git` command exits so it's + // safe-ish, although I know I know, bad practice. + ErrEncountered = true + } + } +} diff --git a/cli/detect/git/git_test.go b/cli/detect/git/git_test.go new file mode 100644 index 0000000000..3a2ea9c359 --- /dev/null +++ b/cli/detect/git/git_test.go @@ -0,0 +1,158 @@ +package git_test + +// TODO: commenting out this test for now because it's flaky. Alternatives to consider to get this working: +// -- use `git stash` instead of `restore()` + +// const repoBasePath = "../../testdata/repos/" + +// const expectPath = "../../testdata/expected/" + +// func TestGitLog(t *testing.T) { +// tests := []struct { +// source string +// logOpts string +// expected string +// }{ +// { +// source: filepath.Join(repoBasePath, "small"), +// expected: filepath.Join(expectPath, "git", "small.txt"), +// }, +// { +// source: filepath.Join(repoBasePath, "small"), +// expected: filepath.Join(expectPath, "git", "small-branch-foo.txt"), +// logOpts: "--all foo...", +// }, +// } + +// err := moveDotGit("dotGit", ".git") +// if err != nil { +// t.Fatal(err) +// } +// defer func() { +// if err = moveDotGit(".git", "dotGit"); err != nil { +// t.Fatal(err) +// } +// }() + +// for _, tt := range tests { +// files, err := git.GitLog(tt.source, tt.logOpts) +// if err != nil { +// t.Error(err) +// } + +// var diffSb strings.Builder +// for f := range files { +// for _, tf := range f.TextFragments { +// diffSb.WriteString(tf.Raw(gitdiff.OpAdd)) +// } +// } + +// expectedBytes, err := os.ReadFile(tt.expected) +// if err != nil { +// t.Error(err) +// } +// expected := string(expectedBytes) +// if expected != diffSb.String() { +// // write string builder to .got file using os.Create +// err = os.WriteFile(strings.Replace(tt.expected, ".txt", ".got.txt", 1), []byte(diffSb.String()), 0644) +// if err != nil { +// t.Error(err) +// } +// t.Error("expected: ", expected, "got: ", diffSb.String()) +// } +// } +// } + +// func TestGitDiff(t *testing.T) { +// tests := []struct { +// source string +// expected string +// additions string +// target string +// }{ +// { +// source: filepath.Join(repoBasePath, "small"), +// expected: "this line is added\nand another one", +// additions: "this line is added\nand another one", +// target: filepath.Join(repoBasePath, "small", "main.go"), +// }, +// } + +// err := moveDotGit("dotGit", ".git") +// if err != nil { +// t.Fatal(err) +// } +// defer func() { +// if err = moveDotGit(".git", "dotGit"); err != nil { +// t.Fatal(err) +// } +// }() + +// for _, tt := range tests { +// noChanges, err := os.ReadFile(tt.target) +// if err != nil { +// t.Error(err) +// } +// err = os.WriteFile(tt.target, []byte(tt.additions), 0644) +// if err != nil { +// restore(tt.target, noChanges, t) +// t.Error(err) +// } + +// files, err := git.GitDiff(tt.source, false) +// if err != nil { +// restore(tt.target, noChanges, t) +// t.Error(err) +// } + +// for f := range files { +// sb := strings.Builder{} +// for _, tf := range f.TextFragments { +// sb.WriteString(tf.Raw(gitdiff.OpAdd)) +// } +// if sb.String() != tt.expected { +// restore(tt.target, noChanges, t) +// t.Error("expected: ", tt.expected, "got: ", sb.String()) +// } +// } +// restore(tt.target, noChanges, t) +// } +// } + +// func restore(path string, data []byte, t *testing.T) { +// err := os.WriteFile(path, data, 0644) +// if err != nil { +// t.Fatal(err) +// } +// } + +// func moveDotGit(from, to string) error { +// repoDirs, err := os.ReadDir("../../testdata/repos") +// if err != nil { +// return err +// } +// for _, dir := range repoDirs { +// if to == ".git" { +// _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit")) +// if os.IsNotExist(err) { +// // dont want to delete the only copy of .git accidentally +// continue +// } +// os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git")) +// } +// if !dir.IsDir() { +// continue +// } +// _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from)) +// if os.IsNotExist(err) { +// continue +// } + +// err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from), +// fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to)) +// if err != nil { +// return err +// } +// } +// return nil +// } diff --git a/cli/detect/location.go b/cli/detect/location.go new file mode 100644 index 0000000000..418af83f6b --- /dev/null +++ b/cli/detect/location.go @@ -0,0 +1,101 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +// Location represents a location in a file +type Location struct { + startLine int + endLine int + startColumn int + endColumn int + startLineIndex int + endLineIndex int +} + +func location(fragment Fragment, matchIndex []int) Location { + var ( + prevNewLine int + location Location + lineSet bool + _lineNum int + ) + + start := matchIndex[0] + end := matchIndex[1] + + // default startLineIndex to 0 + location.startLineIndex = 0 + + // Fixes: https://github.com/zricethezav/gitleaks/issues/1037 + // When a fragment does NOT have any newlines, a default "newline" + // will be counted to make the subsequent location calculation logic work + // for fragments will no newlines. + if len(fragment.newlineIndices) == 0 { + fragment.newlineIndices = [][]int{ + {len(fragment.Raw), len(fragment.Raw) + 1}, + } + } + + for lineNum, pair := range fragment.newlineIndices { + _lineNum = lineNum + newLineByteIndex := pair[0] + if prevNewLine <= start && start < newLineByteIndex { + lineSet = true + location.startLine = lineNum + location.endLine = lineNum + location.startColumn = (start - prevNewLine) + 1 // +1 because counting starts at 1 + location.startLineIndex = prevNewLine + location.endLineIndex = newLineByteIndex + } + if prevNewLine < end && end <= newLineByteIndex { + location.endLine = lineNum + location.endColumn = (end - prevNewLine) + location.endLineIndex = newLineByteIndex + } + prevNewLine = pair[0] + } + + if !lineSet { + // if lines never get set then that means the secret is most likely + // on the last line of the diff output and the diff output does not have + // a newline + location.startColumn = (start - prevNewLine) + 1 // +1 because counting starts at 1 + location.endColumn = (end - prevNewLine) + location.startLine = _lineNum + 1 + location.endLine = _lineNum + 1 + + // search for new line byte index + i := 0 + for end+i < len(fragment.Raw) { + if fragment.Raw[end+i] == '\n' { + break + } + if fragment.Raw[end+i] == '\r' { + break + } + i++ + } + location.endLineIndex = end + i + } + return location +} diff --git a/cli/detect/location_test.go b/cli/detect/location_test.go new file mode 100644 index 0000000000..f76a6f814f --- /dev/null +++ b/cli/detect/location_test.go @@ -0,0 +1,82 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + "testing" +) + +// TestGetLocation tests the getLocation function. +func TestGetLocation(t *testing.T) { + tests := []struct { + linePairs [][]int + start int + end int + wantLocation Location + }{ + { + linePairs: [][]int{ + {0, 39}, + {40, 55}, + {56, 57}, + }, + start: 35, + end: 38, + wantLocation: Location{ + startLine: 1, + startColumn: 36, + endLine: 1, + endColumn: 38, + startLineIndex: 0, + endLineIndex: 40, + }, + }, + { + linePairs: [][]int{ + {0, 39}, + {40, 55}, + {56, 57}, + }, + start: 40, + end: 44, + wantLocation: Location{ + startLine: 2, + startColumn: 1, + endLine: 2, + endColumn: 4, + startLineIndex: 40, + endLineIndex: 56, + }, + }, + } + + for _, test := range tests { + loc := location(Fragment{newlineIndices: test.linePairs}, []int{test.start, test.end}) + if loc != test.wantLocation { + t.Errorf("\nstartLine %d\nstartColumn: %d\nendLine: %d\nendColumn: %d\nstartLineIndex: %d\nendlineIndex %d", + loc.startLine, loc.startColumn, loc.endLine, loc.endColumn, loc.startLineIndex, loc.endLineIndex) + + t.Error("got", loc, "want", test.wantLocation) + } + } +} diff --git a/cli/detect/utils.go b/cli/detect/utils.go new file mode 100644 index 0000000000..462716239a --- /dev/null +++ b/cli/detect/utils.go @@ -0,0 +1,211 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package detect + +import ( + // "encoding/json" + "fmt" + "math" + "strings" + "time" + + "github.com/charmbracelet/lipgloss" + + "github.com/Infisical/infisical-merge/report" + + "github.com/gitleaks/go-gitdiff/gitdiff" + "github.com/rs/zerolog/log" +) + +// augmentGitFinding updates the start and end line numbers of a finding to include the +// delta from the git diff +func augmentGitFinding(finding report.Finding, textFragment *gitdiff.TextFragment, f *gitdiff.File) report.Finding { + if !strings.HasPrefix(finding.Match, "file detected") { + finding.StartLine += int(textFragment.NewPosition) + finding.EndLine += int(textFragment.NewPosition) + } + + if f.PatchHeader != nil { + finding.Commit = f.PatchHeader.SHA + finding.Message = f.PatchHeader.Message() + if f.PatchHeader.Author != nil { + finding.Author = f.PatchHeader.Author.Name + finding.Email = f.PatchHeader.Author.Email + } + finding.Date = f.PatchHeader.AuthorDate.UTC().Format(time.RFC3339) + } + return finding +} + +// shannonEntropy calculates the entropy of data using the formula defined here: +// https://en.wiktionary.org/wiki/Shannon_entropy +// Another way to think about what this is doing is calculating the number of bits +// needed to on average encode the data. So, the higher the entropy, the more random the data, the +// more bits needed to encode that data. +func shannonEntropy(data string) (entropy float64) { + if data == "" { + return 0 + } + + charCounts := make(map[rune]int) + for _, char := range data { + charCounts[char]++ + } + + invLength := 1.0 / float64(len(data)) + for _, count := range charCounts { + freq := float64(count) * invLength + entropy -= freq * math.Log2(freq) + } + + return entropy +} + +// filter will dedupe and redact findings +func filter(findings []report.Finding, redact bool) []report.Finding { + var retFindings []report.Finding + for _, f := range findings { + include := true + if strings.Contains(strings.ToLower(f.RuleID), "generic") { + for _, fPrime := range findings { + if f.StartLine == fPrime.StartLine && + f.Commit == fPrime.Commit && + f.RuleID != fPrime.RuleID && + strings.Contains(fPrime.Secret, f.Secret) && + !strings.Contains(strings.ToLower(fPrime.RuleID), "generic") { + + genericMatch := strings.Replace(f.Match, f.Secret, "REDACTED", -1) + betterMatch := strings.Replace(fPrime.Match, fPrime.Secret, "REDACTED", -1) + log.Trace().Msgf("skipping %s finding (%s), %s rule takes precendence (%s)", f.RuleID, genericMatch, fPrime.RuleID, betterMatch) + include = false + break + } + } + } + + if redact { + f.Redact() + } + if include { + retFindings = append(retFindings, f) + } + } + return retFindings +} + +func printFinding(f report.Finding, noColor bool) { + // trim all whitespace and tabs + f.Line = strings.TrimSpace(f.Line) + f.Secret = strings.TrimSpace(f.Secret) + f.Match = strings.TrimSpace(f.Match) + + isFileMatch := strings.HasPrefix(f.Match, "file detected:") + skipColor := noColor + finding := "" + var secret lipgloss.Style + + // Matches from filenames do not have a |line| or |secret| + if !isFileMatch { + matchInLineIDX := strings.Index(f.Line, f.Match) + secretInMatchIdx := strings.Index(f.Match, f.Secret) + + skipColor = false + + if matchInLineIDX == -1 || noColor { + skipColor = true + matchInLineIDX = 0 + } + + start := f.Line[0:matchInLineIDX] + startMatchIdx := 0 + if matchInLineIDX > 20 { + startMatchIdx = matchInLineIDX - 20 + start = "..." + f.Line[startMatchIdx:matchInLineIDX] + } + + matchBeginning := lipgloss.NewStyle().SetString(f.Match[0:secretInMatchIdx]).Foreground(lipgloss.Color("#f5d445")) + secret = lipgloss.NewStyle().SetString(f.Secret). + Bold(true). + Italic(true). + Foreground(lipgloss.Color("#f05c07")) + matchEnd := lipgloss.NewStyle().SetString(f.Match[secretInMatchIdx+len(f.Secret):]).Foreground(lipgloss.Color("#f5d445")) + + lineEndIdx := matchInLineIDX + len(f.Match) + if len(f.Line)-1 <= lineEndIdx { + lineEndIdx = len(f.Line) - 1 + } + + lineEnd := f.Line[lineEndIdx:] + + if len(f.Secret) > 100 { + secret = lipgloss.NewStyle().SetString(f.Secret[0:100] + "..."). + Bold(true). + Italic(true). + Foreground(lipgloss.Color("#f05c07")) + } + if len(lineEnd) > 20 { + lineEnd = lineEnd[0:20] + "..." + } + + finding = fmt.Sprintf("%s%s%s%s%s\n", strings.TrimPrefix(strings.TrimLeft(start, " "), "\n"), matchBeginning, secret, matchEnd, lineEnd) + } + + if skipColor || isFileMatch { + fmt.Printf("%-12s %s\n", "Finding:", f.Match) + fmt.Printf("%-12s %s\n", "Secret:", f.Secret) + } else { + fmt.Printf("%-12s %s", "Finding:", finding) + fmt.Printf("%-12s %s\n", "Secret:", secret) + } + + fmt.Printf("%-12s %s\n", "RuleID:", f.RuleID) + fmt.Printf("%-12s %f\n", "Entropy:", f.Entropy) + if f.File == "" { + fmt.Println("") + return + } + fmt.Printf("%-12s %s\n", "File:", f.File) + fmt.Printf("%-12s %d\n", "Line:", f.StartLine) + if f.Commit == "" { + fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint) + fmt.Println("") + return + } + fmt.Printf("%-12s %s\n", "Commit:", f.Commit) + fmt.Printf("%-12s %s\n", "Author:", f.Author) + fmt.Printf("%-12s %s\n", "Email:", f.Email) + fmt.Printf("%-12s %s\n", "Date:", f.Date) + fmt.Printf("%-12s %s\n", "Fingerprint:", f.Fingerprint) + fmt.Println("") +} + +func containsDigit(s string) bool { + for _, c := range s { + switch c { + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + return true + } + + } + return false +} diff --git a/cli/go.mod b/cli/go.mod index d1aadd2084..3e9d2aac64 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -4,12 +4,20 @@ go 1.19 require ( github.com/99designs/keyring v1.2.2 + github.com/charmbracelet/lipgloss v0.5.0 + github.com/fatih/semgroup v1.2.0 + github.com/gitleaks/go-gitdiff v0.8.0 + github.com/h2non/filetype v1.1.3 github.com/mattn/go-isatty v0.0.14 github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a github.com/muesli/mango-cobra v1.2.0 github.com/muesli/reflow v0.3.0 github.com/muesli/roff v0.1.0 + github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 + github.com/rs/zerolog v1.26.1 github.com/spf13/cobra v1.6.1 + github.com/spf13/viper v1.8.1 + github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d golang.org/x/term v0.5.0 ) @@ -19,22 +27,39 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/danieljoos/wincred v1.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-openapi/errors v0.20.2 // indirect github.com/go-openapi/strfmt v0.21.3 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/muesli/mango v0.1.0 // indirect github.com/muesli/mango-pflag v0.1.0 // indirect + github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect golang.org/x/net v0.7.0 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( @@ -43,6 +68,5 @@ require ( github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/manifoldco/promptui v0.9.0 - github.com/sirupsen/logrus v1.9.0 github.com/spf13/pflag v1.0.5 // indirect ) diff --git a/cli/go.sum b/cli/go.sum index 2b514ea6e9..24b6aecd36 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,9 +1,59 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -13,6 +63,12 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= @@ -22,8 +78,26 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg= +github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gitleaks/go-gitdiff v0.8.0 h1:7aExTZm+K/M/EQKOyYcub8rIAdWK6ONxPGuRzxmWW+0= +github.com/gitleaks/go-gitdiff v0.8.0/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= @@ -32,35 +106,153 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= +github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= @@ -72,75 +264,458 @@ github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbY github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8= +github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index f0a347800b..5f8bc7aaed 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -5,7 +5,7 @@ import ( "github.com/Infisical/infisical-merge/packages/config" "github.com/go-resty/resty/v2" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) const USER_AGENT = "cli" @@ -222,7 +222,7 @@ func CallIsAuthenticated(httpClient *resty.Client) bool { } if response.IsError() { - log.Debugln(fmt.Errorf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response)) + log.Debug().Msgf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response) return false } diff --git a/cli/packages/cmd/export.go b/cli/packages/cmd/export.go index 07512798b3..230e728375 100644 --- a/cli/packages/cmd/export.go +++ b/cli/packages/cmd/export.go @@ -11,7 +11,7 @@ import ( "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -30,11 +30,6 @@ var exportCmd = &cobra.Command{ DisableFlagsInUseLine: true, Example: "infisical export --env=prod --format=json > secrets.json", Args: cobra.NoArgs, - PreRun: func(cmd *cobra.Command, args []string) { - toggleDebug(cmd, args) - // util.RequireLogin() - // util.RequireLocalWorkspaceFile() - }, Run: func(cmd *cobra.Command, args []string) { environmentName, _ := cmd.Flags().GetString("env") if !cmd.Flags().Changed("env") { @@ -175,8 +170,7 @@ func formatAsJson(envs []models.SingleEnvironmentVariable) string { // Dump as a json array json, err := json.Marshal(envs) if err != nil { - log.Errorln("Unable to marshal environment variables to JSON") - log.Debugln(err) + log.Err(err).Msgf("Unable to marshal environment variables to JSON") return "" } return string(json) diff --git a/cli/packages/cmd/init.go b/cli/packages/cmd/init.go index 1562f68562..fbf5e02701 100644 --- a/cli/packages/cmd/init.go +++ b/cli/packages/cmd/init.go @@ -12,7 +12,7 @@ import ( "github.com/Infisical/infisical-merge/packages/util" "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -30,8 +30,8 @@ var initCmd = &cobra.Command{ if util.WorkspaceConfigFileExistsInCurrentPath() { shouldOverride, err := shouldOverrideWorkspacePrompt() if err != nil { - log.Errorln("Unable to parse your answer") - log.Debug(err) + log.Error().Msg("Unable to parse your answer") + log.Debug().Err(err) return } diff --git a/cli/packages/cmd/logging.go b/cli/packages/cmd/logging.go deleted file mode 100644 index db1afa34fd..0000000000 --- a/cli/packages/cmd/logging.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "fmt" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -var debugLogging bool - -type PlainFormatter struct { -} - -func (f *PlainFormatter) Format(entry *log.Entry) ([]byte, error) { - return []byte(fmt.Sprintf("%s\n", entry.Message)), nil -} -func toggleDebug(cmd *cobra.Command, args []string) { - if debugLogging { - log.Info("Debug logs enabled") - log.SetLevel(log.DebugLevel) - log.SetFormatter(&log.TextFormatter{}) - } else { - plainFormatter := new(PlainFormatter) - log.SetFormatter(plainFormatter) - } -} diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index c3a78a941c..55b3fdc4ff 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -22,7 +22,7 @@ import ( "github.com/fatih/color" "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "golang.org/x/crypto/argon2" ) @@ -44,12 +44,11 @@ var loginCmd = &cobra.Command{ Use: "login", Short: "Login into your Infisical account", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Run: func(cmd *cobra.Command, args []string) { currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails() // if the key can't be found or there is an error getting current credentials from key ring, allow them to override if err != nil && (strings.Contains(err.Error(), "The specified item could not be found in the keyring") || strings.Contains(err.Error(), "unable to get key from Keyring") || strings.Contains(err.Error(), "GetUserCredsFromKeyRing")) { - log.Debug(err) + log.Debug().Err(err) } else if err != nil { util.HandleError(err) } @@ -97,8 +96,8 @@ var loginCmd = &cobra.Command{ loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password) if err != nil { - log.Infoln("Unable to authenticate with the provided credentials, please try again") - log.Debugln(err) + fmt.Println("Unable to authenticate with the provided credentials, please try again") + log.Debug().Err(err) return } @@ -152,7 +151,7 @@ var loginCmd = &cobra.Command{ var decryptedPrivateKey []byte if loginTwoResponse.EncryptionVersion == 1 { - log.Debug("Login version 1") + log.Debug().Msg("Login version 1") encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey) tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag) if err != nil { @@ -175,7 +174,7 @@ var loginCmd = &cobra.Command{ decryptedPrivateKey = computedDecryptedPrivateKey } else if loginTwoResponse.EncryptionVersion == 2 { - log.Debug("Login version 2") + log.Debug().Msg("Login version 2") protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey) if err != nil { util.HandleError(err) @@ -239,7 +238,7 @@ var loginCmd = &cobra.Command{ } if string(decryptedPrivateKey) == "" || email == "" || loginTwoResponse.Token == "" { - log.Debugf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token) + log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token) util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info") } @@ -252,9 +251,9 @@ var loginCmd = &cobra.Command{ err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored) if err != nil { currentVault, _ := util.GetCurrentVaultBackend() - log.Errorf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault) - log.Errorln("To trouble shoot further, read https://infisical.com/docs/cli/faq") - log.Debugln(err) + log.Error().Msgf("Unable to store your credentials in system vault [%s]. Rerun with flag -d to see full logs", currentVault) + log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq") + log.Debug().Err(err) return } @@ -397,7 +396,7 @@ func askForLoginCredentials() (email string, password string, err error) { } func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2Response, *api.GetLoginTwoV2Response, error) { - log.Debugln("getFreshUserCredentials:", "email", email, "password", password) + log.Debug().Msg(fmt.Sprint("getFreshUserCredentials: ", "email", email, "password: ", password)) httpClient := resty.New() httpClient.SetRetryCount(5) diff --git a/cli/packages/cmd/reset.go b/cli/packages/cmd/reset.go index 6c378fd4c4..c9cfae09bf 100644 --- a/cli/packages/cmd/reset.go +++ b/cli/packages/cmd/reset.go @@ -16,9 +16,6 @@ var resetCmd = &cobra.Command{ DisableFlagsInUseLine: true, Example: "infisical reset", Args: cobra.NoArgs, - PreRun: func(cmd *cobra.Command, args []string) { - toggleDebug(cmd, args) - }, Run: func(cmd *cobra.Command, args []string) { // delete config _, pathToDir, err := util.GetFullConfigFilePath() diff --git a/cli/packages/cmd/root.go b/cli/packages/cmd/root.go index 92ad430255..f764e6d11b 100644 --- a/cli/packages/cmd/root.go +++ b/cli/packages/cmd/root.go @@ -5,7 +5,10 @@ package cmd import ( "os" + "strings" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/Infisical/infisical-merge/packages/config" @@ -30,8 +33,8 @@ func Execute() { } func init() { - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging") + cobra.OnInitialize(initLog) + rootCmd.PersistentFlags().StringP("log-level", "l", "info", "log level (trace, debug, info, warn, error, fatal)") rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: INFISICAL_API_URL]") rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { if !util.IsRunningInDocker() { @@ -46,5 +49,30 @@ func init() { config.INFISICAL_URL = envInfisicalBackendUrl } } +} + +func initLog() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + zerolog.SetGlobalLevel(zerolog.InfoLevel) + ll, err := rootCmd.Flags().GetString("log-level") + if err != nil { + log.Fatal().Msg(err.Error()) + } + switch strings.ToLower(ll) { + case "trace": + zerolog.SetGlobalLevel(zerolog.TraceLevel) + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "err", "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "fatal": + zerolog.SetGlobalLevel(zerolog.FatalLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } } diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go index ba60c9fec0..982c2e3298 100644 --- a/cli/packages/cmd/run.go +++ b/cli/packages/cmd/run.go @@ -15,7 +15,7 @@ import ( "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" "github.com/fatih/color" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -28,7 +28,6 @@ var runCmd = &cobra.Command{ Use: "run [any infisical run command flags] -- [your application start command]", Short: "Used to inject environments variables into your application process", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: func(cmd *cobra.Command, args []string) error { // Check if the --command flag has been set commandFlagSet := cmd.Flags().Changed("command") @@ -124,7 +123,7 @@ var runCmd = &cobra.Command{ env = append(env, s) } - log.Debugf("injecting the following environment variables into shell: %v", env) + log.Debug().Msgf("injecting the following environment variables into shell: %v", env) if cmd.Flags().Changed("command") { command := cmd.Flag("command").Value.String() @@ -217,7 +216,7 @@ func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env [] cmd.Env = env color.Green("Injecting %v Infisical secrets into your application process", secretsCount) - log.Debugf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand) + log.Debug().Msgf("executing command: %s %s %s \n", shell[0], shell[1], fullCommand) return execCmd(cmd) } diff --git a/cli/packages/cmd/scan-detect.go b/cli/packages/cmd/scan-detect.go new file mode 100644 index 0000000000..d3d6fcfee0 --- /dev/null +++ b/cli/packages/cmd/scan-detect.go @@ -0,0 +1,307 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package cmd + +import ( + "os" + "path/filepath" + "strings" + "time" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/Infisical/infisical-merge/config" + "github.com/Infisical/infisical-merge/detect" + "github.com/Infisical/infisical-merge/report" +) + +func init() { + rootCmd.AddCommand(detectCmd) + detectCmd.Flags().String("log-opts", "", "git log options") + detectCmd.Flags().Bool("no-git", false, "treat git repo as a regular directory and scan those files, --log-opts has no effect on the scan when --no-git is set") + 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("config", "c", "", configDescription) + detectCmd.Flags().Int("exit-code", 1, "exit code when leaks have been encountered") + detectCmd.Flags().StringP("source", "s", ".", "path to source") + detectCmd.Flags().StringP("report-path", "r", "", "report file") + detectCmd.Flags().StringP("report-format", "f", "json", "output format (json, csv, sarif)") + detectCmd.Flags().StringP("baseline-path", "b", "", "path to baseline with issues that can be ignored") + detectCmd.Flags().BoolP("verbose", "v", false, "show verbose output from scan (which file, where in the file, what secret)") + detectCmd.Flags().BoolP("no-color", "", false, "turn off color for verbose output") + detectCmd.Flags().Int("max-target-megabytes", 0, "files larger than this will be skipped") + detectCmd.Flags().Bool("redact", false, "redact secrets from logs and stdout") + + err := viper.BindPFlag("config", detectCmd.Flags().Lookup("config")) + if err != nil { + log.Fatal().Msgf("err binding config %s", err.Error()) + } + + // Hide the flag + detectCmd.Flags().MarkHidden("config") +} + +var detectCmd = &cobra.Command{ + Use: "scan", + Short: "scan for secrets in repos, directories, and files", + Run: runDetect, +} + +func runDetect(cmd *cobra.Command, args []string) { + initScanConfig(cmd) + + var ( + vc config.ViperConfig + findings []report.Finding + err error + ) + + // Load config + if err = viper.Unmarshal(&vc); err != nil { + log.Fatal().Err(err).Msg("Failed to load config") + } + cfg, err := vc.Translate() + if err != nil { + log.Fatal().Err(err).Msg("Failed to load config") + } + cfg.Path, _ = cmd.Flags().GetString("config") + + // start timer + start := time.Now() + + // Setup detector + detector := detect.NewDetector(cfg) + detector.Config.Path, err = cmd.Flags().GetString("config") + if err != nil { + log.Fatal().Err(err).Msg("") + } + source, err := cmd.Flags().GetString("source") + if err != nil { + log.Fatal().Err(err).Msg("") + } + // if config path is not set, then use the {source}/.gitleaks.toml path. + // note that there may not be a `{source}/.gitleaks.toml` file, this is ok. + if detector.Config.Path == "" { + detector.Config.Path = filepath.Join(source, ".gitleaks.toml") + } + // set verbose flag + if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil { + log.Fatal().Err(err).Msg("") + } + // set redact flag + if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil { + log.Fatal().Err(err).Msg("") + } + if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil { + log.Fatal().Err(err).Msg("") + } + // set color flag + if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil { + log.Fatal().Err(err).Msg("") + } + + if fileExists(filepath.Join(source, ".infisicalignore")) { + if err = detector.AddGitleaksIgnore(filepath.Join(source, ".infisicalignore")); err != nil { + log.Fatal().Err(err).Msg("could not call AddInfisicalIgnore") + } + } + + // ignore findings from the baseline (an existing report in json format generated earlier) + baselinePath, _ := cmd.Flags().GetString("baseline-path") + if baselinePath != "" { + err = detector.AddBaseline(baselinePath, source) + if err != nil { + log.Error().Msgf("Could not load baseline. The path must point of a gitleaks report generated using the default format: %s", err) + } + } + + // set follow symlinks flag + if detector.FollowSymlinks, err = cmd.Flags().GetBool("follow-symlinks"); err != nil { + log.Fatal().Err(err).Msg("") + } + + // set exit code + exitCode, err := cmd.Flags().GetInt("exit-code") + if err != nil { + log.Fatal().Err(err).Msg("could not get exit code") + } + + // determine what type of scan: + // - git: scan the history of the repo + // - no-git: scan files by treating the repo as a plain directory + noGit, err := cmd.Flags().GetBool("no-git") + if err != nil { + log.Fatal().Err(err).Msg("could not call GetBool() for no-git") + } + fromPipe, err := cmd.Flags().GetBool("pipe") + if err != nil { + log.Fatal().Err(err) + } + + // start the detector scan + if noGit { + findings, err = detector.DetectFiles(source) + if err != nil { + // don't exit on error, just log it + log.Error().Err(err).Msg("") + } + } else if fromPipe { + findings, err = detector.DetectReader(os.Stdin, 10) + if err != nil { + // log fatal to exit, no need to continue since a report + // will not be generated when scanning from a pipe...for now + log.Fatal().Err(err).Msg("") + } + } else { + var logOpts string + logOpts, err = cmd.Flags().GetString("log-opts") + if err != nil { + log.Fatal().Err(err).Msg("") + } + findings, err = detector.DetectGit(source, logOpts, detect.DetectType) + if err != nil { + // don't exit on error, just log it + log.Error().Err(err).Msg("") + } + } + + // log info about the scan + if err == nil { + log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start))) + if len(findings) != 0 { + log.Warn().Msgf("leaks found: %d", len(findings)) + } else { + log.Info().Msg("no leaks found") + } + } else { + log.Warn().Msgf("partial scan completed in %s", FormatDuration(time.Since(start))) + if len(findings) != 0 { + log.Warn().Msgf("%d leaks found in partial scan", len(findings)) + } else { + log.Warn().Msg("no leaks found in partial scan") + } + } + + // write report if desired + reportPath, _ := cmd.Flags().GetString("report-path") + ext, _ := cmd.Flags().GetString("report-format") + if reportPath != "" { + if err := report.Write(findings, cfg, ext, reportPath); err != nil { + log.Fatal().Err(err).Msg("could not write") + } + } + + if err != nil { + os.Exit(1) + } + + if len(findings) != 0 { + os.Exit(exitCode) + } +} + +func fileExists(fileName string) bool { + // check for a .gitleaksignore file + info, err := os.Stat(fileName) + if err != nil && !os.IsNotExist(err) { + return false + } + + if info != nil && err == nil { + if !info.IsDir() { + return true + } + } + return false +} + +func FormatDuration(d time.Duration) string { + scale := 100 * time.Second + // look for the max scale that is smaller than d + for scale > d { + scale = scale / 10 + } + return d.Round(scale / 100).String() +} + +const configDescription = `config file path +order of precedence: +1. --config/-c +2. env var GITLEAKS_CONFIG +3. (--source/-s)/.gitleaks.toml +If none of the three options are used, then gitleaks will use the default config` + +func initScanConfig(cmd *cobra.Command) { + cfgPath, err := cmd.Flags().GetString("config") + if err != nil { + log.Fatal().Msg(err.Error()) + } + if cfgPath != "" { + viper.SetConfigFile(cfgPath) + log.Debug().Msgf("using gitleaks config %s from `--config`", cfgPath) + } else if os.Getenv("GITLEAKS_CONFIG") != "" { + envPath := os.Getenv("GITLEAKS_CONFIG") + viper.SetConfigFile(envPath) + log.Debug().Msgf("using gitleaks config from GITLEAKS_CONFIG env var: %s", envPath) + } else { + source, err := cmd.Flags().GetString("source") + if err != nil { + log.Fatal().Msg(err.Error()) + } + fileInfo, err := os.Stat(source) + if err != nil { + log.Fatal().Msg(err.Error()) + } + + if !fileInfo.IsDir() { + log.Debug().Msgf("unable to load gitleaks config from %s since --source=%s is a file, using default config", + filepath.Join(source, ".gitleaks.toml"), source) + viper.SetConfigType("toml") + if err = viper.ReadConfig(strings.NewReader(config.DefaultConfig)); err != nil { + log.Fatal().Msgf("err reading toml %s", err.Error()) + } + return + } + + if _, err := os.Stat(filepath.Join(source, ".gitleaks.toml")); os.IsNotExist(err) { + log.Debug().Msgf("no gitleaks config found in path %s, using default gitleaks config", filepath.Join(source, ".gitleaks.toml")) + viper.SetConfigType("toml") + if err = viper.ReadConfig(strings.NewReader(config.DefaultConfig)); err != nil { + log.Fatal().Msgf("err reading default config toml %s", err.Error()) + } + return + } else { + log.Debug().Msgf("using existing gitleaks config %s from `(--source)/.gitleaks.toml`", filepath.Join(source, ".gitleaks.toml")) + } + + viper.AddConfigPath(source) + viper.SetConfigName(".gitleaks") + viper.SetConfigType("toml") + } + if err := viper.ReadInConfig(); err != nil { + log.Fatal().Msgf("unable to load gitleaks config, err: %s", err) + } +} diff --git a/cli/packages/cmd/scan-protect.go b/cli/packages/cmd/scan-protect.go new file mode 100644 index 0000000000..890e7dd30d --- /dev/null +++ b/cli/packages/cmd/scan-protect.go @@ -0,0 +1,160 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package cmd + +import ( + "os" + "path/filepath" + "time" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/Infisical/infisical-merge/config" + "github.com/Infisical/infisical-merge/detect" + "github.com/Infisical/infisical-merge/report" +) + +func init() { + protectCmd.Flags().Bool("staged", false, "detect secrets in a --staged state") + protectCmd.Flags().String("log-opts", "", "git log options") + + protectCmd.Flags().StringP("config", "c", "", configDescription) + protectCmd.Flags().Int("exit-code", 1, "exit code when leaks have been encountered") + protectCmd.Flags().StringP("source", "s", ".", "path to source") + protectCmd.Flags().StringP("report-path", "r", "", "report file") + protectCmd.Flags().StringP("report-format", "f", "json", "output format (json, csv, sarif)") + protectCmd.Flags().StringP("baseline-path", "b", "", "path to baseline with issues that can be ignored") + protectCmd.Flags().BoolP("verbose", "v", false, "show verbose output from scan (which file, where in the file, what secret)") + protectCmd.Flags().BoolP("no-color", "", false, "turn off color for verbose output") + protectCmd.Flags().Int("max-target-megabytes", 0, "files larger than this will be skipped") + protectCmd.Flags().Bool("redact", false, "redact secrets from logs and stdout") + + err := viper.BindPFlag("config", protectCmd.Flags().Lookup("config")) + if err != nil { + log.Fatal().Msgf("err binding config %s", err.Error()) + } + + // Hide the flag + protectCmd.Flags().MarkHidden("config") + + rootCmd.AddCommand(protectCmd) +} + +var protectCmd = &cobra.Command{ + Use: "prevent", + Short: "scan for secrets in uncommitted changes in a git repo", + Run: runProtect, +} + +func runProtect(cmd *cobra.Command, args []string) { + initScanConfig(cmd) + var vc config.ViperConfig + + if err := viper.Unmarshal(&vc); err != nil { + log.Fatal().Err(err).Msg("Failed to load config") + } + cfg, err := vc.Translate() + if err != nil { + log.Fatal().Err(err).Msg("Failed to load config") + } + + cfg.Path, _ = cmd.Flags().GetString("config") + exitCode, _ := cmd.Flags().GetInt("exit-code") + staged, _ := cmd.Flags().GetBool("staged") + start := time.Now() + + // Setup detector + detector := detect.NewDetector(cfg) + detector.Config.Path, err = cmd.Flags().GetString("config") + if err != nil { + log.Fatal().Err(err).Msg("") + } + source, err := cmd.Flags().GetString("source") + if err != nil { + log.Fatal().Err(err).Msg("") + } + // if config path is not set, then use the {source}/.gitleaks.toml path. + // note that there may not be a `{source}/.gitleaks.toml` file, this is ok. + if detector.Config.Path == "" { + detector.Config.Path = filepath.Join(source, ".gitleaks.toml") + } + // set verbose flag + if detector.Verbose, err = cmd.Flags().GetBool("verbose"); err != nil { + log.Fatal().Err(err).Msg("") + } + // set redact flag + if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil { + log.Fatal().Err(err).Msg("") + } + if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil { + log.Fatal().Err(err).Msg("") + } + // set color flag + if detector.NoColor, err = cmd.Flags().GetBool("no-color"); err != nil { + log.Fatal().Err(err).Msg("") + } + + if fileExists(filepath.Join(source, ".infisicalignore")) { + if err = detector.AddGitleaksIgnore(filepath.Join(source, ".infisicalignore")); err != nil { + log.Fatal().Err(err).Msg("could not call AddInfisicalIgnore") + } + } + + // get log options for git scan + logOpts, err := cmd.Flags().GetString("log-opts") + if err != nil { + log.Fatal().Err(err).Msg("") + } + + // start git scan + var findings []report.Finding + if staged { + findings, err = detector.DetectGit(source, logOpts, detect.ProtectStagedType) + } else { + findings, err = detector.DetectGit(source, logOpts, detect.ProtectType) + } + if err != nil { + // don't exit on error, just log it + log.Error().Err(err).Msg("") + } + + // log info about the scan + log.Info().Msgf("scan completed in %s", FormatDuration(time.Since(start))) + if len(findings) != 0 { + log.Warn().Msgf("leaks found: %d", len(findings)) + } else { + log.Info().Msg("no leaks found") + } + + reportPath, _ := cmd.Flags().GetString("report-path") + ext, _ := cmd.Flags().GetString("report-format") + if reportPath != "" { + if err = report.Write(findings, cfg, ext, reportPath); err != nil { + log.Fatal().Err(err).Msg("") + } + } + if len(findings) != 0 { + os.Exit(exitCode) + } +} diff --git a/cli/packages/cmd/secrets.go b/cli/packages/cmd/secrets.go index 69a4539a67..8256f36270 100644 --- a/cli/packages/cmd/secrets.go +++ b/cli/packages/cmd/secrets.go @@ -19,7 +19,7 @@ import ( "github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/visualize" "github.com/go-resty/resty/v2" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -28,7 +28,6 @@ var secretsCmd = &cobra.Command{ Short: "Used to create, read update and delete secrets", Use: "secrets", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { environmentName, _ := cmd.Flags().GetString("env") @@ -73,7 +72,6 @@ var secretsGetCmd = &cobra.Command{ Use: "get [secrets]", DisableFlagsInUseLine: true, Args: cobra.MinimumNArgs(1), - PreRun: toggleDebug, Run: getSecretsByNames, } @@ -83,7 +81,6 @@ var secretsGenerateExampleEnvCmd = &cobra.Command{ Use: "generate-example-env", DisableFlagsInUseLine: true, Args: cobra.NoArgs, - PreRun: toggleDebug, Run: generateExampleEnv, } @@ -92,7 +89,6 @@ var secretsSetCmd = &cobra.Command{ Short: "Used set secrets", Use: "set [secrets]", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { util.RequireLocalWorkspaceFile() @@ -134,7 +130,7 @@ var secretsSetCmd = &cobra.Command{ currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey) if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 { - log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey) + log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey) util.PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again") } @@ -278,7 +274,6 @@ var secretsDeleteCmd = &cobra.Command{ Short: "Used to delete secrets by name", Use: "delete [secrets]", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { environmentName, _ := cmd.Flags().GetString("env") diff --git a/cli/packages/cmd/vault.go b/cli/packages/cmd/vault.go index d9e32769e2..66c4f595b8 100644 --- a/cli/packages/cmd/vault.go +++ b/cli/packages/cmd/vault.go @@ -4,9 +4,11 @@ Copyright (c) 2023 Infisical Inc. package cmd import ( + "fmt" + "github.com/99designs/keyring" "github.com/Infisical/infisical-merge/packages/util" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -15,25 +17,24 @@ var vaultSetCmd = &cobra.Command{ Use: "set [vault-name]", Short: "Used to set the vault backend to store your login details securely at rest", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { wantedVaultTypeName := args[0] currentVaultBackend, err := util.GetCurrentVaultBackend() if err != nil { - log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) + log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) return } if wantedVaultTypeName == string(currentVaultBackend) { - log.Errorf("You are already on vault backend [%s]", currentVaultBackend) + log.Error().Msgf("You are already on vault backend [%s]", currentVaultBackend) return } if isVaultToSwitchToValid(wantedVaultTypeName) { configFile, err := util.GetConfigFile() if err != nil { - log.Errorf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) + log.Error().Msgf("Unable to set vault to [%s] because of [err=%s]", wantedVaultTypeName, err) return } @@ -42,13 +43,13 @@ var vaultSetCmd = &cobra.Command{ err = util.WriteConfigFile(&configFile) if err != nil { - log.Errorf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]", wantedVaultTypeName, err) + log.Error().Msgf("Unable to set vault to [%s] because an error occurred when saving the config file [err=%s]", wantedVaultTypeName, err) return } - log.Infof("Successfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName) + fmt.Printf("\nSuccessfully, switched vault backend from [%s] to [%s]. Please login in again to store your login details in the new vault with [infisical login]", currentVaultBackend, wantedVaultTypeName) } else { - log.Errorf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends()) + log.Error().Msgf("The requested vault type [%s] is not available on this system. Only the following vault backends are available for you system: %s", wantedVaultTypeName, keyring.AvailableBackends()) } }, } @@ -58,7 +59,6 @@ var vaultCmd = &cobra.Command{ Use: "vault", Short: "Used to manage where your Infisical login token is saved on your machine", DisableFlagsInUseLine: true, - PreRun: toggleDebug, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { printAvailableVaultBackends() @@ -66,17 +66,17 @@ var vaultCmd = &cobra.Command{ } func printAvailableVaultBackends() { - log.Infof("The following vaults are available on your system:") + fmt.Printf("The following vaults are available on your system:") for _, backend := range keyring.AvailableBackends() { - log.Infof("- %s", backend) + fmt.Printf("\n- %s", backend) } currentVaultBackend, err := util.GetCurrentVaultBackend() if err != nil { - log.Errorf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err) + log.Error().Msgf("printAvailableVaultBackends: unable to print the available vault backend because of error [err=%s]", err) } - log.Infof("\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend)) + fmt.Printf("\n\nYou are currently using [%s] vault to store your login credentials", string(currentVaultBackend)) } // Checks if the vault that the user wants to switch to is a valid available vault diff --git a/cli/packages/models/error.go b/cli/packages/models/error.go deleted file mode 100644 index f6d58cb5a2..0000000000 --- a/cli/packages/models/error.go +++ /dev/null @@ -1,17 +0,0 @@ -package models - -import log "github.com/sirupsen/logrus" - -// Custom error type so that we can give helpful messages in CLI -type Error struct { - Err error - FriendlyMessage string -} - -func (e *Error) printFriendlyMessage() { - log.Infoln(e.FriendlyMessage) -} - -func (e *Error) printDebuError() { - log.Debugln(e.Err) -} diff --git a/cli/packages/util/check-for-update.go b/cli/packages/util/check-for-update.go index dd50454c53..e59c043266 100644 --- a/cli/packages/util/check-for-update.go +++ b/cli/packages/util/check-for-update.go @@ -12,9 +12,8 @@ import ( "runtime" "strings" - log "github.com/sirupsen/logrus" - "github.com/fatih/color" + "github.com/rs/zerolog/log" ) func CheckForUpdate() { @@ -23,7 +22,7 @@ func CheckForUpdate() { } latestVersion, err := getLatestTag("Infisical", "infisical") if err != nil { - log.Debug(err) + log.Debug().Err(err) // do nothing and continue return } diff --git a/cli/packages/util/config.go b/cli/packages/util/config.go index 60b330c1ec..e6c6fddb4f 100644 --- a/cli/packages/util/config.go +++ b/cli/packages/util/config.go @@ -9,7 +9,7 @@ import ( "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func WriteInitalConfig(userCredentials *models.UserCredentials) error { @@ -73,7 +73,7 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error { func ConfigFileExists() bool { fullConfigFileURI, _, err := GetFullConfigFilePath() if err != nil { - log.Debugln("There was an error when creating the full path to config file", err) + log.Debug().Err(err).Msgf("There was an error when creating the full path to config file") return false } @@ -88,7 +88,7 @@ func WorkspaceConfigFileExistsInCurrentPath() bool { if _, err := os.Stat(INFISICAL_WORKSPACE_CONFIG_FILE_NAME); err == nil { return true } else { - log.Debugln(err) + log.Debug().Err(err) return false } } @@ -125,7 +125,7 @@ func FindWorkspaceConfigFile() (string, error) { _, err := os.Stat(path) if err == nil { // file found - log.Debugf("FindWorkspaceConfigFile: workspace file found at [path=%s]", path) + log.Debug().Msgf("FindWorkspaceConfigFile: workspace file found at [path=%s]", path) return path, nil } diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index 42a535b5af..a713abddbb 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -12,9 +12,8 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" - log "github.com/sirupsen/logrus" - "github.com/go-resty/resty/v2" + "github.com/rs/zerolog/log" ) func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) { @@ -97,7 +96,7 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work } if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 { - log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey) + log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey) PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again") } @@ -136,12 +135,12 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models if infisicalToken == "" { if isConnected { - log.Debug("GetAllEnvironmentVariables: Connected to internet, checking logged in creds") + log.Debug().Msg("GetAllEnvironmentVariables: Connected to internet, checking logged in creds") RequireLocalWorkspaceFile() RequireLogin() } - log.Debug("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details") + log.Debug().Msg("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details") loggedInUserDetails, err := GetCurrentLoggedInUserDetails() if err != nil { @@ -164,7 +163,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models } secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs) - log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn) + log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn) backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32] if errorToReturn == nil { @@ -182,7 +181,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models } } else { - log.Debug("Trying to fetch secrets using service token") + log.Debug().Msg("Trying to fetch secrets using service token") secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(infisicalToken) // if serviceTokenDetails.Environment != params.Environment { @@ -515,7 +514,7 @@ func DeleteBackupSecrets() error { func GetEnvFromWorkspaceFile() string { workspaceFile, err := GetWorkSpaceFromFile() if err != nil { - log.Debugf("getEnvFromWorkspaceFile: [err=%s]", err) + log.Debug().Msgf("getEnvFromWorkspaceFile: [err=%s]", err) return "" } @@ -529,17 +528,17 @@ func GetEnvFromWorkspaceFile() string { func GetEnvelopmentBasedOnGitBranch(workspaceFile models.WorkspaceConfigFile) string { branch, err := getCurrentBranch() if err != nil { - log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err) + log.Debug().Msgf("getEnvelopmentBasedOnGitBranch: [err=%s]", err) } envBasedOnGitBranch, ok := workspaceFile.GitBranchToEnvironmentMapping[branch] - log.Debugf("GetEnvelopmentBasedOnGitBranch: [envBasedOnGitBranch=%s] [ok=%t]", envBasedOnGitBranch, ok) + log.Debug().Msgf("GetEnvelopmentBasedOnGitBranch: [envBasedOnGitBranch=%s] [ok=%t]", envBasedOnGitBranch, ok) if err == nil && ok { return envBasedOnGitBranch } else { - log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err) + log.Debug().Msgf("getEnvelopmentBasedOnGitBranch: [err=%s]", err) return "" } } diff --git a/cli/packages/visualize/visualize.go b/cli/packages/visualize/visualize.go index 8599f24056..365cbeac41 100644 --- a/cli/packages/visualize/visualize.go +++ b/cli/packages/visualize/visualize.go @@ -8,7 +8,7 @@ import ( "github.com/mattn/go-isatty" "github.com/muesli/ansi" "github.com/muesli/reflow/truncate" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "golang.org/x/term" ) @@ -39,9 +39,9 @@ func Table(headers [3]string, rows [][3]string) { width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { if shouldTruncate { - log.Errorf("error getting terminal size: %s", err) + log.Error().Msgf("error getting terminal size: %s", err) } else { - log.Debug(err) + log.Debug().Err(err) } } diff --git a/cli/report/constants.go b/cli/report/constants.go new file mode 100644 index 0000000000..8bad495cb9 --- /dev/null +++ b/cli/report/constants.go @@ -0,0 +1,25 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package report + +const version = "v8.0.0" +const driver = "gitleaks" diff --git a/cli/report/csv.go b/cli/report/csv.go new file mode 100644 index 0000000000..0a30c9fd59 --- /dev/null +++ b/cli/report/csv.go @@ -0,0 +1,81 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "encoding/csv" + "io" + "strconv" +) + +// writeCsv writes the list of findings to a writeCloser. +func writeCsv(f []Finding, w io.WriteCloser) error { + if len(f) == 0 { + return nil + } + defer w.Close() + cw := csv.NewWriter(w) + err := cw.Write([]string{"RuleID", + "Commit", + "File", + "SymlinkFile", + "Secret", + "Match", + "StartLine", + "EndLine", + "StartColumn", + "EndColumn", + "Author", + "Message", + "Date", + "Email", + "Fingerprint", + }) + if err != nil { + return err + } + for _, f := range f { + err = cw.Write([]string{f.RuleID, + f.Commit, + f.File, + f.SymlinkFile, + f.Secret, + f.Match, + strconv.Itoa(f.StartLine), + strconv.Itoa(f.EndLine), + strconv.Itoa(f.StartColumn), + strconv.Itoa(f.EndColumn), + f.Author, + f.Message, + f.Date, + f.Email, + f.Fingerprint, + }) + if err != nil { + return err + } + } + + cw.Flush() + return cw.Error() +} diff --git a/cli/report/csv_test.go b/cli/report/csv_test.go new file mode 100644 index 0000000000..9670265197 --- /dev/null +++ b/cli/report/csv_test.go @@ -0,0 +1,108 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestWriteCSV(t *testing.T) { + tests := []struct { + findings []Finding + testReportName string + expected string + wantEmpty bool + }{ + { + testReportName: "simple", + expected: filepath.Join(expectPath, "report", "csv_simple.csv"), + findings: []Finding{ + { + RuleID: "test-rule", + Match: "line containing secret", + Secret: "a secret", + StartLine: 1, + EndLine: 2, + StartColumn: 1, + EndColumn: 2, + Message: "opps", + File: "auth.py", + SymlinkFile: "", + Commit: "0000000000000000", + Author: "John Doe", + Email: "johndoe@gmail.com", + Date: "10-19-2003", + Fingerprint: "fingerprint", + }, + }}, + { + + wantEmpty: true, + testReportName: "empty", + expected: filepath.Join(expectPath, "report", "this_should_not_exist.csv"), + findings: []Finding{}}, + } + + for _, test := range tests { + tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".csv")) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + err = writeCsv(test.findings, tmpfile) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + got, err := os.ReadFile(tmpfile.Name()) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + if test.wantEmpty { + if len(got) > 0 { + t.Errorf("Expected empty file, got %s", got) + } + os.Remove(tmpfile.Name()) + continue + } + want, err := os.ReadFile(test.expected) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + + if string(got) != string(want) { + err = os.WriteFile(strings.Replace(test.expected, ".csv", ".got.csv", 1), got, 0644) + if err != nil { + t.Error(err) + } + t.Errorf("got %s, want %s", string(got), string(want)) + } + + os.Remove(tmpfile.Name()) + } +} diff --git a/cli/report/finding.go b/cli/report/finding.go new file mode 100644 index 0000000000..be461072b6 --- /dev/null +++ b/cli/report/finding.go @@ -0,0 +1,72 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "strings" +) + +// Finding contains information about strings that +// have been captured by a tree-sitter query. +type Finding struct { + Description string + StartLine int + EndLine int + StartColumn int + EndColumn int + + Line string `json:"-"` + + Match string + + // Secret contains the full content of what is matched in + // the tree-sitter query. + Secret string + + // File is the name of the file containing the finding + File string + SymlinkFile string + Commit string + + // Entropy is the shannon entropy of Value + Entropy float32 + + Author string + Email string + Date string + Message string + Tags []string + + // Rule is the name of the rule that was matched + RuleID string + + // unique identifer + Fingerprint string +} + +// Redact removes sensitive information from a finding. +func (f *Finding) Redact() { + f.Line = strings.Replace(f.Line, f.Secret, "REDACTED", -1) + f.Match = strings.Replace(f.Match, f.Secret, "REDACTED", -1) + f.Secret = "REDACTED" +} diff --git a/cli/report/finding_test.go b/cli/report/finding_test.go new file mode 100644 index 0000000000..cdb74a3291 --- /dev/null +++ b/cli/report/finding_test.go @@ -0,0 +1,48 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +package report + +import "testing" + +func TestRedact(t *testing.T) { + tests := []struct { + findings []Finding + redact bool + }{ + { + redact: true, + findings: []Finding{ + { + Secret: "line containing secret", + Match: "secret", + }, + }}, + } + for _, test := range tests { + for _, f := range test.findings { + f.Redact() + if f.Secret != "REDACTED" { + t.Error("redact not redacting: ", f.Secret) + } + } + } +} diff --git a/cli/report/json.go b/cli/report/json.go new file mode 100644 index 0000000000..d091ac3c58 --- /dev/null +++ b/cli/report/json.go @@ -0,0 +1,37 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "encoding/json" + "io" +) + +func writeJson(findings []Finding, w io.WriteCloser) error { + if len(findings) == 0 { + findings = []Finding{} + } + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + return encoder.Encode(findings) +} diff --git a/cli/report/json_test.go b/cli/report/json_test.go new file mode 100644 index 0000000000..e81aaa8278 --- /dev/null +++ b/cli/report/json_test.go @@ -0,0 +1,111 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestWriteJSON(t *testing.T) { + tests := []struct { + findings []Finding + testReportName string + expected string + wantEmpty bool + }{ + { + testReportName: "simple", + expected: filepath.Join(expectPath, "report", "json_simple.json"), + findings: []Finding{ + { + + Description: "", + RuleID: "test-rule", + Match: "line containing secret", + Secret: "a secret", + StartLine: 1, + EndLine: 2, + StartColumn: 1, + EndColumn: 2, + Message: "opps", + File: "auth.py", + SymlinkFile: "", + Commit: "0000000000000000", + Author: "John Doe", + Email: "johndoe@gmail.com", + Date: "10-19-2003", + Tags: []string{}, + }, + }}, + { + + testReportName: "empty", + expected: filepath.Join(expectPath, "report", "empty.json"), + findings: []Finding{}}, + } + + for _, test := range tests { + // create tmp file using os.TempDir() + tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".json")) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + err = writeJson(test.findings, tmpfile) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + got, err := os.ReadFile(tmpfile.Name()) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + if test.wantEmpty { + if len(got) > 0 { + os.Remove(tmpfile.Name()) + t.Errorf("Expected empty file, got %s", got) + } + os.Remove(tmpfile.Name()) + continue + } + want, err := os.ReadFile(test.expected) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + + if string(got) != string(want) { + err = os.WriteFile(strings.Replace(test.expected, ".json", ".got.json", 1), got, 0644) + if err != nil { + t.Error(err) + } + t.Errorf("got %s, want %s", string(got), string(want)) + } + + os.Remove(tmpfile.Name()) + } +} diff --git a/cli/report/report.go b/cli/report/report.go new file mode 100644 index 0000000000..1191a4f331 --- /dev/null +++ b/cli/report/report.go @@ -0,0 +1,54 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "os" + "strings" + + "github.com/Infisical/infisical-merge/config" +) + +const ( + // https://cwe.mitre.org/data/definitions/798.html + CWE = "CWE-798" + CWE_DESCRIPTION = "Use of Hard-coded Credentials" +) + +func Write(findings []Finding, cfg config.Config, ext string, reportPath string) error { + file, err := os.Create(reportPath) + if err != nil { + return err + } + ext = strings.ToLower(ext) + switch ext { + case ".json", "json": + err = writeJson(findings, file) + case ".csv", "csv": + err = writeCsv(findings, file) + case ".sarif", "sarif": + err = writeSarif(cfg, findings, file) + } + + return err +} diff --git a/cli/report/report_test.go b/cli/report/report_test.go new file mode 100644 index 0000000000..ef38f19c26 --- /dev/null +++ b/cli/report/report_test.go @@ -0,0 +1,133 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "os" + "path/filepath" + "strconv" + "testing" + + "github.com/Infisical/infisical-merge/config" +) + +const ( + expectPath = "../testdata/expected/" + tmpPath = "../testdata/tmp" +) + +func TestReport(t *testing.T) { + tests := []struct { + findings []Finding + ext string + wantEmpty bool + }{ + { + ext: "json", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + }, + { + ext: ".json", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + }, + { + ext: ".jsonj", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + wantEmpty: true, + }, + { + ext: ".csv", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + }, + { + ext: "csv", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + }, + { + ext: "CSV", + findings: []Finding{ + { + RuleID: "test-rule", + }, + }, + }, + // { + // ext: "SARIF", + // findings: []Finding{ + // { + // RuleID: "test-rule", + // }, + // }, + // }, + } + + for i, test := range tests { + tmpfile, err := os.Create(filepath.Join(tmpPath, strconv.Itoa(i)+test.ext)) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + err = Write(test.findings, config.Config{}, test.ext, tmpfile.Name()) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + got, err := os.ReadFile(tmpfile.Name()) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + os.Remove(tmpfile.Name()) + + if len(got) == 0 && !test.wantEmpty { + t.Errorf("got empty file with extension " + test.ext) + } + + if test.wantEmpty { + if len(got) > 0 { + t.Errorf("Expected empty file, got %s", got) + } + continue + } + } +} diff --git a/cli/report/sarif.go b/cli/report/sarif.go new file mode 100644 index 0000000000..e5120887af --- /dev/null +++ b/cli/report/sarif.go @@ -0,0 +1,237 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/Infisical/infisical-merge/config" +) + +func writeSarif(cfg config.Config, findings []Finding, w io.WriteCloser) error { + sarif := Sarif{ + Schema: "https://json.schemastore.org/sarif-2.1.0.json", + Version: "2.1.0", + Runs: getRuns(cfg, findings), + } + + encoder := json.NewEncoder(w) + encoder.SetIndent("", " ") + return encoder.Encode(sarif) +} + +func getRuns(cfg config.Config, findings []Finding) []Runs { + return []Runs{ + { + Tool: getTool(cfg), + Results: getResults(findings), + }, + } +} + +func getTool(cfg config.Config) Tool { + tool := Tool{ + Driver: Driver{ + Name: driver, + SemanticVersion: version, + InformationUri: "https://github.com/Infisical/infisical", + Rules: getRules(cfg), + }, + } + + // if this tool has no rules, ensure that it is represented as [] instead of null/nil + if hasEmptyRules(tool) { + tool.Driver.Rules = make([]Rules, 0) + } + + return tool +} + +func hasEmptyRules(tool Tool) bool { + return len(tool.Driver.Rules) == 0 +} + +func getRules(cfg config.Config) []Rules { + // TODO for _, rule := range cfg.Rules { + var rules []Rules + for _, rule := range cfg.OrderedRules() { + shortDescription := ShortDescription{ + Text: rule.Description, + } + if rule.Regex != nil { + shortDescription = ShortDescription{ + Text: rule.Regex.String(), + } + } else if rule.Path != nil { + shortDescription = ShortDescription{ + Text: rule.Path.String(), + } + } + rules = append(rules, Rules{ + ID: rule.RuleID, + Name: rule.Description, + Description: shortDescription, + }) + } + return rules +} + +func messageText(f Finding) string { + if f.Commit == "" { + return fmt.Sprintf("%s has detected secret for file %s.", f.RuleID, f.File) + } + + return fmt.Sprintf("%s has detected secret for file %s at commit %s.", f.RuleID, f.File, f.Commit) + +} + +func getResults(findings []Finding) []Results { + results := []Results{} + for _, f := range findings { + r := Results{ + Message: Message{ + Text: messageText(f), + }, + RuleId: f.RuleID, + Locations: getLocation(f), + // This information goes in partial fingerprings until revision + // data can be added somewhere else + PartialFingerPrints: PartialFingerPrints{ + CommitSha: f.Commit, + Email: f.Email, + CommitMessage: f.Message, + Date: f.Date, + Author: f.Author, + }, + } + results = append(results, r) + } + return results +} + +func getLocation(f Finding) []Locations { + uri := f.File + if f.SymlinkFile != "" { + uri = f.SymlinkFile + } + return []Locations{ + { + PhysicalLocation: PhysicalLocation{ + ArtifactLocation: ArtifactLocation{ + URI: uri, + }, + Region: Region{ + StartLine: f.StartLine, + EndLine: f.EndLine, + StartColumn: f.StartColumn, + EndColumn: f.EndColumn, + Snippet: Snippet{ + Text: f.Secret, + }, + }, + }, + }, + } +} + +type PartialFingerPrints struct { + CommitSha string `json:"commitSha"` + Email string `json:"email"` + Author string `json:"author"` + Date string `json:"date"` + CommitMessage string `json:"commitMessage"` +} + +type Sarif struct { + Schema string `json:"$schema"` + Version string `json:"version"` + Runs []Runs `json:"runs"` +} + +type ShortDescription struct { + Text string `json:"text"` +} + +type FullDescription struct { + Text string `json:"text"` +} + +type Rules struct { + ID string `json:"id"` + Name string `json:"name"` + Description ShortDescription `json:"shortDescription"` +} + +type Driver struct { + Name string `json:"name"` + SemanticVersion string `json:"semanticVersion"` + InformationUri string `json:"informationUri"` + Rules []Rules `json:"rules"` +} + +type Tool struct { + Driver Driver `json:"driver"` +} + +type Message struct { + Text string `json:"text"` +} + +type ArtifactLocation struct { + URI string `json:"uri"` +} + +type Region struct { + StartLine int `json:"startLine"` + StartColumn int `json:"startColumn"` + EndLine int `json:"endLine"` + EndColumn int `json:"endColumn"` + Snippet Snippet `json:"snippet"` +} + +type Snippet struct { + Text string `json:"text"` +} + +type PhysicalLocation struct { + ArtifactLocation ArtifactLocation `json:"artifactLocation"` + Region Region `json:"region"` +} + +type Locations struct { + PhysicalLocation PhysicalLocation `json:"physicalLocation"` +} + +type Results struct { + Message Message `json:"message"` + RuleId string `json:"ruleId"` + Locations []Locations `json:"locations"` + PartialFingerPrints `json:"partialFingerprints"` +} + +type Runs struct { + Tool Tool `json:"tool"` + Results []Results `json:"results"` +} diff --git a/cli/report/sarif_test.go b/cli/report/sarif_test.go new file mode 100644 index 0000000000..18e444cffd --- /dev/null +++ b/cli/report/sarif_test.go @@ -0,0 +1,133 @@ +// MIT License + +// Copyright (c) 2019 Zachary Rice + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package report + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/Infisical/infisical-merge/config" + "github.com/spf13/viper" +) + +const configPath = "../testdata/config/" + +func TestWriteSarif(t *testing.T) { + tests := []struct { + findings []Finding + testReportName string + expected string + wantEmpty bool + cfgName string + }{ + { + cfgName: "simple", + testReportName: "simple", + expected: filepath.Join(expectPath, "report", "sarif_simple.sarif"), + findings: []Finding{ + { + + Description: "A test rule", + RuleID: "test-rule", + Match: "line containing secret", + Secret: "a secret", + StartLine: 1, + EndLine: 2, + StartColumn: 1, + EndColumn: 2, + Message: "opps", + File: "auth.py", + Commit: "0000000000000000", + Author: "John Doe", + Email: "johndoe@gmail.com", + Date: "10-19-2003", + Tags: []string{}, + }, + }}, + } + + for _, test := range tests { + // create tmp file using os.TempDir() + tmpfile, err := os.Create(filepath.Join(tmpPath, test.testReportName+".json")) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + viper.Reset() + viper.AddConfigPath(configPath) + viper.SetConfigName(test.cfgName) + viper.SetConfigType("toml") + err = viper.ReadInConfig() + if err != nil { + t.Error(err) + } + + var vc config.ViperConfig + err = viper.Unmarshal(&vc) + if err != nil { + t.Error(err) + } + + cfg, err := vc.Translate() + if err != nil { + t.Error(err) + } + err = writeSarif(cfg, test.findings, tmpfile) + fmt.Println(cfg) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + got, err := os.ReadFile(tmpfile.Name()) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + if test.wantEmpty { + if len(got) > 0 { + os.Remove(tmpfile.Name()) + t.Errorf("Expected empty file, got %s", got) + } + os.Remove(tmpfile.Name()) + continue + } + want, err := os.ReadFile(test.expected) + if err != nil { + os.Remove(tmpfile.Name()) + t.Error(err) + } + + if string(got) != string(want) { + err = os.WriteFile(strings.Replace(test.expected, ".sarif", ".got.sarif", 1), got, 0644) + if err != nil { + t.Error(err) + } + t.Errorf("got %s, want %s", string(got), string(want)) + } + + os.Remove(tmpfile.Name()) + } +}