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] + "..."
|
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(
|
func filterVulnsBySeverity(
|
||||||
vulns map[string][]types.Vulnerability,
|
vulns map[string][]types.Vulnerability,
|
||||||
minSev string,
|
minSev string,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/CarterPerez-dev/angela/internal/config"
|
||||||
"github.com/CarterPerez-dev/angela/internal/osv"
|
"github.com/CarterPerez-dev/angela/internal/osv"
|
||||||
"github.com/CarterPerez-dev/angela/internal/pypi"
|
"github.com/CarterPerez-dev/angela/internal/pypi"
|
||||||
"github.com/CarterPerez-dev/angela/internal/pyproject"
|
"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",
|
"show full vulnerability details",
|
||||||
)
|
)
|
||||||
root.PersistentFlags().StringVar(
|
root.PersistentFlags().StringVar(
|
||||||
&minSeverity, "min-severity", "low",
|
&minSeverity, "min-severity", "",
|
||||||
"minimum severity to report (critical, high, moderate, low)",
|
"minimum severity to report (critical, high, moderate, low)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -183,6 +184,7 @@ func runUpdate(
|
||||||
dryRun bool,
|
dryRun bool,
|
||||||
) error {
|
) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
cfg := config.Load(f.file)
|
||||||
|
|
||||||
deps, err := pyproject.ParseFile(f.file)
|
deps, err := pyproject.ParseFile(f.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -210,7 +212,8 @@ func runUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
updates, updateSpecs := resolveUpdates(
|
updates, updateSpecs := resolveUpdates(
|
||||||
deps, versionMap, f.safe, f.includePrerelease,
|
deps, versionMap, f.safe,
|
||||||
|
f.includePrerelease, cfg.Ignore,
|
||||||
)
|
)
|
||||||
|
|
||||||
sortUpdates(updates)
|
sortUpdates(updates)
|
||||||
|
|
@ -226,8 +229,10 @@ func runUpdate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
minSev := resolveMinSeverity(cfg.MinSeverity)
|
||||||
if vulns != nil {
|
if vulns != nil {
|
||||||
vulns = filterVulnsBySeverity(vulns, minSeverity)
|
vulns = filterIgnoredVulns(vulns, cfg.IgnoreVulns)
|
||||||
|
vulns = filterVulnsBySeverity(vulns, minSev)
|
||||||
PrintVulnerabilities(vulns)
|
PrintVulnerabilities(vulns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,6 +259,7 @@ func runUpdate(
|
||||||
|
|
||||||
func runScan(ctx context.Context, file string) error {
|
func runScan(ctx context.Context, file string) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
cfg := config.Load(file)
|
||||||
|
|
||||||
deps, err := pyproject.ParseFile(file)
|
deps, err := pyproject.ParseFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -262,8 +268,10 @@ func runScan(ctx context.Context, file string) error {
|
||||||
|
|
||||||
PrintScanning(len(deps))
|
PrintScanning(len(deps))
|
||||||
|
|
||||||
|
minSev := resolveMinSeverity(cfg.MinSeverity)
|
||||||
vulns := scanForVulns(ctx, deps)
|
vulns := scanForVulns(ctx, deps)
|
||||||
vulns = filterVulnsBySeverity(vulns, minSeverity)
|
vulns = filterIgnoredVulns(vulns, cfg.IgnoreVulns)
|
||||||
|
vulns = filterVulnsBySeverity(vulns, minSev)
|
||||||
PrintVulnerabilities(vulns)
|
PrintVulnerabilities(vulns)
|
||||||
|
|
||||||
totalVulns := 0
|
totalVulns := 0
|
||||||
|
|
@ -286,11 +294,26 @@ func resolveUpdates(
|
||||||
versionMap map[string][]string,
|
versionMap map[string][]string,
|
||||||
safe bool,
|
safe bool,
|
||||||
includePrerelease bool,
|
includePrerelease bool,
|
||||||
|
ignoreDeps []string,
|
||||||
) ([]types.UpdateResult, map[string]string) {
|
) ([]types.UpdateResult, map[string]string) {
|
||||||
var results []types.UpdateResult
|
var results []types.UpdateResult
|
||||||
specs := make(map[string]string)
|
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 {
|
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]
|
versions, ok := versionMap[dep.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
results = append(results, types.UpdateResult{
|
results = append(results, types.UpdateResult{
|
||||||
|
|
@ -418,6 +441,16 @@ func latestAny(versions []string) (pypi.Version, error) {
|
||||||
return latest, nil
|
return latest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveMinSeverity(configVal string) string {
|
||||||
|
if minSeverity != "" {
|
||||||
|
return minSeverity
|
||||||
|
}
|
||||||
|
if configVal != "" {
|
||||||
|
return configVal
|
||||||
|
}
|
||||||
|
return "low"
|
||||||
|
}
|
||||||
|
|
||||||
func sortUpdates(updates []types.UpdateResult) {
|
func sortUpdates(updates []types.UpdateResult) {
|
||||||
order := map[string]int{
|
order := map[string]int{
|
||||||
pypi.Major.String(): 0,
|
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