mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-05-16 05:29:11 +02:00
3a4c230aa7
Complete architectural overhaul. Replaces the v0.1 monolithic scanner with an event-driven pipeline of auto-registered modules. Foundation (internal/): - eventbus: typed pub/sub, 20 event types, race-safe, drop counter - module: registry with phase-based selection - store: thread-safe host store with per-host locks + deep-copy reads - pipeline: coordinator with phase barriers + panic recovery - config: 5 scan profiles + 3 AI tiers + YAML loader + auto-discovery Modules (26 auto-registered across 6 phases): - Discovery: passive (26 sources), bruteforce, recursive, AXFR, GitHub dorks, CT streaming, permutation, reverse DNS, vhost, ASN, supply chain (npm + PyPI) - Enrichment: HTTP probe + tech fingerprint + TLS appliance ID, ports - Analysis: security checks, takeover (110+ sigs), cloud, JavaScript, GraphQL, JWT, headers (OWASP), HTTP smuggling, AI cascade, Nuclei - Reporting: TXT/JSON/CSV writer + AI scan brief AI layer (internal/ai/ + internal/modules/ai/): - Three profiles: lean (16 GB), balanced (32 GB MoE), heavy (64 GB) - Six event-driven handlers: CVE, JS file, HTTP response, secret filter, multi-agent vuln enrichment, anomaly + executive report - Content-hash cache dedups Ollama calls across hosts - Auto-pull of missing models via /api/pull with streaming progress - End-of-scan AI SCAN BRIEF in terminal with top chains + next actions Nuclei compat layer (internal/nucleitpl/): - Executes ~13k community templates (HTTP subset) - Auto-download of nuclei-templates ZIP to ~/.god-eye/nuclei-templates - Scope filter rejects off-host templates (eliminates OSINT FPs) Operations: - Interactive wizard (internal/wizard/) — zero-flag launch - LivePrinter (internal/tui/) — colorized event stream - Diff engine + scheduler (internal/diff, internal/scheduler) for continuous ASM monitoring with webhook alerts - Proxy support (internal/proxyconf/): http / https / socks5 / socks5h + basic auth Fixes #1 — native SOCKS5 / Tor compatibility via --proxy flag. 185 unit tests across 15 packages, all race-detector clean.
155 lines
4.5 KiB
Go
155 lines
4.5 KiB
Go
package diff
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"god-eye/internal/store"
|
|
)
|
|
|
|
func TestCompute_NewHost(t *testing.T) {
|
|
oldHosts := []*store.Host{}
|
|
newHosts := []*store.Host{{Subdomain: "api.example.com"}}
|
|
r := Compute("example.com", oldHosts, newHosts, time.Now(), time.Now())
|
|
if len(r.Changes) != 1 || r.Changes[0].Kind != "new_host" {
|
|
t.Errorf("expected 1 new_host change, got %+v", r.Changes)
|
|
}
|
|
if !r.HasMeaningful() {
|
|
t.Error("new_host should be meaningful")
|
|
}
|
|
}
|
|
|
|
func TestCompute_RemovedHost(t *testing.T) {
|
|
oldHosts := []*store.Host{{Subdomain: "old.example.com"}}
|
|
newHosts := []*store.Host{}
|
|
r := Compute("example.com", oldHosts, newHosts, time.Now(), time.Now())
|
|
if len(r.Changes) != 1 || r.Changes[0].Kind != "removed_host" {
|
|
t.Errorf("expected removed_host, got %+v", r.Changes)
|
|
}
|
|
if !r.HasMeaningful() {
|
|
t.Error("removed_host should be meaningful")
|
|
}
|
|
}
|
|
|
|
func TestCompute_StatusChange(t *testing.T) {
|
|
oldH := &store.Host{Subdomain: "a.example.com", StatusCode: 200}
|
|
newH := &store.Host{Subdomain: "a.example.com", StatusCode: 401}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
if len(r.Changes) != 1 || r.Changes[0].Kind != "status_change" {
|
|
t.Errorf("expected status_change, got %+v", r.Changes)
|
|
}
|
|
if r.Changes[0].Before != "200" || r.Changes[0].After != "401" {
|
|
t.Errorf("wrong before/after: %+v", r.Changes[0])
|
|
}
|
|
}
|
|
|
|
func TestCompute_IPDelta(t *testing.T) {
|
|
oldH := &store.Host{Subdomain: "a.example.com", IPs: []string{"1.1.1.1"}}
|
|
newH := &store.Host{Subdomain: "a.example.com", IPs: []string{"1.1.1.1", "2.2.2.2"}}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
found := false
|
|
for _, c := range r.Changes {
|
|
if c.Kind == "new_ip" && c.After == "2.2.2.2" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected new_ip change, got %+v", r.Changes)
|
|
}
|
|
}
|
|
|
|
func TestCompute_NewVuln(t *testing.T) {
|
|
oldH := &store.Host{Subdomain: "a.example.com"}
|
|
newH := &store.Host{
|
|
Subdomain: "a.example.com",
|
|
Vulnerabilities: []store.Vulnerability{
|
|
{ID: "xss", Title: "Reflected XSS", Severity: "high"},
|
|
},
|
|
}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
found := false
|
|
for _, c := range r.Changes {
|
|
if c.Kind == "new_vuln" && c.After == "Reflected XSS" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected new_vuln change, got %+v", r.Changes)
|
|
}
|
|
if !r.HasMeaningful() {
|
|
t.Error("new_vuln must be meaningful")
|
|
}
|
|
}
|
|
|
|
func TestCompute_ClearedVuln(t *testing.T) {
|
|
oldH := &store.Host{
|
|
Subdomain: "a.example.com",
|
|
Vulnerabilities: []store.Vulnerability{
|
|
{ID: "git-exposed", Title: "Git Exposed", Severity: "critical"},
|
|
},
|
|
}
|
|
newH := &store.Host{Subdomain: "a.example.com"}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
found := false
|
|
for _, c := range r.Changes {
|
|
if c.Kind == "cleared_vuln" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected cleared_vuln, got %+v", r.Changes)
|
|
}
|
|
}
|
|
|
|
func TestCompute_NewTakeover(t *testing.T) {
|
|
oldH := &store.Host{Subdomain: "a.example.com"}
|
|
newH := &store.Host{
|
|
Subdomain: "a.example.com",
|
|
Takeover: &store.Takeover{Service: "GitHub Pages"},
|
|
}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
found := false
|
|
for _, c := range r.Changes {
|
|
if c.Kind == "new_takeover" && c.After == "GitHub Pages" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected new_takeover, got %+v", r.Changes)
|
|
}
|
|
if !r.HasMeaningful() {
|
|
t.Error("new_takeover must be meaningful")
|
|
}
|
|
}
|
|
|
|
func TestCompute_NoChange(t *testing.T) {
|
|
h := &store.Host{
|
|
Subdomain: "a.example.com",
|
|
IPs: []string{"1.1.1.1"},
|
|
StatusCode: 200,
|
|
Technologies: []string{"nginx"},
|
|
}
|
|
r := Compute("example.com", []*store.Host{h}, []*store.Host{h}, time.Now(), time.Now())
|
|
if len(r.Changes) != 0 {
|
|
t.Errorf("expected no changes, got %+v", r.Changes)
|
|
}
|
|
if r.HasMeaningful() {
|
|
t.Error("empty report should not be meaningful")
|
|
}
|
|
}
|
|
|
|
func TestCompute_TechChange(t *testing.T) {
|
|
oldH := &store.Host{Subdomain: "a.example.com", Technologies: []string{"nginx"}}
|
|
newH := &store.Host{Subdomain: "a.example.com", Technologies: []string{"nginx", "Apache"}}
|
|
r := Compute("example.com", []*store.Host{oldH}, []*store.Host{newH}, time.Now(), time.Now())
|
|
found := false
|
|
for _, c := range r.Changes {
|
|
if c.Kind == "tech_change" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected tech_change, got %+v", r.Changes)
|
|
}
|
|
}
|