mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-05-18 14:24:49 +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.
172 lines
4.0 KiB
Go
172 lines
4.0 KiB
Go
package sources
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestExtractSubdomains(t *testing.T) {
|
|
target := "example.com"
|
|
|
|
tests := []struct {
|
|
name string
|
|
text string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "empty text",
|
|
text: "",
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "no matches",
|
|
text: "some text with no domains",
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "apex only",
|
|
text: "found example.com here",
|
|
want: []string{"example.com"},
|
|
},
|
|
{
|
|
name: "single subdomain",
|
|
text: "api.example.com was found",
|
|
want: []string{"api.example.com"},
|
|
},
|
|
{
|
|
name: "multiple subdomains",
|
|
text: "api.example.com and admin.example.com and dev.example.com",
|
|
want: []string{"admin.example.com", "api.example.com", "dev.example.com"},
|
|
},
|
|
{
|
|
name: "deduplication",
|
|
text: "api.example.com api.example.com api.example.com",
|
|
want: []string{"api.example.com"},
|
|
},
|
|
{
|
|
name: "uppercase normalized",
|
|
text: "API.EXAMPLE.COM and Api.Example.com",
|
|
want: []string{"api.example.com"},
|
|
},
|
|
{
|
|
name: "wildcard prefix stripped",
|
|
text: "*.example.com is a wildcard",
|
|
want: []string{"example.com"},
|
|
},
|
|
{
|
|
name: "different domain filtered",
|
|
text: "api.example.com and other.different.org and sub.example.com",
|
|
want: []string{"api.example.com", "sub.example.com"},
|
|
},
|
|
{
|
|
name: "partial match not allowed",
|
|
text: "evilexample.com should not match",
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "json-wrapped",
|
|
text: `{"name":"api.example.com","type":"A"}`,
|
|
want: []string{"api.example.com"},
|
|
},
|
|
{
|
|
name: "mixed with urls",
|
|
text: `Visit https://api.example.com and https://docs.example.com/path`,
|
|
want: []string{"api.example.com", "docs.example.com"},
|
|
},
|
|
{
|
|
// Regex is greedy: only the longest leftmost match is returned,
|
|
// not every suffix. This is the v1 baseline behavior.
|
|
name: "deep subdomain longest match only",
|
|
text: "a.b.c.example.com",
|
|
want: []string{"a.b.c.example.com"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ExtractSubdomains(tt.text, target)
|
|
sort.Strings(got)
|
|
sort.Strings(tt.want)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("ExtractSubdomains(%q)\n got: %v\n want: %v", tt.text, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetClientForTimeout(t *testing.T) {
|
|
tests := []struct {
|
|
timeout time.Duration
|
|
want string // identify by Timeout field
|
|
}{
|
|
{5 * time.Second, "fast"},
|
|
{10 * time.Second, "fast"},
|
|
{15 * time.Second, "standard"},
|
|
{30 * time.Second, "standard"},
|
|
{60 * time.Second, "slow"},
|
|
{120 * time.Second, "slow"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
c := GetClientForTimeout(tt.timeout)
|
|
if c == nil {
|
|
t.Fatalf("GetClientForTimeout(%v) returned nil", tt.timeout)
|
|
}
|
|
var gotClient string
|
|
switch c {
|
|
case FastClient:
|
|
gotClient = "fast"
|
|
case StandardClient:
|
|
gotClient = "standard"
|
|
case SlowClient:
|
|
gotClient = "slow"
|
|
default:
|
|
gotClient = "unknown"
|
|
}
|
|
if gotClient != tt.want {
|
|
t.Errorf("GetClientForTimeout(%v) = %s, want %s", tt.timeout, gotClient, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClientsInitialized(t *testing.T) {
|
|
if FastClient == nil {
|
|
t.Error("FastClient is nil")
|
|
}
|
|
if StandardClient == nil {
|
|
t.Error("StandardClient is nil")
|
|
}
|
|
if SlowClient == nil {
|
|
t.Error("SlowClient is nil")
|
|
}
|
|
if FastClient.Timeout != 10*time.Second {
|
|
t.Errorf("FastClient.Timeout = %v, want 10s", FastClient.Timeout)
|
|
}
|
|
if StandardClient.Timeout != 15*time.Second {
|
|
t.Errorf("StandardClient.Timeout = %v, want 15s", StandardClient.Timeout)
|
|
}
|
|
if SlowClient.Timeout != 120*time.Second {
|
|
t.Errorf("SlowClient.Timeout = %v, want 120s", SlowClient.Timeout)
|
|
}
|
|
}
|
|
|
|
func TestRegexCompiled(t *testing.T) {
|
|
if SubdomainRegex == nil {
|
|
t.Error("SubdomainRegex not compiled")
|
|
}
|
|
if EmailDomainRegex == nil {
|
|
t.Error("EmailDomainRegex not compiled")
|
|
}
|
|
if URLDomainRegex == nil {
|
|
t.Error("URLDomainRegex not compiled")
|
|
}
|
|
if JSONSubdomainRegex == nil {
|
|
t.Error("JSONSubdomainRegex not compiled")
|
|
}
|
|
if WildcardPrefixRegex == nil {
|
|
t.Error("WildcardPrefixRegex not compiled")
|
|
}
|
|
}
|