109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
package devkit
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var scanSecretsRunner = runGitleaksDetect
|
|
|
|
// ScanSecrets runs gitleaks against the supplied directory and parses the CSV report.
|
|
func ScanSecrets(dir string) ([]Finding, error) {
|
|
output, err := scanSecretsRunner(dir)
|
|
findings, parseErr := parseGitleaksCSV(output)
|
|
if parseErr != nil {
|
|
return nil, parseErr
|
|
}
|
|
if err != nil && len(findings) == 0 {
|
|
return nil, err
|
|
}
|
|
return findings, nil
|
|
}
|
|
|
|
func runGitleaksDetect(dir string) ([]byte, error) {
|
|
bin, err := exec.LookPath("gitleaks")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd := exec.CommandContext(context.Background(), bin,
|
|
"detect",
|
|
"--no-banner",
|
|
"--no-color",
|
|
"--no-git",
|
|
"--source", dir,
|
|
"--report-format", "csv",
|
|
"--report-path", "-",
|
|
)
|
|
|
|
return cmd.Output()
|
|
}
|
|
|
|
func parseGitleaksCSV(data []byte) ([]Finding, error) {
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
reader := csv.NewReader(strings.NewReader(string(data)))
|
|
reader.FieldsPerRecord = -1
|
|
|
|
rows, err := reader.ReadAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(rows) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
header := make(map[string]int, len(rows[0]))
|
|
for idx, name := range rows[0] {
|
|
header[normalizeCSVHeader(name)] = idx
|
|
}
|
|
|
|
var findings []Finding
|
|
for _, row := range rows[1:] {
|
|
finding := Finding{
|
|
Path: csvField(row, header, "file", "path"),
|
|
Line: csvIntField(row, header, "startline", "line"),
|
|
Column: csvIntField(row, header, "startcolumn", "column"),
|
|
Rule: csvField(row, header, "ruleid", "rule", "name"),
|
|
Snippet: csvField(row, header, "match", "secret", "description", "message"),
|
|
}
|
|
|
|
if finding.Snippet == "" {
|
|
finding.Snippet = csvField(row, header, "filename")
|
|
}
|
|
findings = append(findings, finding)
|
|
}
|
|
|
|
return findings, nil
|
|
}
|
|
|
|
func normalizeCSVHeader(name string) string {
|
|
return strings.ToLower(strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(name, "_", ""), " ", "")))
|
|
}
|
|
|
|
func csvField(row []string, header map[string]int, names ...string) string {
|
|
for _, name := range names {
|
|
if idx, ok := header[name]; ok && idx < len(row) {
|
|
return strings.TrimSpace(row[idx])
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func csvIntField(row []string, header map[string]int, names ...string) int {
|
|
value := csvField(row, header, names...)
|
|
if value == "" {
|
|
return 0
|
|
}
|
|
|
|
n, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return n
|
|
}
|