mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-05-17 22:04:46 +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.
144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
package config
|
|
|
|
import "testing"
|
|
|
|
func TestProfileByName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantOK bool
|
|
wantStr string
|
|
}{
|
|
{"bugbounty", "bugbounty", true, "bugbounty"},
|
|
{"pentest", "pentest", true, "pentest"},
|
|
{"asm-continuous", "asm-continuous", true, "asm-continuous"},
|
|
{"stealth-max", "stealth-max", true, "stealth-max"},
|
|
{"quick", "quick", true, "quick"},
|
|
{"empty", "", false, ""},
|
|
{"unknown", "nonsense", false, ""},
|
|
{"case sensitive", "BugBounty", false, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, ok := ProfileByName(tt.input)
|
|
if ok != tt.wantOK {
|
|
t.Errorf("ok = %v, want %v", ok, tt.wantOK)
|
|
}
|
|
if ok && got.Name != tt.wantStr {
|
|
t.Errorf("Name = %q, want %q", got.Name, tt.wantStr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuiltinProfiles_NonEmpty(t *testing.T) {
|
|
if len(BuiltinProfiles) < 5 {
|
|
t.Errorf("expected ≥5 built-in profiles, got %d", len(BuiltinProfiles))
|
|
}
|
|
seen := make(map[string]bool)
|
|
for _, p := range BuiltinProfiles {
|
|
if p.Name == "" {
|
|
t.Error("profile with empty name")
|
|
}
|
|
if p.Description == "" {
|
|
t.Errorf("profile %q has empty description", p.Name)
|
|
}
|
|
if seen[p.Name] {
|
|
t.Errorf("duplicate profile name: %q", p.Name)
|
|
}
|
|
seen[p.Name] = true
|
|
}
|
|
}
|
|
|
|
func TestApplyProfile_NilConfigNoop(t *testing.T) {
|
|
ApplyProfile(nil, ProfileBugBounty) // must not panic
|
|
}
|
|
|
|
func TestApplyProfile_FillsDefaults(t *testing.T) {
|
|
cfg := &Config{} // zero
|
|
ApplyProfile(cfg, ProfileBugBounty)
|
|
if cfg.Concurrency != ProfileBugBounty.Concurrency {
|
|
t.Errorf("Concurrency = %d, want %d", cfg.Concurrency, ProfileBugBounty.Concurrency)
|
|
}
|
|
if cfg.Timeout != ProfileBugBounty.Timeout {
|
|
t.Errorf("Timeout = %d, want %d", cfg.Timeout, ProfileBugBounty.Timeout)
|
|
}
|
|
if cfg.StealthMode != ProfileBugBounty.Stealth {
|
|
t.Errorf("Stealth = %q, want %q", cfg.StealthMode, ProfileBugBounty.Stealth)
|
|
}
|
|
if !cfg.EnableAI {
|
|
t.Error("bugbounty profile should enable AI")
|
|
}
|
|
if !cfg.MultiAgent {
|
|
t.Error("bugbounty profile should enable MultiAgent")
|
|
}
|
|
if !cfg.Recursive {
|
|
t.Error("bugbounty profile should enable Recursive")
|
|
}
|
|
if !cfg.CloudScan {
|
|
t.Error("bugbounty profile should enable CloudScan")
|
|
}
|
|
}
|
|
|
|
func TestApplyProfile_DoesNotOverrideExplicitFlags(t *testing.T) {
|
|
cfg := &Config{
|
|
Concurrency: 42,
|
|
Timeout: 999,
|
|
StealthMode: "paranoid",
|
|
EnableAI: false, // explicitly disabled before profile apply
|
|
}
|
|
// Apply bugbounty which normally enables AI + sets concurrency to 1000
|
|
ApplyProfile(cfg, ProfileBugBounty)
|
|
|
|
// Explicit non-default user values should survive
|
|
if cfg.Concurrency != 42 {
|
|
t.Errorf("Concurrency overwritten: %d", cfg.Concurrency)
|
|
}
|
|
if cfg.Timeout != 999 {
|
|
t.Errorf("Timeout overwritten: %d", cfg.Timeout)
|
|
}
|
|
if cfg.StealthMode != "paranoid" {
|
|
t.Errorf("Stealth overwritten: %q", cfg.StealthMode)
|
|
}
|
|
// Profile AI enable should still apply since cfg.EnableAI was false
|
|
// (we can't distinguish "user explicitly set false" from "zero value").
|
|
// This is a known limitation documented in the CLI help.
|
|
if !cfg.EnableAI {
|
|
t.Errorf("AI not enabled by profile despite cfg.EnableAI being false")
|
|
}
|
|
}
|
|
|
|
func TestApplyProfile_NoForceOff(t *testing.T) {
|
|
// stealth-max sets NoBrute=true. If user did NOT disable, profile wins.
|
|
cfg := &Config{}
|
|
ApplyProfile(cfg, ProfileStealthMax)
|
|
if !cfg.NoBrute {
|
|
t.Error("stealth-max profile should set NoBrute")
|
|
}
|
|
}
|
|
|
|
func TestApplyProfile_ModuleSettings(t *testing.T) {
|
|
p := Profile{
|
|
Name: "custom",
|
|
Modules: map[string]bool{
|
|
"sources.crtsh": true,
|
|
"brute": false,
|
|
},
|
|
}
|
|
cfg := &Config{}
|
|
ApplyProfile(cfg, p)
|
|
if got := cfg.ModuleSettings["sources.crtsh"]; !got {
|
|
t.Error("crtsh should be enabled")
|
|
}
|
|
if got := cfg.ModuleSettings["brute"]; got {
|
|
t.Error("brute should be disabled")
|
|
}
|
|
|
|
// User pre-existing setting must not be overridden
|
|
cfg2 := &Config{ModuleSettings: map[string]bool{"sources.crtsh": false}}
|
|
ApplyProfile(cfg2, p)
|
|
if cfg2.ModuleSettings["sources.crtsh"] {
|
|
t.Error("user explicit module setting was overridden")
|
|
}
|
|
}
|