From d1c7af1489ce92aa7f0ba1d4628f4ee6d4d5b564 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 17 Nov 2025 15:08:49 +0000 Subject: [PATCH 01/15] Added custom rules file yaml example to readme --- README.md | 63 ++++++++++++++++++++++ cmd/config.go | 10 ++++ cmd/testData/customRulesValid.json | 18 +++++++ engine/score/score.go | 87 +++++++++++++++--------------- 4 files changed, 133 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index bfbf6e93..fc4b5bd7 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,69 @@ docker run -v $(pwd)/.2ms.yml:/app/.2ms.yml checkmarx/2ms \ [![asciicast](https://asciinema.org/a/n8RHL4v6vI87uiUPZ9I7CgfYy.svg)](https://asciinema.org/a/n8RHL4v6vI87uiUPZ9I7CgfYy) +## Custom Rules File + +We support custom rules, which are user defined rules that can be passed via a custom rules file using the `--custom-rules-path` flag. The custom rules file format and extension can be YAML or JSON. + +Custom rules can be: + +- **Overrides** - if a rule present in the file shares the same ruleId as a default rule of 2ms, the rule present in the file will replace (override) the default rule in the scan. + - Note: If a rule is overridden, it will simply take all fields from the rule as defined in the file. You must include all fields that you want to be defined, otherwise they will be nil/empty. + +- **New rules** - if a rule does not share ruleId with a default rule, it will be appended to the list of rules used in the scan. + +Custom rules work properly with --rule and --ignore-rule flags. Rules can be selected/ignored by ruleId, ruleName and tag + +Regardless of being an override or new rule, a custom rule has the following required fields: +- ruleId - unique identifier of the rule +- ruleName - human readable name of the rule +- regex - regex pattern used to identify the secret + +Other fields are optional and can be seen in the example bellow of a file with a custom rule + +**YAML Example:** +```yaml +- ruleId: 01ab7659-d25a-4a1c-9f98-dee9d0cf2e70 # REQUIRED: unique id, must match default rule id to override that default rule. Rule ids be used as values in --rule and --ignore-rule flags + ruleName: Custom-Api-Key # REQUIRED: human-readable name. Rule names used as values in --rule and --ignore-rule flags + description: Custom rule + regex: (?i)\b\w*secret\w*\b\s*:?=\s*["']?([A-Za-z0-9/_+=-]{8,150})["']? # REQUIRED: golang regular expression used to find secrets. If capture group is present in regex, it used to find the secret, otherwise whole regex is used. which group is considered the secret can be defined with secretGroup + keywords: # 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. + - access + - api + entropy: 3.5 # shannon entropy, measures how random a string is. The value will be higher the more random a string is. Default rules that use entropy have values between 2.0 and 4.5. Leave empty to consider matches regardless of entropy + secretGroup: 1 # defines which capture group of regex match is considered the secret. Is also used as the group that will have its entropy checked if `entropy` is set. Can be left empty, in which case the first capture group to match will be considered the secret + path: (?i)\.(?:tf|hcl)$ # regex to limit the rule to specific file paths. For example, only .tf and .hcl files + severity: High # severity, can only be one of [Critical, High, Medium, Low, Info] + tags: # identifiers for the rule, tags defined here can be used as values of --rule and --ignore-rule flags + - api-key + scoreParameters: + category: General # category of the rule, should be a string of type ruledefine.RuleCategory. Impacts cvss score + ruleType: 4 # can go from 4 to 0, 4 being most severe. Impacts cvss score + allowLists: # allowed values to ignore if matched + - description: Allowlist for Custom Rule + matchCondition: OR # determines whether all criteria in the allowList must match. Can be AND or OR. Defaults to OR if not specified + regexTarget: match - # determines whether the regexes in allowList are tested against the rule.Regex match or the full line being scanned. Can be 'match' or 'line'. Defaults to 'match' if not specified + regexes: # allowed regex patterns + - (?i)(?:access(?:ibility|or)|access[_.-]?id|random[_.-]?access|api[_.-]?(?:id|name|version)|rapid|capital|[a-z0-9-]*?api[a-z0-9-]*?:jar:|author|X-MS-Exchange-Organization-Auth|Authentication-Results|(?:credentials?[_.-]?id|withCredentials)|(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}|(?:bucket|foreign|hot|idx|natural|primary|pub(?:lic)?|schema|sequence)[_.-]?key|(?:turkey)|key[_.-]?(?:alias|board|code|frame|id|length|mesh|name|pair|press(?:ed)?|ring|selector|signature|size|stone|storetype|word|up|down|left|right)|KeyVault(?:[A-Za-z]*?(?:Administrator|Reader|Contributor|Owner|Operator|User|Officer))\s*[:=]\s*['"]?[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}['"]?|key[_.-]?vault[_.-]?(?:id|name)|keyVaultToStoreSecrets|key(?:store|tab)[_.-]?(?:file|path)|issuerkeyhash|(?-i:[DdMm]onkey|[DM]ONKEY)|keying|(?:secret)[_.-]?(?:length|name|size)|UserSecretsId|(?:csrf)[_.-]?token|(?:io\.jsonwebtoken[ + \t]?:[ + \t]?[\w-]+)|(?:api|credentials|token)[_.-]?(?:endpoint|ur[il])|public[_.-]?token|(?:key|token)[_.-]?file|(?-i:(?:[A-Z_]+=\n[A-Z_]+=|[a-z_]+=\n[a-z_]+=)(?:\n|\z))|(?-i:(?:[A-Z.]+=\n[A-Z.]+=|[a-z.]+=\n[a-z.]+=)(?:\n|\z))) + stopWords: # stop words that if found in the secret, will discard the finding. Stop words are searched on the secret, which can be either the full regex match or the capture group if any is defined in the rule regex + - 000000, + - 6fe4476ee5a1832882e326b506d14126 + paths: # paths that can be ignored for this allowList + - \.bb$ + - \.bbappend$ + - \.bbclass$ + - \.inc$ + - matchCondition: AND + regexTarget: line + regexes: + - LICENSE[^=]*=\s*"[^"]+ + - LIC_FILES_CHKSUM[^=]*=\s*"[^"]+ + - SRC[^=]*=\s*"[a-zA-Z0-9]+ +``` + + ## Scan Commands The following sections describe the arguments used for scanning each of the supported platforms. diff --git a/cmd/config.go b/cmd/config.go index b23137c9..16dbe83a 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/checkmarx/2ms/v4/engine/rules/ruledefine" + "github.com/checkmarx/2ms/v4/engine/score" "github.com/checkmarx/2ms/v4/lib/utils" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -27,6 +28,7 @@ var ( errMissingRegex = fmt.Errorf("missing regex") errInvalidRegex = fmt.Errorf("invalid regex") errInvalidSeverity = fmt.Errorf("invalid severity") + errInvalidCategory = fmt.Errorf("invalid category") ) func processFlags(rootCmd *cobra.Command) error { @@ -203,6 +205,14 @@ func checkRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { err = errors.Join(err, buildCustomRuleError(i, rule, invalidSeverityError)) } } + + if rule.ScoreParameters.Category != "" { + // if exists in map + if _, ok := score.CategoryScoreMap[rule.ScoreParameters.Category]; !ok { + invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", errInvalidCategory, rule.ScoreParameters.Category) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidCategoryError)) + } + } } // Add a newline at start of error if it's not nil, for better presentation in output diff --git a/cmd/testData/customRulesValid.json b/cmd/testData/customRulesValid.json index 0fadfe6d..b08d91ea 100644 --- a/cmd/testData/customRulesValid.json +++ b/cmd/testData/customRulesValid.json @@ -47,5 +47,23 @@ }, "disableValidation": false, "deprecated": false + }, + { + "ruleId": "b47a1995-6572-41bb-b01d", + "ruleName": "mock-rule2", + "description": "Match API keys", + "regex": "(?i)\\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", + "keywords": ["glsa_"], + "entropy": 2.0, + "severity": "Medium", + "oldSeverity": "High", + "allowLists": [], + "tags": ["test-regexes"], + "scoreParameters": { + "category": "API", + "ruleType": 1 + }, + "disableValidation": false, + "deprecated": false } ] \ No newline at end of file diff --git a/engine/score/score.go b/engine/score/score.go index 36b7c9a8..b5cd3f50 100644 --- a/engine/score/score.go +++ b/engine/score/score.go @@ -10,6 +10,47 @@ import ( "github.com/zricethezav/gitleaks/v8/config" ) +var CategoryScoreMap = map[ruledefine.RuleCategory]uint8{ + ruledefine.CategoryAuthenticationAndAuthorization: 4, + ruledefine.CategoryCryptocurrencyExchange: 4, + ruledefine.CategoryFinancialServices: 4, + ruledefine.CategoryPaymentProcessing: 4, + ruledefine.CategorySecurity: 4, + ruledefine.CategoryAPIAccess: 3, + ruledefine.CategoryCICD: 3, + ruledefine.CategoryCloudPlatform: 3, + ruledefine.CategoryDatabaseAsAService: 3, + ruledefine.CategoryDevelopmentPlatform: 3, + ruledefine.CategoryEmailDeliveryService: 3, + ruledefine.CategoryGeneralOrUnknown: 3, + ruledefine.CategoryInfrastructureAsCode: 3, + ruledefine.CategoryPackageManagement: 3, + ruledefine.CategorySourceCodeManagement: 3, + ruledefine.CategoryWebHostingAndDeployment: 3, + ruledefine.CategoryBackgroundProcessingService: 2, + ruledefine.CategoryCDN: 2, + ruledefine.CategoryContentManagementSystem: 2, + ruledefine.CategoryCustomerSupport: 2, + ruledefine.CategoryDataAnalytics: 2, + ruledefine.CategoryFileStorageAndSharing: 2, + ruledefine.CategoryIoTPlatform: 2, + ruledefine.CategoryMappingAndLocationServices: 2, + ruledefine.CategoryNetworking: 2, + ruledefine.CategoryPhotoSharing: 2, + ruledefine.CategorySaaS: 2, + ruledefine.CategoryShipping: 2, + ruledefine.CategorySoftwareDevelopment: 2, + ruledefine.CategoryAIAndMachineLearning: 1, + ruledefine.CategoryApplicationMonitoring: 1, + ruledefine.CategoryECommercePlatform: 1, + ruledefine.CategoryMarketingAutomation: 1, + ruledefine.CategoryNewsAndMedia: 1, + ruledefine.CategoryOnlineSurveyPlatform: 1, + ruledefine.CategoryProjectManagement: 1, + ruledefine.CategorySearchService: 1, + ruledefine.CategorySocialMedia: 1, +} + type scorer struct { rulesBaseRiskScore map[string]float64 withValidation bool @@ -49,50 +90,6 @@ func (s *scorer) AssignScoreAndSeverity(secret *secrets.Secret) { secret.CvssScore = getCvssScore(s.rulesBaseRiskScore[secret.RuleID], validationStatus) } -func getCategoryScore(category ruledefine.RuleCategory) uint8 { - CategoryScore := map[ruledefine.RuleCategory]uint8{ - ruledefine.CategoryAuthenticationAndAuthorization: 4, - ruledefine.CategoryCryptocurrencyExchange: 4, - ruledefine.CategoryFinancialServices: 4, - ruledefine.CategoryPaymentProcessing: 4, - ruledefine.CategorySecurity: 4, - ruledefine.CategoryAPIAccess: 3, - ruledefine.CategoryCICD: 3, - ruledefine.CategoryCloudPlatform: 3, - ruledefine.CategoryDatabaseAsAService: 3, - ruledefine.CategoryDevelopmentPlatform: 3, - ruledefine.CategoryEmailDeliveryService: 3, - ruledefine.CategoryGeneralOrUnknown: 3, - ruledefine.CategoryInfrastructureAsCode: 3, - ruledefine.CategoryPackageManagement: 3, - ruledefine.CategorySourceCodeManagement: 3, - ruledefine.CategoryWebHostingAndDeployment: 3, - ruledefine.CategoryBackgroundProcessingService: 2, - ruledefine.CategoryCDN: 2, - ruledefine.CategoryContentManagementSystem: 2, - ruledefine.CategoryCustomerSupport: 2, - ruledefine.CategoryDataAnalytics: 2, - ruledefine.CategoryFileStorageAndSharing: 2, - ruledefine.CategoryIoTPlatform: 2, - ruledefine.CategoryMappingAndLocationServices: 2, - ruledefine.CategoryNetworking: 2, - ruledefine.CategoryPhotoSharing: 2, - ruledefine.CategorySaaS: 2, - ruledefine.CategoryShipping: 2, - ruledefine.CategorySoftwareDevelopment: 2, - ruledefine.CategoryAIAndMachineLearning: 1, - ruledefine.CategoryApplicationMonitoring: 1, - ruledefine.CategoryECommercePlatform: 1, - ruledefine.CategoryMarketingAutomation: 1, - ruledefine.CategoryNewsAndMedia: 1, - ruledefine.CategoryOnlineSurveyPlatform: 1, - ruledefine.CategoryProjectManagement: 1, - ruledefine.CategorySearchService: 1, - ruledefine.CategorySocialMedia: 1, - } - return CategoryScore[category] -} - func getValidityScore(baseRiskScore float64, validationStatus secrets.ValidationResult) float64 { switch validationStatus { case secrets.ValidResult: @@ -104,7 +101,7 @@ func getValidityScore(baseRiskScore float64, validationStatus secrets.Validation } func GetBaseRiskScore(category ruledefine.RuleCategory, ruleType uint8) float64 { - categoryScore := getCategoryScore(category) + categoryScore := CategoryScoreMap[category] return float64(categoryScore)*0.6 + float64(ruleType)*0.4 } From e82d80ba2a2dfe9158474cef4275ba332adbe7af Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:03:50 +0000 Subject: [PATCH 02/15] Added checks on score parameters --- cmd/main_test.go | 9 ++++- engine/rules/rules.go | 18 ++++++++++ engine/score/score.go | 84 ++++++++++++++++++++++--------------------- pkg/scan_test.go | 9 ++++- 4 files changed, 78 insertions(+), 42 deletions(-) diff --git a/cmd/main_test.go b/cmd/main_test.go index 32ea615b..1d625895 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -80,7 +80,7 @@ func TestPreRun(t *testing.T) { }, }, { - name: "errors on custom rules, regex and severity invalid", + name: "errors on custom rules, regex, severity and score parameters invalid", engineConfigVar: engine.EngineConfig{ CustomRules: []*ruledefine.Rule{ { @@ -89,6 +89,10 @@ func TestPreRun(t *testing.T) { Description: "Match passwords", Regex: "[A-Za-z0-9]{32})", Severity: "mockSeverity", + ScoreParameters: ruledefine.ScoreParameters{ + Category: "mockCategory", + RuleType: 10, + }, }, { RuleID: "b47a1995-6572-41bb-b01d-d215b43ab089", @@ -103,6 +107,9 @@ func TestPreRun(t *testing.T) { fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid regex"), fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid severity:" + " mockSeverity not one of ([Critical High Medium Low Info])"), + fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid category:" + + " mockCategory not an acceptable category of type RuleCategory"), + fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid rule type: 10 not an acceptable uint8 value, maximum is 4"), }, }, { diff --git a/engine/rules/rules.go b/engine/rules/rules.go index aa86df63..d422f22b 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -9,6 +9,7 @@ import ( "slices" "github.com/checkmarx/2ms/v4/engine/rules/ruledefine" + "github.com/checkmarx/2ms/v4/engine/score" "github.com/rs/zerolog/log" ) @@ -18,6 +19,8 @@ var ( errMissingRegex = fmt.Errorf("missing regex") errInvalidRegex = fmt.Errorf("invalid regex") errInvalidSeverity = fmt.Errorf("invalid severity") + errInvalidCategory = fmt.Errorf("invalid category") + errInvalidRuleType = fmt.Errorf("invalid rule type") ) func GetDefaultRules(includeDeprecated bool) []*ruledefine.Rule { //nolint:funlen // This function contains all rule definitions @@ -382,6 +385,21 @@ func CheckRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { err = errors.Join(err, buildCustomRuleError(i, rule, invalidSeverityError)) } } + + if rule.ScoreParameters.Category != "" { + if _, ok := score.CategoryScoreMap[rule.ScoreParameters.Category]; !ok { + invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", errInvalidCategory, rule.ScoreParameters.Category) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidCategoryError)) + } + } + + if rule.ScoreParameters.RuleType != 0 { + if rule.ScoreParameters.RuleType > score.RuleTypeMaxValue { + invalidRuleTypeError := fmt.Errorf("%w: %d not an acceptable uint8 value, maximum is %d", + errInvalidRuleType, rule.ScoreParameters.RuleType, score.RuleTypeMaxValue) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidRuleTypeError)) + } + } } // Add a newline at start of error if it's not nil, for better presentation in output diff --git a/engine/score/score.go b/engine/score/score.go index b5cd3f50..3b458fa4 100644 --- a/engine/score/score.go +++ b/engine/score/score.go @@ -10,46 +10,50 @@ import ( "github.com/zricethezav/gitleaks/v8/config" ) -var CategoryScoreMap = map[ruledefine.RuleCategory]uint8{ - ruledefine.CategoryAuthenticationAndAuthorization: 4, - ruledefine.CategoryCryptocurrencyExchange: 4, - ruledefine.CategoryFinancialServices: 4, - ruledefine.CategoryPaymentProcessing: 4, - ruledefine.CategorySecurity: 4, - ruledefine.CategoryAPIAccess: 3, - ruledefine.CategoryCICD: 3, - ruledefine.CategoryCloudPlatform: 3, - ruledefine.CategoryDatabaseAsAService: 3, - ruledefine.CategoryDevelopmentPlatform: 3, - ruledefine.CategoryEmailDeliveryService: 3, - ruledefine.CategoryGeneralOrUnknown: 3, - ruledefine.CategoryInfrastructureAsCode: 3, - ruledefine.CategoryPackageManagement: 3, - ruledefine.CategorySourceCodeManagement: 3, - ruledefine.CategoryWebHostingAndDeployment: 3, - ruledefine.CategoryBackgroundProcessingService: 2, - ruledefine.CategoryCDN: 2, - ruledefine.CategoryContentManagementSystem: 2, - ruledefine.CategoryCustomerSupport: 2, - ruledefine.CategoryDataAnalytics: 2, - ruledefine.CategoryFileStorageAndSharing: 2, - ruledefine.CategoryIoTPlatform: 2, - ruledefine.CategoryMappingAndLocationServices: 2, - ruledefine.CategoryNetworking: 2, - ruledefine.CategoryPhotoSharing: 2, - ruledefine.CategorySaaS: 2, - ruledefine.CategoryShipping: 2, - ruledefine.CategorySoftwareDevelopment: 2, - ruledefine.CategoryAIAndMachineLearning: 1, - ruledefine.CategoryApplicationMonitoring: 1, - ruledefine.CategoryECommercePlatform: 1, - ruledefine.CategoryMarketingAutomation: 1, - ruledefine.CategoryNewsAndMedia: 1, - ruledefine.CategoryOnlineSurveyPlatform: 1, - ruledefine.CategoryProjectManagement: 1, - ruledefine.CategorySearchService: 1, - ruledefine.CategorySocialMedia: 1, -} +var ( + CategoryScoreMap = map[ruledefine.RuleCategory]uint8{ + ruledefine.CategoryAuthenticationAndAuthorization: 4, + ruledefine.CategoryCryptocurrencyExchange: 4, + ruledefine.CategoryFinancialServices: 4, + ruledefine.CategoryPaymentProcessing: 4, + ruledefine.CategorySecurity: 4, + ruledefine.CategoryAPIAccess: 3, + ruledefine.CategoryCICD: 3, + ruledefine.CategoryCloudPlatform: 3, + ruledefine.CategoryDatabaseAsAService: 3, + ruledefine.CategoryDevelopmentPlatform: 3, + ruledefine.CategoryEmailDeliveryService: 3, + ruledefine.CategoryGeneralOrUnknown: 3, + ruledefine.CategoryInfrastructureAsCode: 3, + ruledefine.CategoryPackageManagement: 3, + ruledefine.CategorySourceCodeManagement: 3, + ruledefine.CategoryWebHostingAndDeployment: 3, + ruledefine.CategoryBackgroundProcessingService: 2, + ruledefine.CategoryCDN: 2, + ruledefine.CategoryContentManagementSystem: 2, + ruledefine.CategoryCustomerSupport: 2, + ruledefine.CategoryDataAnalytics: 2, + ruledefine.CategoryFileStorageAndSharing: 2, + ruledefine.CategoryIoTPlatform: 2, + ruledefine.CategoryMappingAndLocationServices: 2, + ruledefine.CategoryNetworking: 2, + ruledefine.CategoryPhotoSharing: 2, + ruledefine.CategorySaaS: 2, + ruledefine.CategoryShipping: 2, + ruledefine.CategorySoftwareDevelopment: 2, + ruledefine.CategoryAIAndMachineLearning: 1, + ruledefine.CategoryApplicationMonitoring: 1, + ruledefine.CategoryECommercePlatform: 1, + ruledefine.CategoryMarketingAutomation: 1, + ruledefine.CategoryNewsAndMedia: 1, + ruledefine.CategoryOnlineSurveyPlatform: 1, + ruledefine.CategoryProjectManagement: 1, + ruledefine.CategorySearchService: 1, + ruledefine.CategorySocialMedia: 1, + } + + RuleTypeMaxValue uint8 = 4 +) type scorer struct { rulesBaseRiskScore map[string]float64 diff --git a/pkg/scan_test.go b/pkg/scan_test.go index 24137b96..8955001b 100644 --- a/pkg/scan_test.go +++ b/pkg/scan_test.go @@ -578,7 +578,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { }, }, { - Name: "Regex and severity invalid", + Name: "Regex, severity and score parameters invalid", ScanConfig: resources.ScanConfig{ CustomRules: []*ruledefine.Rule{ { @@ -587,6 +587,10 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { Description: "Match passwords", Regex: "[A-Za-z0-9]{32})", Severity: "mockSeverity", + ScoreParameters: ruledefine.ScoreParameters{ + Category: "mockCategory", + RuleType: 10, + }, }, { RuleID: "b47a1995-6572-41bb-b01d-d215b43ab089", @@ -602,6 +606,9 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid regex"), fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid severity:" + " mockSeverity not one of ([Critical High Medium Low Info])"), + fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid category:" + + " mockCategory not an acceptable category of type RuleCategory"), + fmt.Errorf("rule#0;RuleID-db18ccf1-4fbf-49f6-aec1-939a2e5464c0: invalid rule type: 10 not an acceptable uint8 value, maximum is 4"), }, }, { From e7f20a6ca38f94b137fdccec1c3f952a9cd1d786 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:12:29 +0000 Subject: [PATCH 03/15] Removing custom rule used for tests. Fix linter issue --- cmd/testData/customRulesValid.json | 18 ------------------ engine/rules/rules.go | 3 ++- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/cmd/testData/customRulesValid.json b/cmd/testData/customRulesValid.json index b08d91ea..0fadfe6d 100644 --- a/cmd/testData/customRulesValid.json +++ b/cmd/testData/customRulesValid.json @@ -47,23 +47,5 @@ }, "disableValidation": false, "deprecated": false - }, - { - "ruleId": "b47a1995-6572-41bb-b01d", - "ruleName": "mock-rule2", - "description": "Match API keys", - "regex": "(?i)\\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:[\\x60'\"\\s;]|\\\\[nr]|$)", - "keywords": ["glsa_"], - "entropy": 2.0, - "severity": "Medium", - "oldSeverity": "High", - "allowLists": [], - "tags": ["test-regexes"], - "scoreParameters": { - "category": "API", - "ruleType": 1 - }, - "disableValidation": false, - "deprecated": false } ] \ No newline at end of file diff --git a/engine/rules/rules.go b/engine/rules/rules.go index d422f22b..3f5dd278 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -388,7 +388,8 @@ func CheckRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { if rule.ScoreParameters.Category != "" { if _, ok := score.CategoryScoreMap[rule.ScoreParameters.Category]; !ok { - invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", errInvalidCategory, rule.ScoreParameters.Category) + invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", + errInvalidCategory, rule.ScoreParameters.Category) err = errors.Join(err, buildCustomRuleError(i, rule, invalidCategoryError)) } } From b165848514f3385cc32a15f1b7f5b84291344497 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:14:54 +0000 Subject: [PATCH 04/15] Moved rule verification to engine to avoid import cycle --- engine/engine.go | 73 ++++++++++++++++++++++++++++++++++++++- engine/rules/rules.go | 79 ------------------------------------------- 2 files changed, 72 insertions(+), 80 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index ecc83d6f..b89711b9 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -6,6 +6,7 @@ import ( "crypto/hkdf" "crypto/sha256" "encoding/hex" + "errors" "fmt" "io" "os" @@ -45,6 +46,13 @@ var ( ErrNoRulesSelected = fmt.Errorf("no rules were selected") ErrFailedToCompileRegexRule = fmt.Errorf("failed to compile regex rule") + errMissingRuleID = fmt.Errorf("missing ruleID") + errMissingRuleName = fmt.Errorf("missing ruleName") + errMissingRegex = fmt.Errorf("missing regex") + errInvalidRegex = fmt.Errorf("invalid regex") + errInvalidSeverity = fmt.Errorf("invalid severity") + errInvalidCategory = fmt.Errorf("invalid category") + errInvalidRuleType = fmt.Errorf("invalid rule type") ) type DetectorConfig struct { @@ -149,7 +157,7 @@ func Init(engineConfig *EngineConfig, opts ...EngineOption) (IEngine, error) { } func initEngine(engineConfig *EngineConfig, opts ...EngineOption) (*Engine, error) { - err := rules.CheckRulesRequiredFields(engineConfig.CustomRules) + err := CheckRulesRequiredFields(engineConfig.CustomRules) if err != nil { return nil, fmt.Errorf("failed to load custom rules: %w", err) } @@ -827,3 +835,66 @@ func isSecretFromConfluenceResourceIdentifier(secretRuleID, secretLine, secretMa re := regexp.MustCompile(pat) return re.MatchString(secretLine) } + +// CheckRulesRequiredFields checks that required fields are present in the Rule. +// This is meant for user defined rules, default rules have more strict checks in unit tests +func CheckRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { + var err error + for i, rule := range rulesToCheck { + if rule.RuleID == "" { + err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleID)) + } + if rule.RuleName == "" { + err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleName)) + } + + if rule.Regex == "" { + err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRegex)) + } else { + if _, errRegex := regexp.Compile(rule.Regex); errRegex != nil { + invalidRegexError := fmt.Errorf("%w: %v", errInvalidRegex, errRegex) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidRegexError)) + } + } + + if rule.Severity != "" { + if !slices.Contains(ruledefine.SeverityOrder, rule.Severity) { + invalidSeverityError := fmt.Errorf("%w: %s not one of (%s)", errInvalidSeverity, rule.Severity, ruledefine.SeverityOrder) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidSeverityError)) + } + } + + if rule.ScoreParameters.Category != "" { + if _, ok := score.CategoryScoreMap[rule.ScoreParameters.Category]; !ok { + invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", + errInvalidCategory, rule.ScoreParameters.Category) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidCategoryError)) + } + } + + if rule.ScoreParameters.RuleType != 0 { + if rule.ScoreParameters.RuleType > score.RuleTypeMaxValue { + invalidRuleTypeError := fmt.Errorf("%w: %d not an acceptable uint8 value, maximum is %d", + errInvalidRuleType, rule.ScoreParameters.RuleType, score.RuleTypeMaxValue) + err = errors.Join(err, buildCustomRuleError(i, rule, invalidRuleTypeError)) + } + } + } + + // Add a newline at start of error if it's not nil, for better presentation in output + if err != nil { + err = fmt.Errorf("\n%w", err) + } + + return err +} + +func buildCustomRuleError(ruleIndex int, rule *ruledefine.Rule, issue error) error { + if rule.RuleID == "" { + if rule.RuleName == "" { + return fmt.Errorf("rule#%d: %w", ruleIndex, issue) + } + return fmt.Errorf("rule#%d;RuleName-%s: %w", ruleIndex, rule.RuleName, issue) + } + return fmt.Errorf("rule#%d;RuleID-%s: %w", ruleIndex, rule.RuleID, issue) +} diff --git a/engine/rules/rules.go b/engine/rules/rules.go index 3f5dd278..e8525bba 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -1,28 +1,12 @@ package rules import ( - "errors" - "fmt" "strings" - "regexp" - "slices" - "github.com/checkmarx/2ms/v4/engine/rules/ruledefine" - "github.com/checkmarx/2ms/v4/engine/score" "github.com/rs/zerolog/log" ) -var ( - errMissingRuleID = fmt.Errorf("missing ruleID") - errMissingRuleName = fmt.Errorf("missing ruleName") - errMissingRegex = fmt.Errorf("missing regex") - errInvalidRegex = fmt.Errorf("invalid regex") - errInvalidSeverity = fmt.Errorf("invalid severity") - errInvalidCategory = fmt.Errorf("invalid category") - errInvalidRuleType = fmt.Errorf("invalid rule type") -) - func GetDefaultRules(includeDeprecated bool) []*ruledefine.Rule { //nolint:funlen // This function contains all rule definitions allRules := []*ruledefine.Rule{ ruledefine.AdafruitAPIKey(), @@ -357,66 +341,3 @@ func addCustomRules(selectedRules, customRules []*ruledefine.Rule) []*ruledefine } return selectedRules } - -// CheckRulesRequiredFields checks that required fields are present in the Rule. -// This is meant for user defined rules, default rules have more strict checks in unit tests -func CheckRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { - var err error - for i, rule := range rulesToCheck { - if rule.RuleID == "" { - err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleID)) - } - if rule.RuleName == "" { - err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleName)) - } - - if rule.Regex == "" { - err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRegex)) - } else { - if _, errRegex := regexp.Compile(rule.Regex); errRegex != nil { - invalidRegexError := fmt.Errorf("%w: %v", errInvalidRegex, errRegex) - err = errors.Join(err, buildCustomRuleError(i, rule, invalidRegexError)) - } - } - - if rule.Severity != "" { - if !slices.Contains(ruledefine.SeverityOrder, rule.Severity) { - invalidSeverityError := fmt.Errorf("%w: %s not one of (%s)", errInvalidSeverity, rule.Severity, ruledefine.SeverityOrder) - err = errors.Join(err, buildCustomRuleError(i, rule, invalidSeverityError)) - } - } - - if rule.ScoreParameters.Category != "" { - if _, ok := score.CategoryScoreMap[rule.ScoreParameters.Category]; !ok { - invalidCategoryError := fmt.Errorf("%w: %s not an acceptable category of type RuleCategory", - errInvalidCategory, rule.ScoreParameters.Category) - err = errors.Join(err, buildCustomRuleError(i, rule, invalidCategoryError)) - } - } - - if rule.ScoreParameters.RuleType != 0 { - if rule.ScoreParameters.RuleType > score.RuleTypeMaxValue { - invalidRuleTypeError := fmt.Errorf("%w: %d not an acceptable uint8 value, maximum is %d", - errInvalidRuleType, rule.ScoreParameters.RuleType, score.RuleTypeMaxValue) - err = errors.Join(err, buildCustomRuleError(i, rule, invalidRuleTypeError)) - } - } - } - - // Add a newline at start of error if it's not nil, for better presentation in output - if err != nil { - err = fmt.Errorf("\n%w", err) - } - - return err -} - -func buildCustomRuleError(ruleIndex int, rule *ruledefine.Rule, issue error) error { - if rule.RuleID == "" { - if rule.RuleName == "" { - return fmt.Errorf("rule#%d: %w", ruleIndex, issue) - } - return fmt.Errorf("rule#%d;RuleName-%s: %w", ruleIndex, rule.RuleName, issue) - } - return fmt.Errorf("rule#%d;RuleID-%s: %w", ruleIndex, rule.RuleID, issue) -} From d4b5182a936bff7147f9ccf9a5cbba9967899c59 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:27:54 +0000 Subject: [PATCH 05/15] Refactoring for ruleName not being required --- cmd/main_test.go | 3 +- cmd/testData/customRulesValid.json | 6 +- engine/engine.go | 4 - engine/rules/ruledefine/rule.go | 4 + engine/rules/rules.go | 18 +++ engine/rules/rules_test.go | 212 +++++++++++++++++++---------- go.mod | 1 + go.sum | 2 + 8 files changed, 168 insertions(+), 82 deletions(-) diff --git a/cmd/main_test.go b/cmd/main_test.go index 1d625895..2e970848 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -58,7 +58,7 @@ func TestPreRun(t *testing.T) { expectedPreRunErr: nil, }, { - name: "errors on custom rules, rule name, id, regex missing", + name: "errors on custom rules, rule id and regex missing", engineConfigVar: engine.EngineConfig{ CustomRules: []*ruledefine.Rule{ { @@ -75,7 +75,6 @@ func TestPreRun(t *testing.T) { expectedPreRunErr: nil, expectedContainsInitErrs: []error{ fmt.Errorf("rule#0: missing ruleID"), - fmt.Errorf("rule#0: missing ruleName"), fmt.Errorf("rule#0: missing regex"), }, }, diff --git a/cmd/testData/customRulesValid.json b/cmd/testData/customRulesValid.json index 0fadfe6d..86750bb2 100644 --- a/cmd/testData/customRulesValid.json +++ b/cmd/testData/customRulesValid.json @@ -22,7 +22,7 @@ ], "tags": ["security", "credentials"], "scoreParameters": { - "category": "Secrets", + "category": "General", "ruleType": 2 }, "disableValidation": true, @@ -41,10 +41,6 @@ "oldSeverity": "High", "allowLists": [], "tags": ["api", "custom"], - "scoreParameters": { - "category": "API", - "ruleType": 1 - }, "disableValidation": false, "deprecated": false } diff --git a/engine/engine.go b/engine/engine.go index b89711b9..6b62c2a4 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -47,7 +47,6 @@ var ( ErrNoRulesSelected = fmt.Errorf("no rules were selected") ErrFailedToCompileRegexRule = fmt.Errorf("failed to compile regex rule") errMissingRuleID = fmt.Errorf("missing ruleID") - errMissingRuleName = fmt.Errorf("missing ruleName") errMissingRegex = fmt.Errorf("missing regex") errInvalidRegex = fmt.Errorf("invalid regex") errInvalidSeverity = fmt.Errorf("invalid severity") @@ -844,9 +843,6 @@ func CheckRulesRequiredFields(rulesToCheck []*ruledefine.Rule) error { if rule.RuleID == "" { err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleID)) } - if rule.RuleName == "" { - err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRuleName)) - } if rule.Regex == "" { err = errors.Join(err, buildCustomRuleError(i, rule, errMissingRegex)) diff --git a/engine/rules/ruledefine/rule.go b/engine/rules/ruledefine/rule.go index cf24f1c9..f37a8bb1 100644 --- a/engine/rules/ruledefine/rule.go +++ b/engine/rules/ruledefine/rule.go @@ -107,3 +107,7 @@ type AllowList struct { // For patterns that are allowed to be ignored Regexes []string `json:"regexes" yaml:"regexes"` StopWords []string `json:"stopWords" yaml:"stopWords"` // stop words that are allowed to be ignored } + +func (r Rule) CreateRuleNameFromRuleID() { + r.RuleName = r.RuleID +} diff --git a/engine/rules/rules.go b/engine/rules/rules.go index e8525bba..2d5281e1 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -330,14 +330,32 @@ func addCustomRules(selectedRules, customRules []*ruledefine.Rule) []*ruledefine ruleMatch := false for i := range selectedRules { if selectedRules[i].RuleID == customRule.RuleID { + completeOverrideEmptyFields(selectedRules[i], customRule) selectedRules[i] = customRule ruleMatch = true break } } if !ruleMatch { + if customRule.RuleName == "" { + customRule.CreateRuleNameFromRuleID() + } selectedRules = append(selectedRules, customRule) } } return selectedRules } + +func completeOverrideEmptyFields(rule *ruledefine.Rule, overrideRule *ruledefine.Rule) { + if overrideRule.RuleName == "" { + overrideRule.RuleName = rule.RuleName + } + + if overrideRule.ScoreParameters.Category == "" { + overrideRule.ScoreParameters.Category = rule.ScoreParameters.Category + // only replace with default ruleType if category wasn't defined, otherwise assume user set RuleType at 0 intentionally + if overrideRule.ScoreParameters.RuleType == 0 { + overrideRule.ScoreParameters.RuleType = rule.ScoreParameters.RuleType + } + } +} diff --git a/engine/rules/rules_test.go b/engine/rules/rules_test.go index be97f727..f0086103 100644 --- a/engine/rules/rules_test.go +++ b/engine/rules/rules_test.go @@ -6,6 +6,7 @@ import ( "github.com/checkmarx/2ms/v4/engine/rules/ruledefine" "github.com/google/uuid" + "github.com/jinzhu/copier" "github.com/stretchr/testify/assert" "github.com/zricethezav/gitleaks/v8/config" "github.com/zricethezav/gitleaks/v8/regexp" @@ -161,15 +162,35 @@ func Test_CustomRules(t *testing.T) { genericCredentialOverride := &ruledefine.Rule{ RuleID: ruledefine.GenericCredential().RuleID, - RuleName: ruledefine.GenericCredential().RuleID, Description: "Custom rule for Generic API Key", Regex: "[A-Za-z0-9]{32}", Tags: []string{"custom"}, } + // expected rule should have rule name and score parameters with values, copied from default rule into override + expectedGenericCredentialOverride := &ruledefine.Rule{ + RuleID: ruledefine.GenericCredential().RuleID, + RuleName: ruledefine.GenericCredential().RuleName, + Description: "Custom rule for Generic API Key", + Regex: "[A-Za-z0-9]{32}", + Tags: []string{"custom"}, + ScoreParameters: ruledefine.GenericCredential().ScoreParameters, + } + + completeGenericCredentialOverride := &ruledefine.Rule{ + RuleID: ruledefine.GenericCredential().RuleID, + RuleName: "Custom Generic API Key", + Description: "Custom rule for Generic API Key", + Regex: "[A-Za-z0-9]{32}", + Tags: []string{"custom"}, + ScoreParameters: ruledefine.ScoreParameters{ + Category: ruledefine.CategorySaaS, + RuleType: 1, + }, + } deprecatedGenericCredentialOverride := &ruledefine.Rule{ RuleID: ruledefine.GenericCredential().RuleID, - RuleName: ruledefine.GenericCredential().RuleID, + RuleName: ruledefine.GenericCredential().RuleName, Description: "Custom rule for Generic API Key", Regex: "[A-Za-z0-9]{40}", // this regex is different to be able to distinguish with non deprecated rule Tags: []string{"custom"}, @@ -202,7 +223,6 @@ func Test_CustomRules(t *testing.T) { ignoreList []string specialList []string customRules []*ruledefine.Rule - onlyCustomRules bool expectedPresentRules []*ruledefine.Rule expectedMissingRules []*ruledefine.Rule expectedLen int @@ -212,11 +232,11 @@ func Test_CustomRules(t *testing.T) { selectedList: []string{}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule)}, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -228,11 +248,27 @@ func Test_CustomRules(t *testing.T) { selectedList: []string{genericCredentialOverride.RuleID}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, + expectedPresentRules: []*ruledefine.Rule{ + cloneRule(expectedGenericCredentialOverride), + }, + expectedMissingRules: []*ruledefine.Rule{ + ruledefine.GenericCredential(), + }, + expectedLen: 1, + }, + { + name: "select only complete custom generic-api-key with ruleID", + selectedList: []string{completeGenericCredentialOverride.RuleID}, + ignoreList: []string{}, + customRules: []*ruledefine.Rule{ + cloneRule(completeGenericCredentialOverride), + cloneRule(customRule), }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, + cloneRule(completeGenericCredentialOverride), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -240,15 +276,31 @@ func Test_CustomRules(t *testing.T) { expectedLen: 1, }, { - name: "select only custom generic-api-key with ruleName", - selectedList: []string{genericCredentialOverride.RuleName}, + name: "select only custom generic-api-key with ruleName, even if missing ruleName in custom rule definition", + selectedList: []string{ruledefine.GenericCredential().RuleName}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, + cloneRule(expectedGenericCredentialOverride), + }, + expectedMissingRules: []*ruledefine.Rule{ + ruledefine.GenericCredential(), + }, + expectedLen: 1, + }, + { + name: "select only complete custom generic-api-key with ruleName", + selectedList: []string{expectedGenericCredentialOverride.RuleName}, + ignoreList: []string{}, + customRules: []*ruledefine.Rule{ + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), + }, + expectedPresentRules: []*ruledefine.Rule{ + cloneRule(expectedGenericCredentialOverride), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -257,13 +309,14 @@ func Test_CustomRules(t *testing.T) { }, { name: "select only custom generic-api-key with ruleName in lowercase", - selectedList: []string{strings.ToLower(genericCredentialOverride.RuleName)}, + selectedList: []string{strings.ToLower(ruledefine.GenericCredential().RuleName)}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, + cloneRule(expectedGenericCredentialOverride), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -275,10 +328,11 @@ func Test_CustomRules(t *testing.T) { selectedList: []string{genericCredentialOverride.Tags[0]}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, + cloneRule(expectedGenericCredentialOverride), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -293,11 +347,11 @@ func Test_CustomRules(t *testing.T) { }, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule)}, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -312,11 +366,12 @@ func Test_CustomRules(t *testing.T) { }, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -332,12 +387,13 @@ func Test_CustomRules(t *testing.T) { }, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, expectedPresentRules: []*ruledefine.Rule{ ruledefine.CurlBasicAuth(), - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -353,12 +409,13 @@ func Test_CustomRules(t *testing.T) { }, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule}, + cloneRule(genericCredentialOverride), + cloneRule(customRule), + }, expectedPresentRules: []*ruledefine.Rule{ ruledefine.CurlBasicAuth(), - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -370,10 +427,11 @@ func Test_CustomRules(t *testing.T) { selectedList: []string{genericCredentialOverride.RuleID}, ignoreList: []string{}, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - deprecatedGenericCredentialOverride}, + cloneRule(genericCredentialOverride), + cloneRule(deprecatedGenericCredentialOverride), + }, expectedPresentRules: []*ruledefine.Rule{ - genericCredentialOverride, + cloneRule(expectedGenericCredentialOverride), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), @@ -388,13 +446,13 @@ func Test_CustomRules(t *testing.T) { customRule.RuleID, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, - customRule, + cloneRule(deprecatedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: rulesCount - 1, }, @@ -405,15 +463,16 @@ func Test_CustomRules(t *testing.T) { genericCredentialOverride.RuleID, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedPresentRules: []*ruledefine.Rule{ - customRule, + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), }, expectedLen: rulesCount, }, @@ -428,13 +487,14 @@ func Test_CustomRules(t *testing.T) { customRule.RuleID, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: 0, }, @@ -449,13 +509,14 @@ func Test_CustomRules(t *testing.T) { customRule.RuleName, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: 0, }, @@ -470,13 +531,14 @@ func Test_CustomRules(t *testing.T) { customRule.RuleName, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: 0, }, @@ -491,15 +553,16 @@ func Test_CustomRules(t *testing.T) { customRule.Tags[0], }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, expectedPresentRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), }, expectedMissingRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: 1, }, @@ -514,8 +577,8 @@ func Test_CustomRules(t *testing.T) { customRule.RuleID, }, customRules: []*ruledefine.Rule{ - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(customRule), }, specialList: []string{ ruledefine.HardcodedPassword().RuleName, @@ -525,8 +588,9 @@ func Test_CustomRules(t *testing.T) { }, expectedMissingRules: []*ruledefine.Rule{ ruledefine.GenericCredential(), - genericCredentialOverride, - customRule, + cloneRule(genericCredentialOverride), + cloneRule(expectedGenericCredentialOverride), + cloneRule(customRule), }, expectedLen: 1, }, @@ -538,11 +602,11 @@ func Test_CustomRules(t *testing.T) { assert.Equal(t, tt.expectedLen, len(rules)) for _, expectedRule := range tt.expectedPresentRules { - assert.Contains(t, rules, expectedRule, "can't find expected rule") + assert.Contains(t, rules, expectedRule, "can't find expected rule %s", expectedRule.RuleID) } for _, expectedMissingRule := range tt.expectedMissingRules { - assert.NotContains(t, rules, expectedMissingRule, "found unexpected rule") + assert.NotContains(t, rules, expectedMissingRule, "found unexpected rule %s", expectedMissingRule.RuleID) } }) } @@ -725,3 +789,9 @@ func TestIgnoreRules(t *testing.T) { }) } } + +func cloneRule(r *ruledefine.Rule) *ruledefine.Rule { + var ruleCopy ruledefine.Rule + _ = copier.CopyWithOption(&ruleCopy, r, copier.Option{DeepCopy: true}) + return &ruleCopy +} diff --git a/go.mod b/go.mod index 863723de..302bce37 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/google/uuid v1.6.0 github.com/h2non/filetype v1.1.3 github.com/iancoleman/strcase v0.3.0 + github.com/jinzhu/copier v0.4.0 github.com/rs/zerolog v1.33.0 github.com/shirou/gopsutil v3.21.11+incompatible github.com/slack-go/slack v0.12.2 diff --git a/go.sum b/go.sum index 9a49d5af..953366ee 100644 --- a/go.sum +++ b/go.sum @@ -1014,6 +1014,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= 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/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= From bc05a600403c0c777eb441243bca8d2b091ee50c Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:05:28 +0000 Subject: [PATCH 06/15] Updated tests --- engine/rules/rules.go | 33 ++++++++---- pkg/scan_test.go | 51 +++++++++++-------- .../defaultPlusAllCustomRules.json | 18 +++---- .../customRules/onlyCustomRules.json | 18 +++---- .../customRules/onlyOverrideRules.json | 22 ++++---- tests/e2e_test.go | 2 +- .../customRuleConfig/customRules.json | 8 +-- .../customRuleConfig/customRules.yaml | 6 +-- .../default_plus_all_custom_rules.json | 6 +-- .../default_plus_non_override_rules.json | 12 ++--- .../customRules/only_custom_rules.json | 6 +-- .../customRules/only_override_rules.json | 10 ++-- 12 files changed, 108 insertions(+), 84 deletions(-) diff --git a/engine/rules/rules.go b/engine/rules/rules.go index 2d5281e1..32cb4661 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -295,6 +295,10 @@ func FilterRules(selectedList, ignoreList, specialList []string, selectedRules = GetDefaultRules(false) + // Fill in missing fields in custom rules if they match default rules. + // Needs to run before selection/ignoring so that if rule names are used in selected/ignored, overrides will be selected/ignored properly + completeOverridesWithDefaultFields(customRules, selectedRules) + if len(selectedList) > 0 { selectedRules = selectRules(selectedRules, selectedList) customRules = selectRules(customRules, selectedList) @@ -304,6 +308,8 @@ func FilterRules(selectedList, ignoreList, specialList []string, customRules = ignoreRules(customRules, ignoreList) } + // Adding custom rules needs to happen after selection/ignoring so that if tags of overrides are used in ignore, + // the default Rule can still run (if not ignored itself) selectedRules = addCustomRules(selectedRules, customRules) if len(specialList) > 0 { @@ -330,7 +336,6 @@ func addCustomRules(selectedRules, customRules []*ruledefine.Rule) []*ruledefine ruleMatch := false for i := range selectedRules { if selectedRules[i].RuleID == customRule.RuleID { - completeOverrideEmptyFields(selectedRules[i], customRule) selectedRules[i] = customRule ruleMatch = true break @@ -346,16 +351,24 @@ func addCustomRules(selectedRules, customRules []*ruledefine.Rule) []*ruledefine return selectedRules } -func completeOverrideEmptyFields(rule *ruledefine.Rule, overrideRule *ruledefine.Rule) { - if overrideRule.RuleName == "" { - overrideRule.RuleName = rule.RuleName - } +// completeOverridesWithDefaultFields fills in some missing fields in custom rules if they match default rules by ruleID +func completeOverridesWithDefaultFields(customRules []*ruledefine.Rule, defaultRules []*ruledefine.Rule) { + for _, customRule := range customRules { + for _, defaultRule := range defaultRules { + if defaultRule.RuleID == customRule.RuleID { - if overrideRule.ScoreParameters.Category == "" { - overrideRule.ScoreParameters.Category = rule.ScoreParameters.Category - // only replace with default ruleType if category wasn't defined, otherwise assume user set RuleType at 0 intentionally - if overrideRule.ScoreParameters.RuleType == 0 { - overrideRule.ScoreParameters.RuleType = rule.ScoreParameters.RuleType + if customRule.RuleName == "" { + customRule.RuleName = defaultRule.RuleName + } + + if customRule.ScoreParameters.Category == "" { + customRule.ScoreParameters.Category = defaultRule.ScoreParameters.Category + // only replace with default ruleType if category wasn't defined, otherwise assume user set RuleType at 0 intentionally + if customRule.ScoreParameters.RuleType == 0 { + customRule.ScoreParameters.RuleType = defaultRule.ScoreParameters.RuleType + } + } + } } } } diff --git a/pkg/scan_test.go b/pkg/scan_test.go index 8955001b..a0c46ac5 100644 --- a/pkg/scan_test.go +++ b/pkg/scan_test.go @@ -15,6 +15,7 @@ import ( "github.com/checkmarx/2ms/v4/lib/secrets" "github.com/checkmarx/2ms/v4/lib/utils" "github.com/checkmarx/2ms/v4/plugins" + "github.com/jinzhu/copier" "github.com/stretchr/testify/assert" ) @@ -40,16 +41,12 @@ var updateExpected = flag.Bool("update-test-data", false, "Update expected test // Rules to be used to test custom rules. Rules will be selected and ignored depending on the test case var customRules = []*ruledefine.Rule{ { + // this rule is missing name and score parameters to test if they are correctly inherited from the default rule RuleID: "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - RuleName: "Generic-Api-Key-Custom", Description: "Custom Generic Api Key override, should override the default one (very specific regex just for testing purposes)", Regex: `(?i)\b\w*secret\w*\b\s*:?=\s*["']?([A-Za-z0-9/_+=-]{8,150})["']?`, Severity: "Medium", Tags: []string{"custom", "override"}, - ScoreParameters: ruledefine.ScoreParameters{ - Category: "General", - RuleType: 4, - }, }, { RuleID: "b47a1995-6572-41bb-b01d-d215b43ab089", @@ -86,15 +83,15 @@ var customRules = []*ruledefine.Rule{ }, { RuleID: "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - RuleName: "Github-Pat", + RuleName: "Github-Pat-Custom", // different from default to test if override works Description: "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", Regex: `ghp_[0-9a-zA-Z]{36}`, Severity: "Low", Tags: []string{"custom", "override"}, DisableValidation: true, ScoreParameters: ruledefine.ScoreParameters{ - Category: "Development Platform", - RuleType: 4, + Category: "Package Management", // different from default to test if override works + RuleType: 3, // different from default to test if override works }, }, } @@ -445,7 +442,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run all default + custom rules", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, }, ScanItems: scanItems, @@ -455,7 +452,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom rules", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"custom"}, }, @@ -466,7 +463,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom override rules", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"override"}, }, @@ -477,7 +474,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run default + non override rules", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, IgnoreRules: []string{"override"}, }, @@ -488,7 +485,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom rules and ignore overrides", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"custom"}, IgnoreRules: []string{"override"}, @@ -500,7 +497,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only default rules by ignoring custom rules", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, IgnoreRules: []string{"custom"}, }, @@ -511,7 +508,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom rules by ignoring custom rules by id", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"custom"}, IgnoreRules: []string{ @@ -526,12 +523,12 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom rules by ignoring custom rules by name", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"custom"}, IgnoreRules: []string{ - "Generic-Api-Key-Custom", - "Github-Pat", + "Generic-Api-Key", + "Github-Pat-Custom", }, }, ScanItems: scanItems, @@ -541,7 +538,7 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { { Name: "Run only custom rules by ignoring override result Ids", ScanConfig: resources.ScanConfig{ - CustomRules: customRules, + CustomRules: cloneRules(customRules), WithValidation: true, SelectRules: []string{"custom"}, IgnoreResultIds: []string{ @@ -573,7 +570,6 @@ func TestScanAndScanDynamicWithCustomRules(t *testing.T) { ExpectedReportPath: "", expectErrors: []error{ fmt.Errorf("rule#0: missing ruleID"), - fmt.Errorf("rule#0: missing ruleName"), fmt.Errorf("rule#0: missing regex"), }, }, @@ -1041,3 +1037,18 @@ func compareOrUpdateTestData(t *testing.T, actualReport reporting.IReport, expec assert.EqualValues(t, normalizedExpectedReport, normalizedActualReport) } + +func cloneRules(rulesToClone []*ruledefine.Rule) []*ruledefine.Rule { + clonedRules := make([]*ruledefine.Rule, 0, len(rulesToClone)) + for _, rule := range rulesToClone { + clonedRule := cloneRule(rule) + clonedRules = append(clonedRules, clonedRule) + } + return clonedRules +} + +func cloneRule(r *ruledefine.Rule) *ruledefine.Rule { + var ruleCopy ruledefine.Rule + _ = copier.CopyWithOption(&ruleCopy, r, copier.Option{DeepCopy: true}) + return &ruleCopy +} diff --git a/pkg/testData/expectedReports/customRules/defaultPlusAllCustomRules.json b/pkg/testData/expectedReports/customRules/defaultPlusAllCustomRules.json index 8f448995..bfc4cc6a 100644 --- a/pkg/testData/expectedReports/customRules/defaultPlusAllCustomRules.json +++ b/pkg/testData/expectedReports/customRules/defaultPlusAllCustomRules.json @@ -45,8 +45,8 @@ "id": "63139b45c38f502bbbe15115a7995003d76b2a81", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -56,7 +56,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "9346cf95d4513bb2861d1ecfc3e82421bd14cbd8": [ @@ -89,8 +89,8 @@ "id": "993b789425c810d4956c5ed8c84f02f90b0531ee", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": " Text_Example = ghp_9876543210zyxwvutsrqponmlkjihgfedcba", @@ -100,7 +100,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "ab72977b91e213ab4d91eaa2dc89e0b5d54856d5": [ @@ -127,8 +127,8 @@ "id": "c31705d99e835e4ac7bc3f688bd9558309e056ed", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -138,7 +138,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "efc9a9ee89f1d732c7321067eb701b9656e91f15": [ diff --git a/pkg/testData/expectedReports/customRules/onlyCustomRules.json b/pkg/testData/expectedReports/customRules/onlyCustomRules.json index 33d820f6..ee158f7b 100644 --- a/pkg/testData/expectedReports/customRules/onlyCustomRules.json +++ b/pkg/testData/expectedReports/customRules/onlyCustomRules.json @@ -45,8 +45,8 @@ "id": "63139b45c38f502bbbe15115a7995003d76b2a81", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -56,7 +56,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "993b789425c810d4956c5ed8c84f02f90b0531ee": [ @@ -64,8 +64,8 @@ "id": "993b789425c810d4956c5ed8c84f02f90b0531ee", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": " Text_Example = ghp_9876543210zyxwvutsrqponmlkjihgfedcba", @@ -75,7 +75,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "ab72977b91e213ab4d91eaa2dc89e0b5d54856d5": [ @@ -102,8 +102,8 @@ "id": "c31705d99e835e4ac7bc3f688bd9558309e056ed", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -113,7 +113,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ] } diff --git a/pkg/testData/expectedReports/customRules/onlyOverrideRules.json b/pkg/testData/expectedReports/customRules/onlyOverrideRules.json index f469d9b8..55576912 100644 --- a/pkg/testData/expectedReports/customRules/onlyOverrideRules.json +++ b/pkg/testData/expectedReports/customRules/onlyOverrideRules.json @@ -7,8 +7,8 @@ "id": "63139b45c38f502bbbe15115a7995003d76b2a81", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -18,7 +18,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "7035eccf9686b12491a9dceae28f3bdf2dbdd9e6": [ @@ -26,7 +26,7 @@ "id": "7035eccf9686b12491a9dceae28f3bdf2dbdd9e6", "source": "testData/secrets/generic-api-keys.txt", "ruleId": "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - "ruleName": "Generic-Api-Key-Custom", + "ruleName": "Generic-Api-Key", "ruleCategory": "General", "startLine": 0, "endLine": 0, @@ -45,8 +45,8 @@ "id": "993b789425c810d4956c5ed8c84f02f90b0531ee", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": " Text_Example = ghp_9876543210zyxwvutsrqponmlkjihgfedcba", @@ -56,7 +56,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "c31705d99e835e4ac7bc3f688bd9558309e056ed": [ @@ -64,8 +64,8 @@ "id": "c31705d99e835e4ac7bc3f688bd9558309e056ed", "source": "testData/secrets/github-pat.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 0, "endLine": 0, "lineContent": "TextExampleghp_1234567890abcdefghijklmnopqrstuvwxyzTextExampleghp_abcdefghijklmnopqrstuvwxyz1234567890TextExample", @@ -75,7 +75,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "f8658be0ab4dfb26464e388a21fa915e14f59da6": [ @@ -83,7 +83,7 @@ "id": "f8658be0ab4dfb26464e388a21fa915e14f59da6", "source": "testData/secrets/generic-api-keys.txt", "ruleId": "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - "ruleName": "Generic-Api-Key-Custom", + "ruleName": "Generic-Api-Key", "ruleCategory": "General", "startLine": 1, "endLine": 1, diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 9b97ee06..0bdc4ece 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -309,7 +309,7 @@ func TestSecretsScans(t *testing.T) { "--rule", "custom", "--ignore-rule", - "Generic-Api-Key-Custom,Github-Pat", + "Generic-Api-Key,Github-Pat-Custom", "--ignore-on-exit", "results", }, diff --git a/tests/testData/customRuleConfig/customRules.json b/tests/testData/customRuleConfig/customRules.json index 059e2670..2a6e6663 100644 --- a/tests/testData/customRuleConfig/customRules.json +++ b/tests/testData/customRuleConfig/customRules.json @@ -1,7 +1,7 @@ [ { "ruleId": "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - "ruleName": "Generic-Api-Key-Custom", + "ruleName": "Generic-Api-Key", "description": "Custom Generic Api Key override, should override the default one (very specific regex just for testing purposes, shouldn't match with aws-access-token", "regex": "(?i)\\b\\w*secret\\w*\\b\\s*:?=\\s*[\"']?([A-Za-z0-9/_+=-]{8,150})[\"']?", "severity": "Medium", @@ -46,15 +46,15 @@ }, { "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", + "ruleName": "Github-Pat-Custom", "description": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "regex": "ghp_[0-9a-zA-Z]{36}", "severity": "Low", "tags": ["custom", "override"], "disableValidation": true, "scoreParameters": { - "category": "Development Platform", - "ruleType": 4 + "category": "Package Management", + "ruleType": 3 } } ] \ No newline at end of file diff --git a/tests/testData/customRuleConfig/customRules.yaml b/tests/testData/customRuleConfig/customRules.yaml index 9b54c9b7..b8b43183 100644 --- a/tests/testData/customRuleConfig/customRules.yaml +++ b/tests/testData/customRuleConfig/customRules.yaml @@ -1,5 +1,5 @@ - ruleId: 01ab7659-d25a-4a1c-9f98-dee9d0cf2e70 - ruleName: Generic-Api-Key-Custom + ruleName: Generic-Api-Key description: >- Custom Generic Api Key override, should override the default one (very specific regex just for testing purposes, shouldn't match with aws-access-token regex: "(?i)\\b\\w*secret\\w*\\b\\s*:?=\\s*[\"']?([A-Za-z0-9/_+=-]{8,150})[\"']?" @@ -56,6 +56,6 @@ - custom - override scoreParameters: - category: Development Platform - ruleType: 4 + category: Package Management + ruleType: 3 disableValidation: true \ No newline at end of file diff --git a/tests/testData/expectedReport/customRules/default_plus_all_custom_rules.json b/tests/testData/expectedReport/customRules/default_plus_all_custom_rules.json index a6017dba..79d815ce 100644 --- a/tests/testData/expectedReport/customRules/default_plus_all_custom_rules.json +++ b/tests/testData/expectedReport/customRules/default_plus_all_custom_rules.json @@ -45,8 +45,8 @@ "id": "4431f6f38c1a36156f0486df95b0436810272bfb", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": "ghp_1234567890abcdefghijklmnopqrstuvwxyz123", @@ -56,7 +56,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "b72e2b9140a555724a63bbb8e8e6689b81ebaf28": [ diff --git a/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json b/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json index 31be4993..79d815ce 100644 --- a/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json +++ b/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json @@ -45,18 +45,18 @@ "id": "4431f6f38c1a36156f0486df95b0436810272bfb", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": "ghp_1234567890abcdefghijklmnopqrstuvwxyz123", "startColumn": 1, "endColumn": 40, "value": "ghp_1234567890abcdefghijklmnopqrstuvwxyz", - "validationStatus": "Invalid", - "ruleDescription": "Uncovered a GitHub Personal Access Token, potentially leading to unauthorized repository access and sensitive content exposure.", - "severity": "Medium", - "cvssScore": 5.2 + "validationStatus": "Unknown", + "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", + "severity": "Low", + "cvssScore": 7 } ], "b72e2b9140a555724a63bbb8e8e6689b81ebaf28": [ diff --git a/tests/testData/expectedReport/customRules/only_custom_rules.json b/tests/testData/expectedReport/customRules/only_custom_rules.json index d4e0063a..46909ca4 100644 --- a/tests/testData/expectedReport/customRules/only_custom_rules.json +++ b/tests/testData/expectedReport/customRules/only_custom_rules.json @@ -45,8 +45,8 @@ "id": "4431f6f38c1a36156f0486df95b0436810272bfb", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": "ghp_1234567890abcdefghijklmnopqrstuvwxyz123", @@ -56,7 +56,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "b72e2b9140a555724a63bbb8e8e6689b81ebaf28": [ diff --git a/tests/testData/expectedReport/customRules/only_override_rules.json b/tests/testData/expectedReport/customRules/only_override_rules.json index e0a906f6..2bdc1631 100644 --- a/tests/testData/expectedReport/customRules/only_override_rules.json +++ b/tests/testData/expectedReport/customRules/only_override_rules.json @@ -7,8 +7,8 @@ "id": "4431f6f38c1a36156f0486df95b0436810272bfb", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat", - "ruleCategory": "Development Platform", + "ruleName": "Github-Pat-Custom", + "ruleCategory": "Package Management", "startLine": 1, "endLine": 1, "lineContent": "ghp_1234567890abcdefghijklmnopqrstuvwxyz123", @@ -18,7 +18,7 @@ "validationStatus": "Unknown", "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", "severity": "Low", - "cvssScore": 8.2 + "cvssScore": 7 } ], "58a104ec1b079f2e63d3794d374587048ef6548a": [ @@ -26,7 +26,7 @@ "id": "58a104ec1b079f2e63d3794d374587048ef6548a", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - "ruleName": "Generic-Api-Key-Custom", + "ruleName": "Generic-Api-Key", "ruleCategory": "General", "startLine": 3, "endLine": 3, @@ -45,7 +45,7 @@ "id": "b69313d6d65da942f1b91a5f12dca17637bff7d9", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "01ab7659-d25a-4a1c-9f98-dee9d0cf2e70", - "ruleName": "Generic-Api-Key-Custom", + "ruleName": "Generic-Api-Key", "ruleCategory": "General", "startLine": 4, "endLine": 4, From 4b1371b405070580258ee7551d33d133660d4844 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:32:05 +0000 Subject: [PATCH 07/15] Fixing tests --- cmd/config_test.go | 30 ++++++++----------- cmd/testData/customRulesValid.yaml | 5 +--- engine/rules/ruledefine/rule.go | 2 +- engine/rules/rules.go | 3 +- tests/e2e_test.go | 9 ++++-- .../customRuleConfig/customRules.yaml | 2 +- .../default_plus_non_override_rules.json | 12 ++++---- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/cmd/config_test.go b/cmd/config_test.go index bfaed795..b2174fc0 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -299,29 +299,25 @@ func TestCustomRulesFlag(t *testing.T) { }, Tags: []string{"security", "credentials"}, ScoreParameters: ruledefine.ScoreParameters{ - Category: "Secrets", + Category: "General", RuleType: 2, }, DisableValidation: true, Deprecated: true, }, { - RuleID: "b47a1995-6572-41bb-b01d-d215b43ab089", - RuleName: "mock-rule2", - Description: "Match API keys", - Regex: "[A-Za-z0-9]{40}", - Keywords: []string{"api", "key"}, - Entropy: 4.0, - Path: "config/api_keys.yaml", - SecretGroup: 0, - Severity: "Medium", - OldSeverity: "High", - AllowLists: []*ruledefine.AllowList{}, - Tags: []string{"api", "custom"}, - ScoreParameters: ruledefine.ScoreParameters{ - Category: "API", - RuleType: 1, - }, + RuleID: "b47a1995-6572-41bb-b01d-d215b43ab089", + RuleName: "mock-rule2", + Description: "Match API keys", + Regex: "[A-Za-z0-9]{40}", + Keywords: []string{"api", "key"}, + Entropy: 4.0, + Path: "config/api_keys.yaml", + SecretGroup: 0, + Severity: "Medium", + OldSeverity: "High", + AllowLists: []*ruledefine.AllowList{}, + Tags: []string{"api", "custom"}, DisableValidation: false, Deprecated: false, }, diff --git a/cmd/testData/customRulesValid.yaml b/cmd/testData/customRulesValid.yaml index 5c11d1c5..ab49ca8a 100644 --- a/cmd/testData/customRulesValid.yaml +++ b/cmd/testData/customRulesValid.yaml @@ -26,7 +26,7 @@ - security - credentials scoreParameters: - category: Secrets + category: General ruleType: 2 disableValidation: true deprecated: true @@ -47,8 +47,5 @@ tags: - api - custom - scoreParameters: - category: API - ruleType: 1 disableValidation: false deprecated: false \ No newline at end of file diff --git a/engine/rules/ruledefine/rule.go b/engine/rules/ruledefine/rule.go index f37a8bb1..fd3cfd48 100644 --- a/engine/rules/ruledefine/rule.go +++ b/engine/rules/ruledefine/rule.go @@ -108,6 +108,6 @@ type AllowList struct { // For patterns that are allowed to be ignored StopWords []string `json:"stopWords" yaml:"stopWords"` // stop words that are allowed to be ignored } -func (r Rule) CreateRuleNameFromRuleID() { +func (r *Rule) CreateRuleNameFromRuleID() { r.RuleName = r.RuleID } diff --git a/engine/rules/rules.go b/engine/rules/rules.go index 32cb4661..c6a849c7 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -352,11 +352,10 @@ func addCustomRules(selectedRules, customRules []*ruledefine.Rule) []*ruledefine } // completeOverridesWithDefaultFields fills in some missing fields in custom rules if they match default rules by ruleID -func completeOverridesWithDefaultFields(customRules []*ruledefine.Rule, defaultRules []*ruledefine.Rule) { +func completeOverridesWithDefaultFields(customRules, defaultRules []*ruledefine.Rule) { for _, customRule := range customRules { for _, defaultRule := range defaultRules { if defaultRule.RuleID == customRule.RuleID { - if customRule.RuleName == "" { customRule.RuleName = defaultRule.RuleName } diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 0bdc4ece..878101cc 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -353,8 +353,13 @@ func TestSecretsScans(t *testing.T) { for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { - executable, err := createCLI(t.TempDir()) - require.NoError(t, err) + executable := &cli{ + executable: "C:\\Users\\diogoro\\workspace\\2ms\\2ms.exe", + resultsPath: path.Join(t.TempDir(), "results.json"), + } + + //executable, err := createCLI(t.TempDir()) + //require.NoError(t, err) if err := executable.run(tc.ScanTarget, tc.Args...); err != nil { t.Fatalf("error running scan with args: %v, got: %v", tc.Args, err) diff --git a/tests/testData/customRuleConfig/customRules.yaml b/tests/testData/customRuleConfig/customRules.yaml index b8b43183..11ba95ab 100644 --- a/tests/testData/customRuleConfig/customRules.yaml +++ b/tests/testData/customRuleConfig/customRules.yaml @@ -47,7 +47,7 @@ ruleType: 4 - ruleId: 9f24ac30-9e04-4dc2-bc32-26da201f87e5 - ruleName: Github-Pat + ruleName: Github-Pat-Custom description: >- Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid regex: "ghp_[0-9a-zA-Z]{36}" diff --git a/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json b/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json index 79d815ce..31be4993 100644 --- a/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json +++ b/tests/testData/expectedReport/customRules/default_plus_non_override_rules.json @@ -45,18 +45,18 @@ "id": "4431f6f38c1a36156f0486df95b0436810272bfb", "source": "testData/input/custom_rules_secrets.txt", "ruleId": "9f24ac30-9e04-4dc2-bc32-26da201f87e5", - "ruleName": "Github-Pat-Custom", - "ruleCategory": "Package Management", + "ruleName": "Github-Pat", + "ruleCategory": "Development Platform", "startLine": 1, "endLine": 1, "lineContent": "ghp_1234567890abcdefghijklmnopqrstuvwxyz123", "startColumn": 1, "endColumn": 40, "value": "ghp_1234567890abcdefghijklmnopqrstuvwxyz", - "validationStatus": "Unknown", - "ruleDescription": "Github-Pat with DisableValidation set to true to test if validation is correctly disabled, resulting in Unknown validity instead of Invalid", - "severity": "Low", - "cvssScore": 7 + "validationStatus": "Invalid", + "ruleDescription": "Uncovered a GitHub Personal Access Token, potentially leading to unauthorized repository access and sensitive content exposure.", + "severity": "Medium", + "cvssScore": 5.2 } ], "b72e2b9140a555724a63bbb8e8e6689b81ebaf28": [ From 837662839c0e3a0ce7c4233ce36575425fe0f449 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:46:24 +0000 Subject: [PATCH 08/15] Removing code for local tests --- tests/e2e_test.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 878101cc..0bdc4ece 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -353,13 +353,8 @@ func TestSecretsScans(t *testing.T) { for _, tc := range tests { t.Run(tc.Name, func(t *testing.T) { - executable := &cli{ - executable: "C:\\Users\\diogoro\\workspace\\2ms\\2ms.exe", - resultsPath: path.Join(t.TempDir(), "results.json"), - } - - //executable, err := createCLI(t.TempDir()) - //require.NoError(t, err) + executable, err := createCLI(t.TempDir()) + require.NoError(t, err) if err := executable.run(tc.ScanTarget, tc.Args...); err != nil { t.Fatalf("error running scan with args: %v, got: %v", tc.Args, err) From ae1fe801b0d88e9511170646f9107c720bd566c2 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:56:32 +0000 Subject: [PATCH 09/15] Improving readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fc4b5bd7..92a21311 100644 --- a/README.md +++ b/README.md @@ -285,8 +285,8 @@ Other fields are optional and can be seen in the example bellow of a file with a **YAML Example:** ```yaml -- ruleId: 01ab7659-d25a-4a1c-9f98-dee9d0cf2e70 # REQUIRED: unique id, must match default rule id to override that default rule. Rule ids be used as values in --rule and --ignore-rule flags - ruleName: Custom-Api-Key # REQUIRED: human-readable name. Rule names used as values in --rule and --ignore-rule flags +- ruleId: 01ab7659-d25a-4a1c-9f98-dee9d0cf2e70 # REQUIRED: unique id, must match default rule id to override that default rule. Rule ids can be used as values in --rule and --ignore-rule flags + ruleName: Custom-Api-Key # human-readable name. Can be left empty for overrides, in which case the respective default rule name will be considered. Rule names can be used as values in --rule and --ignore-rule flags description: Custom rule regex: (?i)\b\w*secret\w*\b\s*:?=\s*["']?([A-Za-z0-9/_+=-]{8,150})["']? # REQUIRED: golang regular expression used to find secrets. If capture group is present in regex, it used to find the secret, otherwise whole regex is used. which group is considered the secret can be defined with secretGroup keywords: # 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. @@ -296,11 +296,11 @@ Other fields are optional and can be seen in the example bellow of a file with a secretGroup: 1 # defines which capture group of regex match is considered the secret. Is also used as the group that will have its entropy checked if `entropy` is set. Can be left empty, in which case the first capture group to match will be considered the secret path: (?i)\.(?:tf|hcl)$ # regex to limit the rule to specific file paths. For example, only .tf and .hcl files severity: High # severity, can only be one of [Critical, High, Medium, Low, Info] - tags: # identifiers for the rule, tags defined here can be used as values of --rule and --ignore-rule flags + tags: # identifiers for the rule, tags can be used as values of --rule and --ignore-rule flags - api-key - scoreParameters: + scoreParameters: # scoreParameters can be omitted for overrides, in which case the respective default rule scoreParameters will be considered category: General # category of the rule, should be a string of type ruledefine.RuleCategory. Impacts cvss score - ruleType: 4 # can go from 4 to 0, 4 being most severe. Impacts cvss score + ruleType: 4 # can go from 4 to 0, 4 being most severe. For overrides, if Category is defined, ruleType also needs to be defined, or otherwise it will be considered 0. Impacts cvss score allowLists: # allowed values to ignore if matched - description: Allowlist for Custom Rule matchCondition: OR # determines whether all criteria in the allowList must match. Can be AND or OR. Defaults to OR if not specified From fe00086f8fd798cb17a54d3c25b165851e775411 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:00:16 +0000 Subject: [PATCH 10/15] Improving readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92a21311..a85ea696 100644 --- a/README.md +++ b/README.md @@ -286,7 +286,7 @@ Other fields are optional and can be seen in the example bellow of a file with a **YAML Example:** ```yaml - ruleId: 01ab7659-d25a-4a1c-9f98-dee9d0cf2e70 # REQUIRED: unique id, must match default rule id to override that default rule. Rule ids can be used as values in --rule and --ignore-rule flags - ruleName: Custom-Api-Key # human-readable name. Can be left empty for overrides, in which case the respective default rule name will be considered. Rule names can be used as values in --rule and --ignore-rule flags + ruleName: Custom-Api-Key # should be human-readable name. If left empty for new rule, ruleName will take the value of ruleId. If left empty for override, default rule name will be considered. Rule names can be used as values in --rule and --ignore-rule flags description: Custom rule regex: (?i)\b\w*secret\w*\b\s*:?=\s*["']?([A-Za-z0-9/_+=-]{8,150})["']? # REQUIRED: golang regular expression used to find secrets. If capture group is present in regex, it used to find the secret, otherwise whole regex is used. which group is considered the secret can be defined with secretGroup keywords: # 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. From 17aeb2bd35187de6e921d63660c25aaab02802e5 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:07:27 +0000 Subject: [PATCH 11/15] Add ignore secrets in .2ms.yml --- .2ms.yml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/.2ms.yml b/.2ms.yml index b5be3ea8..66352079 100644 --- a/.2ms.yml +++ b/.2ms.yml @@ -2815,3 +2815,59 @@ ignore-result: - e41df94f30916531be27748175d28f851f0bacad # test data from only_custom_rules.json - faf4f974fad32cc5fb928a6d94adfe141007e925 # test data from custom_rules_secrets.txt - 20734a351c9be94da9240871fa50cf0959cf94ba # test data from default_plus_non_override_rules.json +- 05602cbb737e019ceaed1842a1fafa048f057e39 # test data from expectedReportWithIgnoredResults.json +- 0911ff5aa4fb801ef72e9aec41274ff6642fb6cb # test data from defaultPlusNonOverrideRules.json +- 09b533109d917e12c1474d6f6d7783561fb58d41 # test data from generic-api-keys.txt +- 0a7fc5f23b68249d5c3aa5486c1a2caebeaac45e # test data from onlyCustomRules.json +- 0bb0f0b115e826fc22e3058062fa5fb4a50f143b # test data from defaultPlusNonOverrideRules.json +- 0d741fcc834c41f5650c3bd30abd750dca238aa9 # test data from onlyCustomRules.json +- 10d99d86045759663429f5b5357877319fb62afb # test data from defaultPlusNonOverrideRules.json +- 1150ca30a572ee85c486f042afbd078d677be261 # test data from defaultPlusNonOverrideRules.json +- 1458d3fa01ddbd4681338b1f35dccb0b84160a02 # test data from onlyOverrideRules.json +- 1577a912b253138103b9aad14d8a97c827750eaf # test data from expectedReportWithIgnoredResults.json +- 16f450b8ed7bee3163796ccae2a86aff4516c740 # test data from defaultPlusAllCustomRules.json +- 1cbf4baac00a479cf5fad6bdba794b5f4b33e984 # test data from defaultPlusNonOverrideRules.json +- 1e29c09cf8f3a599a96f7d19659543a1587a0ee5 # test data from defaultPlusAllCustomRules.json +- 241414fa731c9f86094d79292244c46f13e8f0e5 # test data from onlyDefaultIgnoreCustomRules.json +- 2e021a1d6ea1b7c72177dae61918faf7f0ae902b # test data from generic-api-keys.txt +- 310b6939ef5aa98f3a84130b777565470f5a03d6 # test data from onlyOverrideRules.json +- 3881ff90e41473873bfbe2b9de1165c3bc642b3c # test data from expectedReportWithIgnoredResults.json +- 429cd5eb6407c41b08eb4955f9b1775abe50bd5c # test data from defaultPlusAllCustomRules.json +- 42d8be555f4cf15d452f3bd9b0b2e9549a012597 # test data from onlyOverrideRules.json +- 4303f8965b423d7e652a58d480582cc4ba4a54a8 # test data from defaultPlusAllCustomRules.json +- 4607cbe90fbc7552b0977298a96063ef3aec39c9 # test data from onlyCustomRules.json +- 4ddac4f4b08377912a5de2323b6e376d63d80f3f # test data from expectedReportWithIgnoredResults.json +- 5376ee01f078e3e9e2f94a174fef9a72e09caaaf # test data from defaultPlusAllCustomRules.json +- 53fceac899c26c7ef5f008eb5693f95ffe07e7c4 # test data from expectedReportWithIgnoredResults.json +- 5a4af3cfa60282d73f0864b2c8acc1403c832a33 # test data from onlyCustomRules.json +- 5b7ff548f191f5437026d0e507f6b43cd037a320 # test data from defaultPlusAllCustomRules.json +- 5ba90ad878a6770da26c654005e875aa4a373190 # test data from onlyOverrideRules.json +- 5d1f1d3f613ae7862047c7c83170fa05fd244e43 # test data from defaultPlusAllCustomRules.json +- 6ae0b6fa63c9827e787c4994107ee0cac421eb60 # test data from expectedReportWithIgnoredResults.json +- 6b2b89be362c7d0ec4f06c615304b032f41250d0 # test data from expectedReportWithIgnoredResults.json +- 6c0d8ef2eb9f751288ecf36a1024d612c1e27ab3 # test data from onlyCustomRules.json +- 6c14ac093ccfdf9d878c79b4ea6376f0e4815a82 # test data from onlyCustomRules.json +- 7345100bea8e537d8aa434d739a3c799ce4d2a62 # test data from onlyOverrideRules.json +- 74eb842cf07b07fbe25093536654e7288b69c49a # test data from defaultPlusNonOverrideRules.json +- 7903a5376d2c1179125c726e72dc96637dd49256 # test data from defaultPlusAllCustomRules.json +- 8314bdcfa48aa5e09eca4ddc30c0d4715d5388dc # test data from expectedReportWithIgnoredResults.json +- 86963a531def427b48e008d4333803a1ed857094 # test data from onlyOverrideRules.json +- 8dfcd4aa7d4620bb02493b37ef82373dd940a662 # test data from onlyDefaultIgnoreCustomRules.json +- 8f0baeb46dc7357b2ad677811cefe24bf27a45ed # test data from onlyOverrideRules.json +- 9850a1398255e717ec5a76e08b861b0d3da87402 # test data from expectedReportWithIgnoredResults.json +- 9afb2c9aece1398d6286625ef3a06e15b26147d4 # test data from defaultPlusNonOverrideRules.json +- a415ed511dec492ee023608d76bc4e66934381de # test data from defaultPlusAllCustomRules.json +- a49f52bcec28c23b51d9082c2001ed2ef401edc3 # test data from onlyCustomRules.json +- b061d2c11bea74a82eb3fec7830843a2b3572d0d # test data from expectedReportWithIgnoredResults.json +- bdcfaa5f2c91bf942042a507925e320dd43816ea # test data from onlyCustomRules.json +- c028d45a6be26e44b7cf71ed25dd11ca0100800c # test data from expectedReportWithIgnoredResults.json +- c66c28ce94415cd6f3efe40130f2ab08a7214087 # test data from onlyCustomNoOverrideRules.json +- c6b5416e40ffd8bb1cf7d9d0c7965431144e00f8 # test data from defaultPlusAllCustomRules.json +- ced63354eaca48df7cdf8ac571fdb6e25cefbaae # test data from onlyOverrideRules.json +- d07771fdf69f59c61607f7994e30694f4ba25177 # test data from onlyCustomNoOverrideRules.json +- d53aff50117f07d339749ebdc5556b117d82e5f5 # test data from defaultPlusNonOverrideRules.json +- dac4cd38432590cee87d46e3448c874b7e2c70c5 # test data from defaultPlusAllCustomRules.json +- dfbbddd73932c7210bfc953917c5b485e3ee7535 # test data from defaultPlusNonOverrideRules.json +- f0544e7e9e25a6223cd10c37a445bfd4a4641337 # test data from defaultPlusAllCustomRules.json +- f93dd5ab91efe7b70381afe3d4ebd1b26e628ac9 # test data from defaultPlusNonOverrideRules.json +- ff81ccd6553feaf5fbbf573eac2a0042d05aaee4 # test data from defaultPlusNonOverrideRules.json From 82b6ff5ee297ee0434c7b4fb9d7f9eab722fc4a9 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:23:23 +0000 Subject: [PATCH 12/15] Added missing fields on readme yaml example --- README.md | 2 ++ engine/rules/ruledefine/rule.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a85ea696..95ea38d2 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,8 @@ Other fields are optional and can be seen in the example bellow of a file with a scoreParameters: # scoreParameters can be omitted for overrides, in which case the respective default rule scoreParameters will be considered category: General # category of the rule, should be a string of type ruledefine.RuleCategory. Impacts cvss score ruleType: 4 # can go from 4 to 0, 4 being most severe. For overrides, if Category is defined, ruleType also needs to be defined, or otherwise it will be considered 0. Impacts cvss score + disableValidation: false # if true, disables validity check for this rule, regardless of --validate flag + deprecated: false # if true, the rule will not be used in the scan, regardless of --rule flag allowLists: # allowed values to ignore if matched - description: Allowlist for Custom Rule matchCondition: OR # determines whether all criteria in the allowList must match. Can be AND or OR. Defaults to OR if not specified diff --git a/engine/rules/ruledefine/rule.go b/engine/rules/ruledefine/rule.go index fd3cfd48..8c1904bd 100644 --- a/engine/rules/ruledefine/rule.go +++ b/engine/rules/ruledefine/rule.go @@ -91,7 +91,7 @@ type Rule struct { Path string `json:"path" yaml:"path"` // present in some gitleaks secrets (regex) SecretGroup int `json:"secretGroup" yaml:"secretGroup"` //nolint:lll // SecretGroup is used to extract secret from regex match and used as the group that will have its entropy checked if `entropy` is set. Severity Severity `json:"severity" yaml:"severity"` - OldSeverity string `json:"oldSeverity" yaml:"oldSeverity"` // fallback for when critical is not enabled + OldSeverity string `json:"oldSeverity" yaml:"oldSeverity"` // fallback for when critical is not enabled, has no effect on open source AllowLists []*AllowList `json:"allowLists" yaml:"allowLists"` Tags []string `json:"tags" yaml:"tags"` ScoreParameters ScoreParameters `json:"scoreParameters" yaml:"scoreParameters"` // used for ASPM From 22f4d11c956b51b132c251041625e8ade8e83b9e Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:07:29 +0000 Subject: [PATCH 13/15] lint issues --- engine/rules/ruledefine/rule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/rules/ruledefine/rule.go b/engine/rules/ruledefine/rule.go index 8c1904bd..bd5ccd12 100644 --- a/engine/rules/ruledefine/rule.go +++ b/engine/rules/ruledefine/rule.go @@ -91,7 +91,7 @@ type Rule struct { Path string `json:"path" yaml:"path"` // present in some gitleaks secrets (regex) SecretGroup int `json:"secretGroup" yaml:"secretGroup"` //nolint:lll // SecretGroup is used to extract secret from regex match and used as the group that will have its entropy checked if `entropy` is set. Severity Severity `json:"severity" yaml:"severity"` - OldSeverity string `json:"oldSeverity" yaml:"oldSeverity"` // fallback for when critical is not enabled, has no effect on open source + OldSeverity string `json:"oldSeverity" yaml:"oldSeverity"` //nolint:lll // fallback for when critical is not enabled, has no effect on open source AllowLists []*AllowList `json:"allowLists" yaml:"allowLists"` Tags []string `json:"tags" yaml:"tags"` ScoreParameters ScoreParameters `json:"scoreParameters" yaml:"scoreParameters"` // used for ASPM From a3012ada1410d03da64121cb308d6a46b8e73e94 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:03:43 +0000 Subject: [PATCH 14/15] Applying break to completeOverrides for loop --- engine/rules/rules.go | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/rules/rules.go b/engine/rules/rules.go index c6a849c7..16c416bc 100644 --- a/engine/rules/rules.go +++ b/engine/rules/rules.go @@ -367,6 +367,7 @@ func completeOverridesWithDefaultFields(customRules, defaultRules []*ruledefine. customRule.ScoreParameters.RuleType = defaultRule.ScoreParameters.RuleType } } + break } } } From 7188a29e6f32b8fbf5924f8ec5a200e6ed197040 Mon Sep 17 00:00:00 2001 From: Diogo-fj-rocha <104084969+cx-diogo-rocha@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:28:05 +0000 Subject: [PATCH 15/15] Fixing trivy vuln --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 302bce37..d168450f 100644 --- a/go.mod +++ b/go.mod @@ -25,9 +25,9 @@ require ( github.com/stretchr/testify v1.10.0 github.com/zricethezav/gitleaks/v8 v8.28.0 go.uber.org/mock v0.5.2 - golang.org/x/net v0.40.0 - golang.org/x/sync v0.17.0 - golang.org/x/text v0.29.0 + golang.org/x/net v0.47.0 + golang.org/x/sync v0.18.0 + golang.org/x/text v0.31.0 golang.org/x/time v0.5.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -87,7 +87,7 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/crypto v0.38.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect ) diff --git a/go.sum b/go.sum index 953366ee..02b85d61 100644 --- a/go.sum +++ b/go.sum @@ -1251,8 +1251,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1386,8 +1386,8 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1410,8 +1410,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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= @@ -1506,8 +1506,8 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1557,8 +1557,8 @@ golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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=