mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2026-06-15 13:24:37 +00:00
141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
package output
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/fatih/color"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
|
|
)
|
|
|
|
var (
|
|
yellowPrinter = color.New(color.FgYellow)
|
|
greenPrinter = color.New(color.FgHiGreen)
|
|
boldGreenPrinter = color.New(color.Bold, color.FgHiGreen)
|
|
whitePrinter = color.New(color.FgWhite)
|
|
boldWhitePrinter = color.New(color.Bold, color.FgWhite)
|
|
cyanPrinter = color.New(color.FgCyan)
|
|
)
|
|
|
|
// PlainPrinter is a printer that prints results in plain text format.
|
|
type PlainPrinter struct{ mu sync.Mutex }
|
|
|
|
func (p *PlainPrinter) Print(_ context.Context, r *detectors.ResultWithMetadata) error {
|
|
out := outputFormat{
|
|
DetectorType: r.Result.DetectorType.String(),
|
|
DecoderType: r.DecoderType.String(),
|
|
Verified: r.Result.Verified,
|
|
VerificationError: r.Result.VerificationError(),
|
|
MetaData: r.SourceMetadata,
|
|
Raw: strings.TrimSpace(string(r.Result.Raw)),
|
|
DetectorDescription: r.DetectorDescription,
|
|
}
|
|
|
|
meta, err := structToMap(out.MetaData.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("could not marshal result: %w", err)
|
|
}
|
|
|
|
printer := greenPrinter
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if out.Verified {
|
|
boldGreenPrinter.Print("✅ Found verified result 🐷🔑\n")
|
|
} else {
|
|
printer = whitePrinter
|
|
boldWhitePrinter.Print("Found unverified result 🐷🔑❓\n")
|
|
if out.VerificationError != nil {
|
|
yellowPrinter.Printf("Verification issue: %s\n", out.VerificationError)
|
|
}
|
|
}
|
|
if r.VerificationFromCache {
|
|
cyanPrinter.Print("(Verification info cached)\n")
|
|
}
|
|
printer.Printf("Detector Type: %s\n", out.DetectorType)
|
|
printer.Printf("Decoder Type: %s\n", out.DecoderType)
|
|
printer.Printf("Raw result: %s\n", whitePrinter.Sprint(out.Raw))
|
|
|
|
for k, v := range r.Result.ExtraData {
|
|
printer.Printf(
|
|
"%s: %v\n",
|
|
cases.Title(language.AmericanEnglish).String(k),
|
|
v)
|
|
}
|
|
|
|
if r.Result.StructuredData != nil {
|
|
for idx, v := range r.Result.StructuredData.GithubSshKey {
|
|
printer.Printf("GithubSshKey %d User: %s\n", idx, v.User)
|
|
|
|
if v.PublicKeyFingerprint != "" {
|
|
printer.Printf("GithubSshKey %d Fingerprint: %s\n", idx, v.PublicKeyFingerprint)
|
|
}
|
|
}
|
|
|
|
for idx, v := range r.Result.StructuredData.TlsPrivateKey {
|
|
printer.Printf("TlsPrivateKey %d Fingerprint: %s\n", idx, v.CertificateFingerprint)
|
|
printer.Printf("TlsPrivateKey %d Verification URL: %s\n", idx, v.VerificationUrl)
|
|
printer.Printf("TlsPrivateKey %d Expiration: %d\n", idx, v.ExpirationTimestamp)
|
|
}
|
|
}
|
|
|
|
aggregateData := make(map[string]any)
|
|
var aggregateDataKeys []string
|
|
|
|
for _, data := range meta {
|
|
for k, v := range data {
|
|
aggregateDataKeys = append(aggregateDataKeys, k)
|
|
aggregateData[k] = v
|
|
}
|
|
}
|
|
sort.Strings(aggregateDataKeys)
|
|
for _, k := range aggregateDataKeys {
|
|
printer.Printf("%s: %v\n", cases.Title(language.AmericanEnglish).String(k), aggregateData[k])
|
|
}
|
|
|
|
// if analysis info is not nil, means the detector added key for analyzer and result is verified
|
|
if r.Result.AnalysisInfo != nil && r.Result.Verified {
|
|
printer.Printf("Analyze: Run `trufflehog analyze` to analyze this key's permissions\n")
|
|
}
|
|
|
|
fmt.Println("")
|
|
return nil
|
|
}
|
|
|
|
func structToMap(obj any) (m map[string]map[string]any, err error) {
|
|
data, err := json.Marshal(obj)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = json.Unmarshal(data, &m)
|
|
// Due to PostmanLocationType protobuf field being an enum, we want to be able to assign the string value of the enum to the field without needing to create another Protobuf field.
|
|
// To have the "UNKNOWN_POSTMAN = 0" value be assigned correctly to the field, we need to check if the Postman workspace ID or collection ID is filled since every secret
|
|
// in the Postman source should have a valid workspace ID or collection ID and the 0 value is considered nil for integers.
|
|
if m["Postman"]["workspace_uuid"] != nil || m["Postman"]["collection_id"] != nil {
|
|
if m["Postman"]["location_type"] == nil {
|
|
m["Postman"]["location_type"] = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN.String()
|
|
} else {
|
|
m["Postman"]["location_type"] = obj.(*source_metadatapb.MetaData_Postman).Postman.LocationType.String()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type outputFormat struct {
|
|
DetectorType,
|
|
DecoderType string
|
|
Verified bool
|
|
VerificationError error
|
|
Raw string
|
|
*source_metadatapb.MetaData
|
|
DetectorDescription string
|
|
}
|