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

157 lines
3.6 KiB
Go

package config
import "testing"
func TestView_NilSafe(t *testing.T) {
var v *View
if v.Profile() != "" {
t.Error("nil view Profile should be empty")
}
if v.Bool("ai.enabled", true) != true {
t.Error("nil view Bool should return fallback")
}
if v.Int("concurrency", 99) != 99 {
t.Error("nil view Int should return fallback")
}
if v.String("domain", "fb") != "fb" {
t.Error("nil view String should return fallback")
}
if v.ModuleEnabled("x") {
t.Error("nil view ModuleEnabled should be false")
}
}
func TestView_Profile(t *testing.T) {
v := NewView(&Config{Profile: "bugbounty"})
if v.Profile() != "bugbounty" {
t.Errorf("Profile = %q", v.Profile())
}
}
func TestView_Bool(t *testing.T) {
cfg := &Config{
EnableAI: true,
AICascade: true,
AIDeepAnalysis: false,
MultiAgent: true,
Silent: true,
Verbose: false,
JsonOutput: true,
NoBrute: true,
OnlyActive: true,
Recursive: true,
CloudScan: true,
APIScan: false,
}
v := NewView(cfg)
tests := []struct {
key string
fb bool
want bool
}{
{"ai.enabled", false, true},
{"ai.cascade", false, true},
{"ai.deep", true, false},
{"ai.multi_agent", false, true},
{"silent", false, true},
{"verbose", true, false},
{"json", false, true},
{"no_brute", false, true},
{"only_active", false, true},
{"recursive", false, true},
{"cloud_scan", false, true},
{"api_scan", true, false},
{"unknown_key", true, true}, // fallback
{"unknown_key", false, false},
}
for _, tt := range tests {
if got := v.Bool(tt.key, tt.fb); got != tt.want {
t.Errorf("Bool(%q, %v) = %v, want %v", tt.key, tt.fb, got, tt.want)
}
}
}
func TestView_Int(t *testing.T) {
v := NewView(&Config{Concurrency: 500, Timeout: 10, RecursiveDepth: 4})
if v.Int("concurrency", 1) != 500 {
t.Errorf("concurrency wrong")
}
if v.Int("timeout", 1) != 10 {
t.Errorf("timeout wrong")
}
if v.Int("recursive.depth", 1) != 4 {
t.Errorf("recursive.depth wrong")
}
if v.Int("unknown", 99) != 99 {
t.Errorf("unknown key should return fallback")
}
}
func TestView_String(t *testing.T) {
v := NewView(&Config{
Domain: "example.com",
Wordlist: "/wl",
Output: "/out",
Format: "json",
Ports: "80,443",
Resolvers: "8.8.8.8",
StealthMode: "light",
AIUrl: "http://x",
AIFastModel: "f",
AIDeepModel: "d",
})
cases := map[string]string{
"domain": "example.com",
"wordlist": "/wl",
"output": "/out",
"format": "json",
"ports": "80,443",
"resolvers": "8.8.8.8",
"stealth": "light",
"ai.url": "http://x",
"ai.fast_model": "f",
"ai.deep_model": "d",
}
for k, want := range cases {
if got := v.String(k, "fb"); got != want {
t.Errorf("String(%q) = %q, want %q", k, got, want)
}
}
if v.String("unknown", "fb") != "fb" {
t.Error("unknown key should return fallback")
}
}
func TestView_Strings(t *testing.T) {
// Placeholder — no multi-value keys defined yet
v := NewView(&Config{})
if got := v.Strings("anything"); got != nil {
t.Errorf("expected nil, got %v", got)
}
}
func TestView_ModuleEnabled(t *testing.T) {
cfg := &Config{ModuleSettings: map[string]bool{"m1": true, "m2": false}}
v := NewView(cfg)
if !v.ModuleEnabled("m1") {
t.Error("m1 should be enabled")
}
if v.ModuleEnabled("m2") {
t.Error("m2 should be disabled (false in map)")
}
if v.ModuleEnabled("unset") {
t.Error("unset module should be false")
}
}
func TestView_ModuleEnabled_NilMap(t *testing.T) {
v := NewView(&Config{})
if v.ModuleEnabled("anything") {
t.Error("nil map should result in false")
}
}