From b63467668ac2867b1e407e8afafcab1df451e429 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:49:52 +0000 Subject: [PATCH] refactor(#234): extract FormatFinding helper for security findings Add FormatFinding to internal/security/ as the canonical format for printing finding details: [severity] name: detail. This resolves the Scanner vs Name field inconsistency in scan.go (line 125 used f.Scanner while line 258 used f.Name) and gives future callers a single function to use instead of copying a format string. Changes: - Add FormatFinding(f Finding) string to scanner.go - Update scan.go input scan to use FormatFinding (fixes Scanner to Name) - Update scan.go context scan to use FormatFinding - Add TestFormatFinding Closes #234 --- internal/cli/scan.go | 4 ++-- internal/security/scanner.go | 11 +++++++++++ internal/security/scanner_test.go | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) 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 {