add .angela.toml and [tool.angela] config with ignore lists
This commit is contained in:
parent
d73241dc86
commit
841d287893
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue