package output import ( "encoding/json" "io" "time" "github.com/salvacybersec/keyhunter/pkg/engine" ) func init() { Register("json", JSONFormatter{}) } // JSONFormatter renders findings as a JSON array with 2-space indent. // Empty input produces "[]\n". When Options.Unmask is false the "key" // field carries the masked representation; "key_masked" always carries // the masked form regardless of Unmask. Verification fields are omitted // when empty so unverified scans stay compact. type JSONFormatter struct{} type jsonFinding struct { Provider string `json:"provider"` Key string `json:"key"` KeyMasked string `json:"key_masked"` Confidence string `json:"confidence"` Source string `json:"source"` SourceType string `json:"source_type"` Line int `json:"line"` Offset int64 `json:"offset"` DetectedAt string `json:"detected_at"` Verified bool `json:"verified"` VerifyStatus string `json:"verify_status,omitempty"` VerifyHTTPCode int `json:"verify_http_code,omitempty"` VerifyMetadata map[string]string `json:"verify_metadata,omitempty"` VerifyError string `json:"verify_error,omitempty"` } // Format implements the Formatter interface. func (JSONFormatter) Format(findings []engine.Finding, w io.Writer, opts Options) error { out := make([]jsonFinding, 0, len(findings)) for _, f := range findings { key := f.KeyMasked if opts.Unmask { key = f.KeyValue } out = append(out, jsonFinding{ Provider: f.ProviderName, Key: key, KeyMasked: f.KeyMasked, Confidence: f.Confidence, Source: f.Source, SourceType: f.SourceType, Line: f.LineNumber, Offset: f.Offset, DetectedAt: f.DetectedAt.Format(time.RFC3339), Verified: f.Verified, VerifyStatus: f.VerifyStatus, VerifyHTTPCode: f.VerifyHTTPCode, VerifyMetadata: f.VerifyMetadata, VerifyError: f.VerifyError, }) } enc := json.NewEncoder(w) enc.SetIndent("", " ") return enc.Encode(out) }