Files
Vyntral 3a4c230aa7 feat: v2.0 full rewrite — event-driven pipeline, AI + Nuclei + proxy
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.
2026-04-18 16:48:41 +02:00

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")
}
}