Cybersecurity-Projects/PROJECTS/intermediate/secrets-scanner/internal/rules/registry.go

168 lines
3.6 KiB
Go

// ©AngelaMos | 2026
// registry.go
package rules
import (
"fmt"
"regexp"
"sort"
"strings"
"github.com/CarterPerez-dev/portia/pkg/types"
)
type Registry struct {
rules map[string]*types.Rule
disabled map[string]bool
}
func NewRegistry() *Registry {
return &Registry{
rules: make(map[string]*types.Rule),
disabled: make(map[string]bool),
}
}
func (r *Registry) Register(rule *types.Rule) {
if _, exists := r.rules[rule.ID]; exists {
panic(fmt.Sprintf(
"duplicate rule ID: %s", rule.ID,
))
}
r.rules[rule.ID] = rule
}
func (r *Registry) Get(
id string,
) (*types.Rule, bool) {
rule, ok := r.rules[id]
if !ok || r.disabled[id] {
return nil, false
}
return rule, true
}
func (r *Registry) All() []*types.Rule {
result := make([]*types.Rule, 0, len(r.rules))
for _, rule := range r.rules {
if !r.disabled[rule.ID] {
result = append(result, rule)
}
}
sort.Slice(result, func(i, j int) bool {
return result[i].ID < result[j].ID
})
return result
}
func (r *Registry) Disable(ids ...string) {
for _, id := range ids {
r.disabled[id] = true
}
}
func (r *Registry) MatchKeywords(
content string,
) []*types.Rule {
lower := strings.ToLower(content)
var matched []*types.Rule
for _, rule := range r.rules {
if r.disabled[rule.ID] {
continue
}
for _, kw := range rule.Keywords {
if strings.Contains(lower, strings.ToLower(kw)) {
matched = append(matched, rule)
break
}
}
}
return matched
}
func (r *Registry) Len() int {
count := 0
for id := range r.rules {
if !r.disabled[id] {
count++
}
}
return count
}
func (r *Registry) Replace(rule *types.Rule) {
r.rules[rule.ID] = rule
}
var GlobalPathAllowlist = []*regexp.Regexp{
regexp.MustCompile(
`go\.(?:mod|sum|work(?:\.sum)?)$`,
),
regexp.MustCompile(
`(?:^|/)(?:package-lock\.json|pnpm-lock\.yaml|` +
`yarn\.lock|npm-shrinkwrap\.json|deno\.lock|` +
`Cargo\.lock|composer\.lock|Gemfile\.lock|` +
`poetry\.lock|Pipfile\.lock|mix\.lock|` +
`pubspec\.lock|Podfile\.lock|flake\.lock|` +
`bun\.lockb)$`,
),
regexp.MustCompile(`(?:^|/)node_modules/`),
regexp.MustCompile(`(?:^|/)vendor/`),
regexp.MustCompile(`(?:^|/)\.git/`),
regexp.MustCompile(`(?:^|/)\.svn/`),
regexp.MustCompile(`(?:^|/)\.hg/`),
regexp.MustCompile(
`(?:^|/)(?:__pycache__|\.venv|venv|\.tox|` +
`\.mypy_cache|\.pytest_cache|\.ruff_cache|` +
`\.eggs|.*\.egg-info)/`,
),
regexp.MustCompile(
`(?:^|/)(?:\.next|\.nuxt|\.svelte-kit|` +
`\.terraform|\.gradle|\.mvn|\.bundle|` +
`Pods|coverage|\.nyc_output)/`,
),
regexp.MustCompile(
`(?:^|/)(?:target|build|dist|out)/`,
),
regexp.MustCompile(
`\.min\.(?:js|css)(?:\.map)?$`,
),
regexp.MustCompile(
`\.(?:png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot|otf|` +
`mp[34]|avi|mkv|mov|zip|tar|gz|rar|7z|pdf|exe|` +
`dll|so|dylib)$`,
),
regexp.MustCompile(`dist-info/`),
}
var GlobalValueAllowlist = []*regexp.Regexp{
regexp.MustCompile(
`(?i)^(?:example|test|dummy|fake|placeholder|` +
`sample|your[-_]?(?:api[-_]?key|token|` +
`secret|password))`,
),
regexp.MustCompile(`^x{4,}$`),
regexp.MustCompile(`^\*{4,}$`),
regexp.MustCompile(`^\$\{.+\}$`),
regexp.MustCompile(`^\{\{.+\}\}$`),
regexp.MustCompile(`^<[A-Z_]{2,}>$`),
regexp.MustCompile(`^0{8,}$`),
regexp.MustCompile(
`^(?:1{8,}|2{8,}|3{8,}|4{8,}|5{8,}|6{8,}|7{8,}|8{8,}|9{8,})$`,
),
regexp.MustCompile(
`(?i)^(?:none|null|nil|undefined|true|false|` +
`TODO|FIXME|CHANGEME|INSERT|REPLACE|` +
`REDACTED|N/A|TBD|REMOVED|MASKED|` +
`changeit|s3cr3t|p@ssw0rd|admin123)$`,
),
regexp.MustCompile(
`(?i)^(?:dGVzdA==|cGFzc3dvcmQ=|YWRtaW4=)$`,
),
regexp.MustCompile(
`(?i)^(?:PUT_YOUR_|ENTER_YOUR_|ADD_YOUR_)`,
),
}