Files

7.9 KiB

phase, plan, subsystem, tags, requirements, dependency-graph, tech-stack, key-files, decisions, metrics
phase plan subsystem tags requirements dependency-graph tech-stack key-files decisions metrics
05-verification-engine 02 verification-consent
legal
consent
embed
cli
verify
VRFY-01
VRFY-06
requires provides affects
pkg/storage settings table GetSetting/SetSetting (Phase 1)
cmd/root.go cobra command tree (Phase 1)
pkg/legal.Text() — compile-time embedded LEGAL.md
pkg/verify.EnsureConsent(db, in, out) — consent gate for --verify
keyhunter legal subcommand
verify.consent setting key contract
cmd/root.go (new subcommand registration)
added patterns
go:embed dual-location mirror (root LEGAL.md + pkg/legal/LEGAL.md), same pattern as providers/ vs pkg/providers/definitions/
Sticky-granted, transient-declined consent semantics (granted persists, declined re-prompts)
io.Reader/io.Writer injection for testable interactive prompts
created modified
LEGAL.md
pkg/legal/LEGAL.md
pkg/legal/legal.go
pkg/legal/legal_test.go
cmd/legal.go
pkg/verify/consent.go
pkg/verify/consent_test.go
.planning/phases/05-verification-engine/05-02-SUMMARY.md
cmd/root.go
LEGAL.md duplicated at repo root and pkg/legal/ because go:embed cannot traverse parents
verify.consent setting: 'granted' is sticky, 'declined' is not — users who initially refuse can change their mind on the next run without manual reset
EnsureConsent takes io.Reader/io.Writer rather than reading os.Stdin/os.Stdout directly, so tests can drive it with strings.NewReader + bytes.Buffer
Consent prompt text explicitly mentions CFAA, Computer Misuse Act, GDPR, provider ToS and directs users to `keyhunter legal` for the full document
duration completed_date tasks_completed files_created files_modified
~7m 2026-04-05 2 8 1

Phase 5 Plan 2: Legal Disclaimer & Consent Prompt Summary

One-liner: Shipped LEGAL.md embedded in the binary via go:embed, added a keyhunter legal subcommand, and implemented verify.EnsureConsent with sticky-granted / transient-declined semantics backed by the SQLite settings table.

What Was Built

LEGAL.md (repo root + pkg/legal mirror)

A 109-line user-facing disclaimer covering:

  • Purpose — what the verification feature does at a high level
  • What --verify Does — lightweight single HTTP call per key, no mutations, masked output
  • Legal Considerations — CFAA (18 U.S.C. § 1030), UK Computer Misuse Act 1990, EU Directive 2013/40/EU, provider ToS
  • Responsible Use — own keys, authorized engagements, own CI/CD only
  • Responsible Disclosure — security.txt, never publish, reasonable rotation window
  • Disclaimer — AS IS, no warranty, user bears sole responsibility
  • Consent Record — explains the first-run prompt and where the decision is persisted

The root copy is what users read; pkg/legal/LEGAL.md is a byte-identical mirror required by //go:embed (Go embed directives cannot traverse ..). This mirrors the Phase 1 decision to keep providers/*.yaml user-visible with a pkg/providers/definitions/*.yaml embed mirror.

pkg/legal package

//go:embed LEGAL.md
var legalMarkdown string
func Text() string { return legalMarkdown }

Two tests: TestText_NonEmpty (>500 bytes) and TestText_ContainsKeyPhrases (contains "CFAA", "Responsible Use", "Disclaimer"). Both pass.

cmd/legal.go registers a trivial legalCmd that prints legal.Text() to stdout. Wired in cmd/root.go alongside the existing scanCmd, providersCmd, configCmd. go run . legal | grep CFAA returns a match.

pkg/verify.EnsureConsent

func EnsureConsent(db *storage.DB, in io.Reader, out io.Writer) (bool, error)

Behavior:

  1. Reads verify.consent from the settings table.
  2. If previously "granted" → returns (true, nil) immediately, writes nothing.
  3. Otherwise writes a multi-line warning to out referencing "legal implications", CFAA, Computer Misuse Act, GDPR, provider ToS, and pointing the user to keyhunter legal.
  4. Reads one line from in, lowercases and trims.
  5. If the answer is "yes" → persists "granted" and returns (true, nil).
  6. Any other input (including "y", "no", empty) → persists "declined" and returns (false, nil).
  7. Declined is not sticky — the next call re-prompts (found && value != "granted" falls through to the prompt branch).

Six test cases cover the full decision matrix:

  • TestEnsureConsent_GrantedPrevious — sticky short-circuit with no prompt output
  • TestEnsureConsent_TypeYes — prompts, persists granted, prompt text contains "legal implications" and "keyhunter legal"
  • TestEnsureConsent_TypeYesUppercase — case-insensitive match
  • TestEnsureConsent_TypeNo — persists declined, returns false
  • TestEnsureConsent_Empty — empty input treated as decline
  • TestEnsureConsent_DeclinedNotSticky — seed declined + type yes → re-prompted and now granted

All 6 pass.

TDD Trace (Task 2)

  • RED (e5f7214) — wrote consent_test.go referencing ConsentSettingKey, ConsentGranted, ConsentDeclined, and EnsureConsent before the implementation existed. Build failed with "undefined:" errors for all 5 symbols. confirmed failing.
  • GREEN (d4c1403) — created consent.go with the constants and function. One iteration needed: the initial prompt wrapping split "legal implications" across two lines ("legal\nimplications"), which broke the literal substring assertion in TestEnsureConsent_TypeYes. Rewrapped the prompt text to keep the phrase contiguous on one line. All 6 tests green.
  • REFACTOR — none needed; implementation matched the plan's reference code modulo the prompt rewrap.

Verification

$ go build ./...
(clean)

$ go test ./pkg/legal/... ./pkg/verify/...
ok  github.com/salvacybersec/keyhunter/pkg/legal  0.002s
ok  github.com/salvacybersec/keyhunter/pkg/verify 0.331s

$ go run . legal | grep -c CFAA
1

Must-haves truths verified:

  • keyhunter legal prints the embedded LEGAL.md to stdout (grep -c CFAA returns 1)
  • First invocation prompts; "yes" (case-insensitive) proceeds; anything else aborts (6 tests)
  • Consent decision stored in settings and not re-prompted when granted (TestEnsureConsent_GrantedPrevious)
  • LEGAL.md ships via go:embed (grep -q "go:embed LEGAL.md" pkg/legal/legal.go — present)

Commits

Hash Type Summary
260e342 feat Add LEGAL.md, pkg/legal embed, and keyhunter legal command
e5f7214 test RED: failing consent tests
d4c1403 feat GREEN: implement EnsureConsent

Deviations from Plan

None — plan executed as written. One minor in-task iteration: the prompt text in the reference code happened to wrap "legal implications" across two Fprintln calls, which broke the test assertion on literal substrings. Rewrapped the prompt lines to keep "legal implications" contiguous. Functionally identical; no new behavior.

Out-of-Scope Note

During verification I observed an uncommitted modification to pkg/verify/verifier.go unrelated to this plan — it contains an in-progress HTTPVerifier implementation from what appears to be Plan 05-03 work (the git log shows 3ceccd9 test(05-03): add failing tests for HTTPVerifier single-key verification). I deliberately did not touch that file; it will be committed by plan 05-03 executor.

Self-Check: PASSED

All 8 claimed files exist on disk. All 3 claimed commits exist in git log.

Dependencies Satisfied For Next Plans

  • 05-03 (HTTP verifier) — can call verify.EnsureConsent at the top of the scan/verify entry point once this plan lands
  • 05-04 (verify CLI flag integration) — has ConsentSettingKey, ConsentGranted, ConsentDeclined constants to key off
  • Any future plan that needs to surface legal disclaimers or track user acknowledgements — can follow the same pkg/legal + dual-LEGAL.md embed pattern