diff --git a/internal/cli/scan.go b/internal/cli/scan.go index 4b96f3157..3cb3f1723 100644 --- a/internal/cli/scan.go +++ b/internal/cli/scan.go @@ -122,7 +122,7 @@ Critical findings exit non-zero. Non-critical findings are sanitized.`, // Print findings for _, f := range allFindings { - printer.StepWarn(fmt.Sprintf("[%s] %s: %s", f.Severity, f.Scanner, f.Detail)) + printer.StepWarn(security.FormatFinding(f)) } // Generate trace ID for finding correlation. @@ -255,7 +255,7 @@ Only scans known context filenames (AGENTS.md, CLAUDE.md, .cursorrules, etc.).`, if len(normResult.Findings)+len(injResult.Findings) > 0 { printer.StepWarn(fmt.Sprintf("%s: %d finding(s)", path, len(normResult.Findings)+len(injResult.Findings))) for _, f := range append(normResult.Findings, injResult.Findings...) { - printer.StepWarn(fmt.Sprintf(" [%s] %s: %s", f.Severity, f.Name, f.Detail)) + printer.StepWarn(" " + security.FormatFinding(f)) } } else { printer.StepDone(fmt.Sprintf("%s: clean", path)) diff --git a/internal/security/scanner.go b/internal/security/scanner.go index a793cdee5..5fae52af3 100644 --- a/internal/security/scanner.go +++ b/internal/security/scanner.go @@ -13,6 +13,8 @@ // and evaluation results. package security +import "fmt" + // Finding represents a single security issue detected by a scanner. type Finding struct { Scanner string // "secret_redactor", "ssrf_validator", "context_injection", "unicode_normalizer" @@ -101,6 +103,15 @@ func OutputPipeline() *Pipeline { ) } +// FormatFinding returns a human-readable summary of a single finding. +// The canonical format is "[severity] name: detail", using the Name field +// (pattern name or category) rather than Scanner (scanner identifier). +// All callers that print individual findings should use this function to +// prevent format drift. +func FormatFinding(f Finding) string { + return fmt.Sprintf("[%s] %s: %s", f.Severity, f.Name, f.Detail) +} + // HasCriticalFindings reports whether any finding has critical severity. func HasCriticalFindings(findings []Finding) bool { for _, f := range findings { diff --git a/internal/security/scanner_test.go b/internal/security/scanner_test.go index 0b627c1c9..5ee12ccec 100644 --- a/internal/security/scanner_test.go +++ b/internal/security/scanner_test.go @@ -449,6 +449,17 @@ func TestSSRFValidator_DNSResolution(t *testing.T) { }) } +func TestFormatFinding(t *testing.T) { + f := Finding{ + Scanner: "secret_redactor", + Name: "github_pat", + Severity: "critical", + Detail: "GitHub personal access token detected", + } + got := FormatFinding(f) + assert.Equal(t, "[critical] github_pat: GitHub personal access token detected", got) +} + func hasFinding(r ScanResult, name string) bool { for _, f := range r.Findings { if f.Name == name {