Files
trufflehog/hack/checksecretparts/main.go
Amaan Ullah 0fa069c12f Enable errcheck and staticcheck for golangci-lint v2 and resolve all issues (#4924)
* enable errcheck and staticcheck for golangci-lint v2 and resolve all issues

* skip lint on intentional reference of deprecated DetectorType values
2026-05-15 17:07:14 +05:00

147 lines
3.8 KiB
Go

// checksecretparts is a static analysis tool that finds detector packages
// which construct detectors.Result values without populating the SecretParts
// field.
//
// It runs as WARNING by default (exit code 0 even when findings exist). Pass
// -fail to exit non-zero on findings; this is intended for use after every
// detector has been migrated to populate SecretParts (see the SecretParts
// design doc, step C).
//
// Usage:
//
// go run ./hack/checksecretparts [dir ...]
// go run ./hack/checksecretparts -fail ./pkg/detectors/...
//
// With no arguments, it scans ./pkg/detectors. The "/..." suffix is accepted
// for parity with go list but is stripped — the tool always recurses.
package main
import (
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
func main() {
var (
failOnFindings bool
quiet bool
)
flag.BoolVar(&failOnFindings, "fail", false, "exit 1 if any findings are reported (default: warning-only)")
flag.BoolVar(&quiet, "quiet", false, "suppress the summary line when no findings are reported")
flag.Usage = func() {
_, _ = fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [flags] [dir ...]\n", os.Args[0])
_, _ = fmt.Fprintln(flag.CommandLine.Output(), "\nFinds detector packages that construct detectors.Result without setting SecretParts.")
_, _ = fmt.Fprintln(flag.CommandLine.Output(), "\nFlags:")
flag.PrintDefaults()
}
flag.Parse()
roots := flag.Args()
if len(roots) == 0 {
roots = []string{"./pkg/detectors"}
}
pkgDirs, err := collectPackageDirs(roots)
if err != nil {
fmt.Fprintln(os.Stderr, "checksecretparts:", err)
os.Exit(2)
}
var findings []Finding
for _, dir := range pkgDirs {
f, err := CheckPackageDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "checksecretparts: %s: %v\n", dir, err)
os.Exit(2)
}
findings = append(findings, f...)
}
for _, f := range findings {
fmt.Printf("%s: warning: detectors.Result constructed without SecretParts\n", f.Position)
}
if len(findings) > 0 {
pkgs := map[string]struct{}{}
for _, f := range findings {
pkgs[f.Package] = struct{}{}
}
fmt.Fprintf(os.Stderr, "checksecretparts: %d finding(s) across %d package(s) constructing detectors.Result without SecretParts\n", len(findings), len(pkgs))
if failOnFindings {
os.Exit(1)
}
return
}
if !quiet {
fmt.Fprintf(os.Stderr, "checksecretparts: scanned %d package(s), no findings\n", len(pkgDirs))
}
}
// collectPackageDirs expands the caller-supplied roots into a sorted,
// deduplicated list of directories containing at least one non-test .go file.
func collectPackageDirs(roots []string) ([]string, error) {
seen := map[string]struct{}{}
var dirs []string
for _, r := range roots {
r = strings.TrimSuffix(r, "/...")
r = strings.TrimSuffix(r, "/")
info, err := os.Stat(r)
if err != nil {
return nil, fmt.Errorf("stat %s: %w", r, err)
}
if !info.IsDir() {
return nil, fmt.Errorf("%s is not a directory", r)
}
err = filepath.WalkDir(r, func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if !d.IsDir() {
return nil
}
name := d.Name()
if path != r && (name == "testdata" || name == "vendor" || strings.HasPrefix(name, ".")) {
return fs.SkipDir
}
hasGo, err := dirHasNonTestGoFile(path)
if err != nil {
return err
}
if !hasGo {
return nil
}
if _, ok := seen[path]; ok {
return nil
}
seen[path] = struct{}{}
dirs = append(dirs, path)
return nil
})
if err != nil {
return nil, err
}
}
return dirs, nil
}
func dirHasNonTestGoFile(dir string) (bool, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return false, err
}
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
if strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
return true, nil
}
}
return false, nil
}