From 841d287893f955ffadde231613bbdec52689ca0c Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Wed, 28 Jan 2026 01:24:43 -0500 Subject: [PATCH] add .angela.toml and [tool.angela] config with ignore lists --- .../internal/cli/output.go | 41 ++++++++++ .../internal/cli/update.go | 41 +++++++++- .../internal/config/config.go | 80 +++++++++++++++++++ 3 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 PROJECTS/simple-vulnerability-scanner/internal/config/config.go diff --git a/PROJECTS/simple-vulnerability-scanner/internal/cli/output.go b/PROJECTS/simple-vulnerability-scanner/internal/cli/output.go index 2711bd35..1e5e58d2 100644 --- a/PROJECTS/simple-vulnerability-scanner/internal/cli/output.go +++ b/PROJECTS/simple-vulnerability-scanner/internal/cli/output.go @@ -307,6 +307,47 @@ func truncate(s string, maxLen int) string { return s[:maxLen-3] + "..." } +func filterIgnoredVulns( + vulns map[string][]types.Vulnerability, + ignoreIDs []string, +) map[string][]types.Vulnerability { + if len(ignoreIDs) == 0 { + return vulns + } + + ignored := make(map[string]bool, len(ignoreIDs)) + for _, id := range ignoreIDs { + ignored[id] = true + } + + filtered := make(map[string][]types.Vulnerability) + for pkg, vlist := range vulns { + var kept []types.Vulnerability + for _, v := range vlist { + if isVulnIgnored(v, ignored) { + continue + } + kept = append(kept, v) + } + if len(kept) > 0 { + filtered[pkg] = kept + } + } + return filtered +} + +func isVulnIgnored(v types.Vulnerability, ignored map[string]bool) bool { + if ignored[v.ID] { + return true + } + for _, alias := range v.Aliases { + if ignored[alias] { + return true + } + } + return false +} + func filterVulnsBySeverity( vulns map[string][]types.Vulnerability, minSev string, diff --git a/PROJECTS/simple-vulnerability-scanner/internal/cli/update.go b/PROJECTS/simple-vulnerability-scanner/internal/cli/update.go index a11b38f0..9c9e9044 100644 --- a/PROJECTS/simple-vulnerability-scanner/internal/cli/update.go +++ b/PROJECTS/simple-vulnerability-scanner/internal/cli/update.go @@ -11,6 +11,7 @@ import ( "sort" "time" + "github.com/CarterPerez-dev/angela/internal/config" "github.com/CarterPerez-dev/angela/internal/osv" "github.com/CarterPerez-dev/angela/internal/pypi" "github.com/CarterPerez-dev/angela/internal/pyproject" @@ -54,7 +55,7 @@ latest stable versions, and checks for known CVEs using OSV.dev.`, "show full vulnerability details", ) root.PersistentFlags().StringVar( - &minSeverity, "min-severity", "low", + &minSeverity, "min-severity", "", "minimum severity to report (critical, high, moderate, low)", ) @@ -183,6 +184,7 @@ func runUpdate( dryRun bool, ) error { start := time.Now() + cfg := config.Load(f.file) deps, err := pyproject.ParseFile(f.file) if err != nil { @@ -210,7 +212,8 @@ func runUpdate( } updates, updateSpecs := resolveUpdates( - deps, versionMap, f.safe, f.includePrerelease, + deps, versionMap, f.safe, + f.includePrerelease, cfg.Ignore, ) sortUpdates(updates) @@ -226,8 +229,10 @@ func runUpdate( } } + minSev := resolveMinSeverity(cfg.MinSeverity) if vulns != nil { - vulns = filterVulnsBySeverity(vulns, minSeverity) + vulns = filterIgnoredVulns(vulns, cfg.IgnoreVulns) + vulns = filterVulnsBySeverity(vulns, minSev) PrintVulnerabilities(vulns) } @@ -254,6 +259,7 @@ func runUpdate( func runScan(ctx context.Context, file string) error { start := time.Now() + cfg := config.Load(file) deps, err := pyproject.ParseFile(file) if err != nil { @@ -262,8 +268,10 @@ func runScan(ctx context.Context, file string) error { PrintScanning(len(deps)) + minSev := resolveMinSeverity(cfg.MinSeverity) vulns := scanForVulns(ctx, deps) - vulns = filterVulnsBySeverity(vulns, minSeverity) + vulns = filterIgnoredVulns(vulns, cfg.IgnoreVulns) + vulns = filterVulnsBySeverity(vulns, minSev) PrintVulnerabilities(vulns) totalVulns := 0 @@ -286,11 +294,26 @@ func resolveUpdates( versionMap map[string][]string, safe bool, includePrerelease bool, + ignoreDeps []string, ) ([]types.UpdateResult, map[string]string) { var results []types.UpdateResult specs := make(map[string]string) + ignoreSet := make(map[string]bool, len(ignoreDeps)) + for _, name := range ignoreDeps { + ignoreSet[pypi.NormalizeName(name)] = true + } + for _, dep := range deps { + if ignoreSet[pypi.NormalizeName(dep.Name)] { + results = append(results, types.UpdateResult{ + Name: dep.Name, + Skipped: true, + Reason: "ignored in config", + }) + continue + } + versions, ok := versionMap[dep.Name] if !ok { results = append(results, types.UpdateResult{ @@ -418,6 +441,16 @@ func latestAny(versions []string) (pypi.Version, error) { return latest, nil } +func resolveMinSeverity(configVal string) string { + if minSeverity != "" { + return minSeverity + } + if configVal != "" { + return configVal + } + return "low" +} + func sortUpdates(updates []types.UpdateResult) { order := map[string]int{ pypi.Major.String(): 0, diff --git a/PROJECTS/simple-vulnerability-scanner/internal/config/config.go b/PROJECTS/simple-vulnerability-scanner/internal/config/config.go new file mode 100644 index 00000000..2404c318 --- /dev/null +++ b/PROJECTS/simple-vulnerability-scanner/internal/config/config.go @@ -0,0 +1,80 @@ +// ©AngelaMos | 2026 +// config.go + +package config + +import ( + "os" + "strings" + + toml "github.com/pelletier/go-toml/v2" +) + +// Config holds project-level angela settings loaded from .angela.toml +// or [tool.angela] in pyproject.toml +type Config struct { + MinSeverity string `toml:"min-severity"` + Ignore []string `toml:"ignore"` + IgnoreVulns []string `toml:"ignore-vulns"` +} + +type pyprojectWrapper struct { + Tool struct { + Angela Config `toml:"angela"` + } `toml:"tool"` +} + +// Load reads angela configuration using a cascading resolution order: +// .angela.toml in current directory, then [tool.angela] in pyproject.toml +func Load(pyprojectPath string) Config { + if cfg, err := loadFile(".angela.toml"); err == nil { + return cfg + } + + if cfg, ok := loadFromPyproject(pyprojectPath); ok { + return cfg + } + + return Config{} +} + +func loadFile(path string) (Config, error) { + data, err := os.ReadFile(path) //nolint:gosec + if err != nil { + return Config{}, err + } + + var cfg Config + if err := toml.Unmarshal(data, &cfg); err != nil { + return Config{}, err + } + + cfg.MinSeverity = strings.ToLower( + strings.TrimSpace(cfg.MinSeverity), + ) + return cfg, nil +} + +func loadFromPyproject(path string) (Config, bool) { + data, err := os.ReadFile(path) //nolint:gosec + if err != nil { + return Config{}, false + } + + var wrapper pyprojectWrapper + if err := toml.Unmarshal(data, &wrapper); err != nil { + return Config{}, false + } + + cfg := wrapper.Tool.Angela + if cfg.MinSeverity == "" && + len(cfg.Ignore) == 0 && + len(cfg.IgnoreVulns) == 0 { + return Config{}, false + } + + cfg.MinSeverity = strings.ToLower( + strings.TrimSpace(cfg.MinSeverity), + ) + return cfg, true +}