From e5f93ef89c98eb261b4cbc97f9ff3962c3f95d7c Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Sun, 5 Apr 2026 23:42:57 +0300 Subject: [PATCH] docs(06-06): complete scan output wiring plan --- .planning/REQUIREMENTS.md | 2 +- .planning/ROADMAP.md | 2 +- .planning/STATE.md | 15 +-- .../06-output-reporting/06-06-SUMMARY.md | 110 ++++++++++++++++++ 4 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/06-output-reporting/06-06-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index f8da2bf..01c3d80 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -54,7 +54,7 @@ Requirements for initial release. Each maps to roadmap phases. - [x] **OUT-02**: JSON output format - [x] **OUT-03**: SARIF output format (CI/CD compatible) - [x] **OUT-04**: CSV output format -- [ ] **OUT-05**: Key masking by default (first 8 + last 4 chars) with --unmask flag for full keys +- [x] **OUT-05**: Key masking by default (first 8 + last 4 chars) with --unmask flag for full keys - [x] **OUT-06**: Exit codes: 0=clean, 1=keys found, 2=error ### Key Management diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0f9234b..6f1d5cc 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -147,7 +147,7 @@ Plans: - [x] 06-03-PLAN.md — SARIF 2.1.0 formatter with custom structs (rule dedup, level mapping) - [x] 06-04-PLAN.md — pkg/storage/queries.go: Filters, ListFindingsFiltered, GetFinding, DeleteFinding - [x] 06-05-PLAN.md — cmd/keys.go command tree: list/show/export/copy/delete/verify (KEYS-01..06) -- [ ] 06-06-PLAN.md — scan --output registry dispatch + exit codes 0/1/2 (OUT-05, OUT-06) +- [x] 06-06-PLAN.md — scan --output registry dispatch + exit codes 0/1/2 (OUT-05, OUT-06) ### Phase 7: Import Adapters & CI/CD Integration **Goal**: Users can import findings from TruffleHog and Gitleaks into KeyHunter's database, and use KeyHunter in pre-commit hooks and CI/CD pipelines with SARIF output uploadable to GitHub Security diff --git a/.planning/STATE.md b/.planning/STATE.md index 960958f..09d872a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 06-05-PLAN.md -last_updated: "2026-04-05T20:40:28.139Z" +stopped_at: Completed 06-06-PLAN.md +last_updated: "2026-04-05T20:42:54.085Z" last_activity: 2026-04-05 progress: total_phases: 18 - completed_phases: 5 + completed_phases: 6 total_plans: 34 - completed_plans: 33 + completed_plans: 34 percent: 20 --- @@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-04-04) ## Current Position Phase: 06 (output-reporting) — EXECUTING -Plan: 3 of 6 +Plan: 4 of 6 Status: Ready to execute Last activity: 2026-04-05 @@ -77,6 +77,7 @@ Progress: [██░░░░░░░░] 20% | Phase 06 P01 | 8m | 2 tasks | 7 files | | Phase 06 P03 | ~6m | 1 tasks | 2 files | | Phase 06-output-reporting P05 | 4min | 2 tasks | 3 files | +| Phase 06 P06 | 3min | 2 tasks | 3 files | ## Accumulated Context @@ -122,6 +123,6 @@ None yet. ## Session Continuity -Last session: 2026-04-05T20:40:28.135Z -Stopped at: Completed 06-05-PLAN.md +Last session: 2026-04-05T20:42:54.082Z +Stopped at: Completed 06-06-PLAN.md Resume file: None diff --git a/.planning/phases/06-output-reporting/06-06-SUMMARY.md b/.planning/phases/06-output-reporting/06-06-SUMMARY.md new file mode 100644 index 0000000..8e83083 --- /dev/null +++ b/.planning/phases/06-output-reporting/06-06-SUMMARY.md @@ -0,0 +1,110 @@ +--- +phase: 06-output-reporting +plan: 06 +subsystem: cli +tags: [cobra, cli, output-dispatch, exit-codes, sarif, csv, json, ci-cd] + +requires: + - phase: 06-output-reporting + provides: "formatter registry (table/json/csv/sarif), output.Options, ErrUnknownFormat" +provides: + - "scan --output=table|json|sarif|csv wired through output.Get()" + - "renderScanOutput helper for hermetic unit testing" + - "OUT-06 exit-code contract: 0 clean, 1 findings, 2 tool error" + - "version var in cmd package (ldflags-settable) feeding SARIF tool.driver.version" +affects: [07-import-hooks, 15-ci-cd-integration] + +tech-stack: + added: [] + patterns: + - "Extract RunE output dispatch into a small helper (renderScanOutput) so tests exercise the dispatch path without booting scanner/DB" + - "Error wrapping with fmt.Errorf(\"%w (valid: %s)\", ErrUnknownFormat, ...) keeps errors.Is checks intact while surfacing user guidance" + - "Root Execute differentiates exit 1 (findings, set inline in scan RunE) from exit 2 (any non-nil RunE error)" + +key-files: + created: + - cmd/scan_output_test.go + - .planning/phases/06-output-reporting/06-06-SUMMARY.md + modified: + - cmd/scan.go + - cmd/root.go + +key-decisions: + - "Exit 1 is emitted directly inside scan RunE via os.Exit(1) before RunE returns — this preserves the 1-vs-2 split without plumbing a sentinel error" + - "version is declared as a package-level var (default \"dev\") so goreleaser can override via -ldflags -X; SARIF tool.driver.version uses it" + - "renderScanOutput takes io.Writer (not os.Stdout) so tests can capture output without redirecting file descriptors" + - "Unknown-format error wraps output.ErrUnknownFormat AND appends the valid list in the same string — one error, both machine-readable and human-readable" + +requirements-completed: [OUT-05, OUT-06] + +duration: 3min +completed: 2026-04-05 +--- + +# Phase 06 Plan 06: Scan Output Wiring and Exit Codes Summary + +**Scan command now dispatches --output=table|json|sarif|csv through the formatter registry and honors the OUT-06 exit-code contract (0 clean, 1 findings, 2 error) for CI/CD consumers.** + +## Performance + +- **Duration:** ~3 min +- **Tasks:** 2 +- **Files modified:** 2 (cmd/scan.go, cmd/root.go) +- **Files created:** 1 (cmd/scan_output_test.go) + +## Accomplishments + +- Replaced inline `jsonFinding` switch in scan RunE with a single call to `output.Get(flagOutput)` → `Formatter.Format(...)`, unlocking SARIF and CSV for scan output. +- Extracted the dispatch into `renderScanOutput(findings, name, unmask, w io.Writer)` so the logic can be unit-tested without booting the full scanner/DB/consent pipeline. +- Implemented OUT-06 exit-code contract: + - `0`: scan returns cleanly, no findings + - `1`: `os.Exit(1)` at the tail of scan RunE when `len(findings) > 0` + - `2`: root `Execute()` now maps any non-nil `rootCmd.Execute()` error to `os.Exit(2)` (was 1) +- Added `version` package var + `versionString()` helper so SARIF `tool.driver.version` is populated and overridable via `-ldflags "-X github.com/salvacybersec/keyhunter/cmd.version=..."`. +- Updated `--output` help text to list all four formats. +- Four-test coverage in `cmd/scan_output_test.go`: + 1. `TestScanOutput_FormatNamesIncludeAll` — registry wiring guard + 2. `TestRenderScanOutput_UnknownReturnsError` — `errors.Is(err, output.ErrUnknownFormat)` + "valid:" substring + 3. `TestRenderScanOutput_JSONSucceeds` — valid `[]` JSON for empty findings + 4. `TestRenderScanOutput_TableEmpty` — "No API keys found" sentinel + +## Verification + +- `go build ./...` — clean +- `go vet ./cmd/...` — clean +- `go test ./... -count=1` — all packages green (cmd, engine, engine/sources, legal, output, providers, storage, verify) +- `go test ./cmd/... -run "TestScanOutput|TestRenderScanOutput" -count=1` — 4/4 pass + +## Commits + +| Task | Type | Hash | Message | +| ---- | ---- | ---- | ------- | +| 1 | feat | c9114e4 | wire scan --output to formatter registry and exit-code contract | +| 2 | test | cdf3c8a | cover scan output dispatch and unknown-format error | + +## Deviations from Plan + +None — plan executed exactly as written. The `renderScanOutput` helper called out as optional in Task 2 was added during Task 1 (the plan explicitly permitted this consolidation in Task 2 step 1). + +## Files Touched + +**Modified:** +- `cmd/scan.go` — removed inline `jsonFinding` struct + `encoding/json` import; added `io`/`strings` imports; replaced output switch with `renderScanOutput` helper; introduced `version` var + `versionString()`; updated flag help text. +- `cmd/root.go` — `Execute()` now exits with code 2 (was 1) on non-nil `rootCmd.Execute()` error, with a comment documenting the OUT-06 exit-code contract. + +**Created:** +- `cmd/scan_output_test.go` — four tests covering registration, unknown-format error, JSON dispatch, table dispatch. + +## Known Stubs + +None. All four registered formatters (table, json, csv, sarif) are fully wired and exercised by tests. + +## Self-Check: PASSED + +- cmd/scan.go contains `output.Get(name)` — verified (in renderScanOutput) +- cmd/scan.go contains `output.Options{` — verified +- cmd/scan.go no longer contains `jsonFinding` — verified +- cmd/root.go contains `os.Exit(2)` — verified +- cmd/scan_output_test.go exists with four tests — verified +- Task 1 commit c9114e4 present in git log — verified +- Task 2 commit cdf3c8a present in git log — verified