---
phase: 07-import-cicd
plan: 02
subsystem: importer
tags: [importer, gitleaks, json, csv, normalization]
requires:
  - pkg/engine/finding.go
  - pkg/importer/importer.go
provides:
  - GitleaksImporter (gitleaks JSON)
  - GitleaksCSVImporter (gitleaks CSV)
  - normalizeGitleaksRuleID
  - buildGitleaksFinding
affects:
  - pkg/importer/
tech-stack:
  added: []
  patterns:
    - "Header-indexed CSV parsing for column-order resilience"
    - "Shared finding-builder helper across JSON/CSV paths"
    - "Suffix-stripping rule-ID normalization"
key-files:
  created:
    - pkg/importer/gitleaks.go
    - pkg/importer/gitleaks_test.go
    - pkg/importer/testdata/gitleaks-sample.json
    - pkg/importer/testdata/gitleaks-sample.csv
  modified: []
decisions:
  - "RuleID normalization strips suffixes (-api-key, -access-token, -secret-key, -secret, -token, -key) in that order; unknown patterns are lowercased only"
  - "CSV reader resolves columns by header name (not position) for forward-compat with Gitleaks column-order drift"
  - "StartLine parse errors in CSV are swallowed (LineNumber=0) — findings remain ingestible"
  - "Confidence fixed to 'medium' because Gitleaks does not verify keys"
metrics:
  duration: "~4m"
  completed: 2026-04-05
  tasks: 1
  files: 4
  tests: 8
requirements: [IMP-02]
---

# Phase 7 Plan 02: Gitleaks Importer Summary

One-liner: Gitleaks JSON and CSV output ingests into normalized engine.Finding records with provider-name normalization (e.g., `openai-api-key` -> `openai`) and header-indexed CSV parsing for column-order resilience.

## What was built

- `GitleaksImporter` (`Name() = "gitleaks"`) decodes a JSON array of Gitleaks finding records into `[]engine.Finding`.
- `GitleaksCSVImporter` (`Name() = "gitleaks-csv"`) reads CSV with a mandatory header row, resolves columns by name so column order drift does not break ingestion.
- `normalizeGitleaksRuleID` trims common Gitleaks suffixes (`-api-key`, `-access-token`, `-secret-key`, `-secret`, `-token`, `-key`) after lowercasing; unknown patterns pass through lowercased (e.g., `github-pat` -> `github-pat`).
- `buildGitleaksFinding` is a private helper used by both JSON and CSV paths so the two stay in lockstep: sets `SourceType="import:gitleaks"`, `Confidence="medium"`, `VerifyStatus="unverified"`, `Verified=false`, and falls back from `File` to `SymlinkFile` when blank.
- Fixtures with 3 records each (OpenAI / AWS / generic) in matching JSON and CSV shapes.

## Tests

All 8 tests under `go test ./pkg/importer/... -run Gitleaks -v` pass:

- `TestGitleaksImporter_Name` — Name() assertions for both importers
- `TestGitleaksImporter_JSON` — 3-record fixture, provider names, line numbers, SourceType, Confidence, masked key
- `TestGitleaksImporter_CSV` — same assertions against CSV fixture
- `TestGitleaksImporter_NormalizeRuleID` — table: openai/aws/anthropic/generic/github-pat + case variants
- `TestGitleaksImporter_EmptyArray` — `[]` returns empty slice, nil error
- `TestGitleaksImporter_EmptyCSV` — header-only returns empty slice, nil error
- `TestGitleaksImporter_InvalidJSON` — returns wrapped error
- `TestGitleaksImporter_SymlinkFallback` — uses SymlinkFile when File is blank

Full package `go test ./pkg/importer/...` also passes (trufflehog/dedup tests from the parallel 07-01 plan continue to pass alongside).

## Commits

- `bd8eb9b` feat(07-02): add Gitleaks JSON + CSV importers

## Deviations from Plan

None - plan executed exactly as written. The Importer interface file (`pkg/importer/importer.go`) was already present from the parallel wave-1 Plan 07-01, so this executor did not need to create it (plan explicitly allowed for either case).

## Self-Check: PASSED

- FOUND: pkg/importer/gitleaks.go
- FOUND: pkg/importer/gitleaks_test.go
- FOUND: pkg/importer/testdata/gitleaks-sample.json
- FOUND: pkg/importer/testdata/gitleaks-sample.csv
- FOUND commit: bd8eb9b
- Tests: 8/8 passing
- Build: `go build ./pkg/importer/...` clean
