Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
499 changes: 229 additions & 270 deletions README.md

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions cmd/stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmd

import (
"testing"

"github.com/HodeTech/leakwatch/internal/detector"
"github.com/HodeTech/leakwatch/internal/meta"
"github.com/HodeTech/leakwatch/internal/verifier"
"github.com/stretchr/testify/assert"
)

// detectorsAtInit and verifiersAtInit snapshot the registries right after every
// package blank-imported by imports.go has run its init(), before any test can
// mutate the global registries. Capturing here makes the guard below
// independent of test ordering.
var (
detectorsAtInit []detector.Detector
verifiersAtInit []verifier.Verifier
)

func init() {
detectorsAtInit = detector.All()
verifiersAtInit = verifier.All()
}

// TestMetaCounts_MatchRuntime guards the published counts in internal/meta
// against what the binary actually registers. Every detector and verifier
// package is blank-imported by imports.go in this package, so both registries
// are fully populated here (the detector-only test in internal/detector cannot
// see verifiers, hence the cross-check lives here).
func TestMetaCounts_MatchRuntime(t *testing.T) {
assert.Len(t, detectorsAtInit, meta.Detectors,
"meta.Detectors drifted from detector.All(); update internal/meta then run `go generate ./...`")
assert.Len(t, verifiersAtInit, meta.Verifiers,
"meta.Verifiers drifted from verifier.All(); update internal/meta then run `go generate ./...`")
}
72 changes: 42 additions & 30 deletions docs/05-ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Leakwatch - Phased Development Roadmap

> **Document Version:** 7.0
> **Document Version:** 7.1
> **Date:** 2026-04-09
> **Status:** Approved
> **Last Updated:** 2026-05-24
> **Last Updated:** 2026-05-25
Comment on lines +3 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document metadata dates are inconsistent.

Line 4 (Date: 2026-04-09) conflicts with Line 6 (Last Updated: 2026-05-25) after the v7.1 bump. Align these dates to avoid version-history confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/05-ROADMAP.md` around lines 3 - 6, The document metadata contains
inconsistent dates: "Date: 2026-04-09" and "Last Updated: 2026-05-25" for
Document Version 7.1; update the Date field to match the Last Updated value (or
vice versa if you prefer) so both "Date" and "Last Updated" are identical for
"Document Version: 7.1" to avoid version-history confusion—modify the Date line
in the header to "2026-05-25" (or change Last Updated to "2026-04-09" if that's
the intended authoritative date).


---

Expand All @@ -23,15 +23,25 @@
| Phase 8.2 — CLI UX Improvements | Completed | `v1.3.2` | 2026-03-25 |
| Phase 8.3 — Scan Summary + Security | Completed | `v1.4.0` | 2026-04-08 |
| Phase 8.4 — False Positive Reduction | Completed | `v1.5.0` | 2026-04-09 |
| Phase 9 — Detection Accuracy & FP Reduction | Planned | `v1.6.0` | — |
| Phase 10 — Detector Library Expansion | Planned | `v1.7.0` | — |
| Phase 11 — Verification Depth & Credential Impact | Planned | `v1.8.0` | — |
| Phase 12 — Source Expansion (Confluence/Jira, org-scale) | Planned | `v1.9.0` | — |
| Phase 13 — Secrets Inventory | Planned | `v1.10.0` | — |
| Phase 14 — Honeytokens | Planned | `v1.11.0` | — |
| Phase 8.5 — GitHub Marketplace Action & Distribution | Completed | `v1.6.0` | 2026-05-25 |
| Phase 9 — Detection Accuracy & FP Reduction | Planned | `v1.7.0` | — |
| Phase 10 — Detector Library Expansion | Planned | `v1.8.0` | — |
| Phase 11 — Verification Depth & Credential Impact | Planned | `v1.9.0` | — |
| Phase 12 — Source Expansion (Confluence/Jira, org-scale) | Planned | `v1.10.0` | — |
| Phase 13 — Secrets Inventory | Planned | `v1.11.0` | — |
| Phase 14 — Honeytokens | Planned | `v1.12.0` | — |

> **Prioritization note (v7.0):** the planned sequence is re-ordered so the work that most strengthens the core promise — accurate, verified, low-noise findings — comes first. Detection accuracy and false-positive reduction (Phase 9), broader coverage of high-blast-radius credential types (Phase 10), and deeper verification with credential-impact insight (Phase 11) precede new scan sources (Phase 12) and the inventory/honeytoken platform features (Phases 13–14). Rationale is detailed in [Planned Work — Prioritization](#planned-work--prioritization).

### v1.6.0 Highlights

- **GitHub Marketplace Action** — `uses: HodeTech/Leakwatch@v1`. Composite action that installs a prebuilt, checksum-verified binary (no Go toolchain), runs a scan, maps exit codes, writes a job summary, supports PR-diff scanning (`scan-diff`), and can upload SARIF to Code Scanning. Linux & macOS runners.
- **New `github` output format** — `--format github` emits workflow commands so findings appear as inline annotations on pull requests
- **Config keys now take effect** — `custom-rules`, `verification.*`, `filter.exclude-detectors`, and `output.severity-threshold` from `.leakwatch.yaml` are wired into the scan (previously documented but no-ops); `scan repos` honors all scan config too
- **Accurate locations & inline ignore** — findings report real line numbers; `# leakwatch:ignore[:<detector-id>]` markers are honored; SARIF results carry location-stable `partialFingerprints`
- **Distribution** — multi-arch GHCR image (public), Homebrew tap (`HodeTech/tap/leakwatch`), and cross-platform release archives with checksums
- **Security hardening** — credentials redacted in Git URLs and verifier transport errors; the composite action isolates inputs via env (no shell injection) and honors the leakwatch exit code

### v1.5.0 Highlights

- **False positive reduction** — improved filtering for lock files (`package-lock.json`, `yarn.lock`, etc.), test fixtures, and placeholder patterns
Expand Down Expand Up @@ -84,7 +94,7 @@

## Roadmap Overview

Leakwatch development proceeds in incremental phases, each building on the previous one and each producing a usable deliverable. Phases 1–8 (through `v1.5.0`) are complete; Phases 9–14 are the planned forward path, sequenced by leverage on the product's core promise — see [Planned Work — Prioritization](#planned-work--prioritization).
Leakwatch development proceeds in incremental phases, each building on the previous one and each producing a usable deliverable. Phases 1–8 (through `v1.6.0`) are complete; Phases 9–14 are the planned forward path, sequenced by leverage on the product's core promise — see [Planned Work — Prioritization](#planned-work--prioritization).

```mermaid
gantt
Expand Down Expand Up @@ -119,12 +129,13 @@ gantt
GitHub Action & Docker :done, f5b, after f5a, 2w
v1.0.0 Release :milestone, after f5b, 0d

section Completed v1.1-v1.5
section Completed v1.1-v1.6
Remediation, Slack, Verifiers :done, f6, after f5b, 6w
UX, Security, FP reduction :done, f8, after f6, 6w
Marketplace Action & distrib. :done, f85, after f8, 3w

section Planned v1.6.0+
Detection accuracy & FP :p9, after f8, 5w
section Planned v1.7.0+
Detection accuracy & FP :p9, after f85, 5w
Detector library expansion :p10, after p9, 6w
Verification depth & impact :p11, after p10, 6w
Source expansion :p12, after p11, 6w
Expand Down Expand Up @@ -411,7 +422,7 @@ The product's core promise is **accurate, verified, low-noise secret findings**.

**Goal:** Make accuracy a measurable strength. Raise detector precision and recall, cut false positives across the board, and ensure every documented detection/verification behavior actually fires. This phase improves the quality of every existing scan without adding new surfaces.

**Duration:** 4-5 weeks | **Version:** `v1.6.0` | **Status:** Planned
**Duration:** 4-5 weeks | **Version:** `v1.7.0` | **Status:** Planned

### Deliverables

Expand Down Expand Up @@ -440,15 +451,15 @@ The product's core promise is **accurate, verified, low-noise secret findings**.

### Exit Criteria

GitHub Release published with `v1.6.0` tag.
GitHub Release published with `v1.7.0` tag.

---

## Phase 10: Detector Library Expansion — PLANNED

**Goal:** Grow coverage of frequently-leaked, high-blast-radius credential types, prioritizing secrets whose exposure causes the most damage. Every new detector with a public verification endpoint ships with its verifier.

**Duration:** 5-6 weeks | **Version:** `v1.7.0` | **Status:** Planned
**Duration:** 5-6 weeks | **Version:** `v1.8.0` | **Status:** Planned

### Deliverables

Expand All @@ -471,15 +482,15 @@ GitHub Release published with `v1.6.0` tag.

### Exit Criteria

GitHub Release published with `v1.7.0` tag.
GitHub Release published with `v1.8.0` tag.

---

## Phase 11: Verification Depth & Credential Impact — PLANNED

**Goal:** Deepen the verification differentiator. Harden the verification engine, verify more credential classes, and — for live secrets — tell users what the credential can actually reach so they can triage blast radius.

**Duration:** 5-6 weeks | **Version:** `v1.8.0` | **Status:** Planned
**Duration:** 5-6 weeks | **Version:** `v1.9.0` | **Status:** Planned

### Deliverables

Expand All @@ -501,15 +512,15 @@ GitHub Release published with `v1.7.0` tag.

### Exit Criteria

GitHub Release published with `v1.8.0` tag.
GitHub Release published with `v1.9.0` tag.

---

## Phase 12: Source Expansion — PLANNED

**Goal:** Reach secrets wherever they live — collaboration platforms and org-scale code hosting — now that the detection/verification core is strong.

**Duration:** 5-6 weeks | **Version:** `v1.9.0` | **Status:** Planned
**Duration:** 5-6 weeks | **Version:** `v1.10.0` | **Status:** Planned

### Deliverables

Expand Down Expand Up @@ -538,15 +549,15 @@ GitHub Release published with `v1.8.0` tag.

### Exit Criteria

GitHub Release published with `v1.9.0` tag.
GitHub Release published with `v1.10.0` tag.

---

## Phase 13: Secrets Inventory — PLANNED

**Goal:** Persistent SQLite-based inventory tracking secrets across scans.

**Duration:** 4-5 weeks | **Version:** `v1.10.0` | **Status:** Planned
**Duration:** 4-5 weeks | **Version:** `v1.11.0` | **Status:** Planned

### Deliverables

Expand All @@ -573,15 +584,15 @@ GitHub Release published with `v1.9.0` tag.

### Exit Criteria

GitHub Release published with `v1.10.0` tag.
GitHub Release published with `v1.11.0` tag.

---

## Phase 14: Honeytokens — PLANNED

**Goal:** Generate and deploy decoy credentials that alert on unauthorized use.

**Duration:** 3-4 weeks | **Version:** `v1.11.0` | **Status:** Planned
**Duration:** 3-4 weeks | **Version:** `v1.12.0` | **Status:** Planned

### Deliverables

Expand All @@ -607,7 +618,7 @@ GitHub Release published with `v1.10.0` tag.

### Exit Criteria

GitHub Release published with `v1.11.0` tag.
GitHub Release published with `v1.12.0` tag.

---

Expand Down Expand Up @@ -753,12 +764,13 @@ Source packages (no formal standard, but visible gaps):
| `v1.3.2` | Phase 8.2 | CLI UX improvements | 2026-03-25 |
| `v1.4.0` | Phase 8.3 | Scan summary, `init` command, colored table, security patches | 2026-04-08 |
| `v1.5.0` | Phase 8.4 | False positive reduction, ADO.NET support | 2026-04-09 |
| `v1.6.0` | Phase 9 | Detection accuracy & false-positive reduction | — |
| `v1.7.0` | Phase 10 | Detector library expansion | — |
| `v1.8.0` | Phase 11 | Verification depth & credential impact | — |
| `v1.9.0` | Phase 12 | Source expansion (Confluence/Jira, org-scale) | — |
| `v1.10.0` | Phase 13 | Secrets inventory (SQLite) | — |
| `v1.11.0` | Phase 14 | Honeytokens | — |
| `v1.6.0` | Phase 8.5 | GitHub Marketplace Action, `github` output format, config wiring | 2026-05-25 |
| `v1.7.0` | Phase 9 | Detection accuracy & false-positive reduction | — |
| `v1.8.0` | Phase 10 | Detector library expansion | — |
| `v1.9.0` | Phase 11 | Verification depth & credential impact | — |
| `v1.10.0` | Phase 12 | Source expansion (Confluence/Jira, org-scale) | — |
| `v1.11.0` | Phase 13 | Secrets inventory (SQLite) | — |
| `v1.12.0` | Phase 14 | Honeytokens | — |
| `v2.x.x` | Future | ML detection, SaaS platform, Vault | Ongoing |

> **Note on v1.1.0 / v1.2.0:** Phase 6 (Remediation Guidance) and Phase 7 (Slack Scanning) were completed and merged into `main`, but no `v1.1.0` or `v1.2.0` git tags were ever created. The features shipped as part of the `v1.3.0` release. The version slots are preserved here to keep the phase-to-version mapping consistent.
Expand Down
89 changes: 89 additions & 0 deletions docs/assets/banner.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!doctype html>
<!-- Source for docs/assets/banner.png. Re-render (2x) with:
chrome --headless --screenshot=docs/assets/banner.png \
--window-size=1200,480 --force-device-scale-factor=2 \
--default-background-color=00000000 --virtual-time-budget=3000 \
file://$PWD/docs/assets/banner.html -->
<html>
<head>
<meta charset="utf-8">
<style>
@font-face { font-family:'Space Grotesk'; font-weight:700; font-display:block;
src:url('../../site/assets/fonts/space-grotesk-700-latin.woff2') format('woff2'); }
@font-face { font-family:'JetBrains Mono'; font-weight:400; font-display:block;
src:url('../../site/assets/fonts/jetbrains-mono-400-latin.woff2') format('woff2'); }
@font-face { font-family:'JetBrains Mono'; font-weight:700; font-display:block;
src:url('../../site/assets/fonts/jetbrains-mono-700-latin.woff2') format('woff2'); }

* { margin:0; padding:0; box-sizing:border-box; }
body { width:1200px; height:480px; overflow:hidden; }
.banner {
width:1200px; height:480px; position:relative; overflow:hidden;
background:linear-gradient(135deg, #0c0d10 0%, #0a0b0d 100%);
font-family:'Space Grotesk', sans-serif; color:#f1eee5;
}
.grid {
position:absolute; inset:0;
background-image:
linear-gradient(#23272e 1px, transparent 1px),
linear-gradient(90deg, #23272e 1px, transparent 1px);
background-size:40px 40px; opacity:.35;
}
/* Thin brand accent rule (replaces the old CLASSIFIED bar). */
.accent {
position:absolute; top:0; left:0; right:0; height:5px;
background:linear-gradient(90deg, #e6394d 0%, #ff6377 100%);
}
.content { position:absolute; left:64px; right:64px; top:88px; }
.brand { display:flex; align-items:center; gap:18px; margin-bottom:38px; }
.brand svg { width:50px; height:50px; }
.wordmark { font-weight:700; font-size:54px; letter-spacing:-1px; line-height:1; }
.wordmark b { color:#ff6377; font-weight:700; }
.headline { font-weight:700; font-size:52px; letter-spacing:-1.5px; line-height:1.12; }
.headline .t { color:#ff6377; }
.subtitle {
font-family:'JetBrains Mono', monospace; font-weight:400; font-size:22px;
color:#8b9098; margin-top:24px; line-height:1.5;
}
.stats {
position:absolute; left:64px; bottom:74px;
font-family:'JetBrains Mono', monospace; font-weight:700; font-size:22px;
display:flex; gap:34px; align-items:center;
}
.stats .sep { color:#3a3f47; font-weight:400; }
.g { color:#34d39a; } .y { color:#f0b429; } .b { color:#5aa9e6; } .r { color:#ff6377; }
.footer {
position:absolute; left:64px; bottom:34px;
font-family:'JetBrains Mono', monospace; font-weight:400; font-size:18px; color:#5b616b;
}
</style>
</head>
<body>
<div class="banner">
<div class="grid"></div>
<div class="accent"></div>
<div class="content">
<div class="brand">
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="2.5" width="27" height="27" rx="6" fill="#16191e" stroke="#e6394d" stroke-width="1.8"/>
<rect x="7.5" y="10.6" width="17" height="3.6" rx="1.2" fill="#0a0b0d"/>
<rect x="7.5" y="16.6" width="10.5" height="3.6" rx="1.2" fill="#0a0b0d"/>
<circle cx="22" cy="18.9" r="2.7" fill="none" stroke="#34d39a" stroke-width="1.8"/>
</svg>
<span class="wordmark">Leak<b>watch</b></span>
</div>
<div class="headline">Find leaked secrets<br>before <span class="t">attackers</span> do.</div>
<div class="subtitle">Detect · verify · report — across code, git history, containers &amp; cloud.</div>
</div>
<!-- stats:begin — managed by internal/meta/statsgen; run `go generate ./...` -->
<div class="stats">
<span class="g">63 detectors</span><span class="sep">·</span>
<span class="y">54 live verifiers</span><span class="sep">·</span>
<span class="b">6 sources</span><span class="sep">·</span>
<span class="r">5 output formats</span>
</div>
<!-- stats:end -->
<div class="footer">github.com/HodeTech/Leakwatch · MIT</div>
</div>
</body>
</html>
Binary file added docs/assets/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 6 additions & 7 deletions internal/detector/registry_count_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ package detector_test
// rules at runtime (detector.RegisterIfAbsent) and is therefore not part of
// the compile-time count.
//
// If you add or remove a detector, update wantDetectorCount below and keep the
// blank-import block in sync with cmd/imports.go.
// If you add or remove a detector, update internal/meta.Detectors (the single
// source of truth for the published count) and keep the blank-import block in
// sync with cmd/imports.go.

import (
"testing"

"github.com/HodeTech/leakwatch/internal/detector"
"github.com/HodeTech/leakwatch/internal/meta"
"github.com/stretchr/testify/assert"

// Each blank import runs the package's init(), registering its detector(s)
Expand Down Expand Up @@ -88,9 +90,6 @@ import (
_ "github.com/HodeTech/leakwatch/internal/detector/vercel" // register vercel detector
)

// wantDetectorCount is the expected number of compile-time registered detectors.
const wantDetectorCount = 63

// registeredAtInit snapshots the registry right after every blank-imported
// detector package has run its init(), but before any test can mutate the
// global registry (the in-package registry_test.go calls detector.Reset()).
Expand All @@ -102,8 +101,8 @@ func init() {
}

func TestAll_RegisteredDetectorCount_MatchesGolden(t *testing.T) {
assert.Len(t, registeredAtInit, wantDetectorCount,
"compile-time registered detector count drifted; update wantDetectorCount and cmd/imports.go together")
assert.Len(t, registeredAtInit, meta.Detectors,
"compile-time registered detector count drifted; update internal/meta.Detectors and cmd/imports.go together")

// Every registered detector must have a unique, non-empty ID.
ids := make(map[string]bool, len(registeredAtInit))
Expand Down
Loading
Loading