add .angela.toml and [tool.angela] config with ignore lists

This commit is contained in:
CarterPerez-dev 2026-01-28 01:24:43 -05:00
parent d73241dc86
commit 841d287893
3 changed files with 158 additions and 4 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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
}