--- phase: 07-import-cicd plan: 02 type: execute wave: 1 depends_on: [] files_modified: - pkg/importer/gitleaks.go - pkg/importer/gitleaks_test.go - pkg/importer/testdata/gitleaks-sample.json - pkg/importer/testdata/gitleaks-sample.csv autonomous: true requirements: [IMP-02] must_haves: truths: - "Gitleaks JSON output parses to []engine.Finding" - "Gitleaks CSV output parses to []engine.Finding" - "Gitleaks RuleID normalizes to KeyHunter lowercase provider names" artifacts: - path: pkg/importer/gitleaks.go provides: "GitleaksImporter (JSON) + GitleaksCSVImporter" contains: "func (GitleaksImporter) Import" - path: pkg/importer/testdata/gitleaks-sample.json provides: "Gitleaks JSON fixture" - path: pkg/importer/testdata/gitleaks-sample.csv provides: "Gitleaks CSV fixture" key_links: - from: pkg/importer/gitleaks.go to: pkg/engine/finding.go via: "constructs engine.Finding from Gitleaks records" pattern: "engine\\.Finding\\{" --- Add Gitleaks adapters (JSON + CSV) to pkg/importer implementing the Importer interface from Plan 07-01. Purpose: Gitleaks is the second major secret scanner; ingesting its output (both JSON and CSV flavors) lets users unify findings (IMP-02). Output: GitleaksImporter, GitleaksCSVImporter, tests, fixtures. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/07-import-cicd/07-CONTEXT.md @pkg/engine/finding.go Contract defined by Plan 07-01 in pkg/importer/importer.go: ```go type Importer interface { Name() string Import(r io.Reader) ([]engine.Finding, error) } ``` NOTE: If pkg/importer/importer.go does not yet exist at execution time (waves 1 run in parallel), this plan's executor MUST first create that file with the interface above. The TruffleHog plan (07-01) will reuse the same file. Task 1: Gitleaks JSON + CSV parsers with fixtures pkg/importer/gitleaks.go, pkg/importer/gitleaks_test.go, pkg/importer/testdata/gitleaks-sample.json, pkg/importer/testdata/gitleaks-sample.csv - GitleaksImporter.Import parses a JSON array of records with fields: Description, StartLine, EndLine, StartColumn, EndColumn, Match, Secret, File, SymlinkFile, Commit, Entropy (float), Author, Email, Date, Message, Tags ([]string), RuleID, Fingerprint - Each JSON record maps to engine.Finding: ProviderName = normalizeGitleaksRuleID(RuleID) // "openai-api-key" -> "openai", "aws-access-token" -> "aws", "generic-api-key" -> "generic" KeyValue = Secret KeyMasked = engine.MaskKey(Secret) Confidence = "medium" (Gitleaks doesn't verify) Source = File (fallback SymlinkFile) SourceType = "import:gitleaks" LineNumber = StartLine DetectedAt = time.Now() Verified = false, VerifyStatus = "unverified" - GitleaksCSVImporter.Import reads CSV via encoding/csv. Header row mandatory; column order follows gitleaks default: RuleID,Commit,File,SymlinkFile,Secret,Match,StartLine,EndLine,StartColumn,EndColumn,Author,Message,Date,Email,Fingerprint,Tags. Parse header to index map so column order resilience is not required but header names must match. - normalizeGitleaksRuleID trims common suffixes: "-api-key", "-access-token", "-token", "-secret", "-key". E.g., "openai-api-key" -> "openai", "anthropic-api-key" -> "anthropic", "aws-access-token" -> "aws", "github-pat" -> "github-pat" (no suffix match, kept as-is but lowercased). - Empty array / empty CSV (header only): returns empty slice nil error. - Malformed JSON or CSV: returns wrapped error. - Name() methods return "gitleaks" and "gitleaks-csv" respectively. If pkg/importer/importer.go does not exist yet (parallel execution with 07-01), create it first with the Importer interface (see above). Create pkg/importer/gitleaks.go: - Package `importer`; imports: encoding/csv, encoding/json, fmt, io, strconv, strings, time, engine pkg. - Define `type GitleaksImporter struct{}` and `type GitleaksCSVImporter struct{}`. - Define `type gitleaksRecord struct` with JSON tags matching the Gitleaks schema above. - Implement `(GitleaksImporter) Name() string` -> "gitleaks"; Import decodes JSON array, loops building engine.Finding. - Implement `(GitleaksCSVImporter) Name() string` -> "gitleaks-csv"; Import uses csv.NewReader(r), reads header row, builds `map[string]int` of column index, then loops reading records. Parses StartLine via strconv.Atoi; swallows parse errors by setting LineNumber=0. - Implement `normalizeGitleaksRuleID(id string) string`: ```go id = strings.ToLower(id) suffixes := []string{"-api-key", "-access-token", "-secret-key", "-secret", "-token", "-key"} for _, s := range suffixes { if strings.HasSuffix(id, s) { return strings.TrimSuffix(id, s) } } return id ``` - Helper `buildGitleaksFinding(ruleID, secret, file, symlink string, startLine int) engine.Finding` shared between JSON and CSV paths: - source := file; if source == "" { source = symlink } - returns engine.Finding{ProviderName: normalizeGitleaksRuleID(ruleID), KeyValue: secret, KeyMasked: engine.MaskKey(secret), Confidence: "medium", Source: source, SourceType: "import:gitleaks", LineNumber: startLine, DetectedAt: time.Now(), VerifyStatus: "unverified"} Create pkg/importer/testdata/gitleaks-sample.json — JSON array with 3 records covering: - {"RuleID":"openai-api-key","Secret":"sk-proj-1234567890abcdef1234","File":"config/app.yml","StartLine":12, ...} - {"RuleID":"aws-access-token","Secret":"AKIAIOSFODNN7EXAMPLE","File":"terraform/main.tf","StartLine":55, ...} - {"RuleID":"generic-api-key","Secret":"xoxp-abcdefghijklmnopqrstuvwxyz","File":"scripts/deploy.sh","StartLine":3, ...} Create pkg/importer/testdata/gitleaks-sample.csv with header row and the same 3 rows (in Gitleaks default column order). Create pkg/importer/gitleaks_test.go: - TestGitleaksImporter_JSON: loads fixture, expects 3 findings, findings[0].ProviderName=="openai", findings[1].ProviderName=="aws", Source/LineNumber correct. - TestGitleaksImporter_CSV: loads CSV fixture, same 3 findings, same assertions. - TestGitleaksImporter_NormalizeRuleID: table — {"openai-api-key","openai"}, {"aws-access-token","aws"}, {"anthropic-api-key","anthropic"}, {"generic-api-key","generic"}, {"github-pat","github-pat"}. - TestGitleaksImporter_EmptyArray, TestGitleaksImporter_EmptyCSV (header only). - TestGitleaksImporter_InvalidJSON returns error. - Name() assertions for both importers. cd /home/salva/Documents/apikey && go test ./pkg/importer/... -run Gitleaks -v - GitleaksImporter + GitleaksCSVImporter implemented - JSON + CSV fixtures committed - All Gitleaks tests pass - go build ./pkg/importer/... succeeds go test ./pkg/importer/... passes. Both JSON and CSV paths produce identical Finding slices from equivalent fixtures. Gitleaks output (JSON and CSV) ingests into normalized engine.Finding records with correct provider name mapping and line number extraction. After completion, create `.planning/phases/07-import-cicd/07-02-SUMMARY.md`.