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

99 lines
2.6 KiB
Go

package config
import "testing"
func TestAIProfileByName(t *testing.T) {
cases := []struct {
in string
wantOK bool
wantTag string
}{
{"lean", true, "qwen3:1.7b"},
{"LEAN", true, "qwen3:1.7b"},
{"balanced", true, "qwen3:4b"},
{"balance", true, "qwen3:4b"},
{"mid", true, "qwen3:4b"},
{"heavy", true, "qwen3:8b"},
{"max", true, "qwen3:8b"},
{"power", true, "qwen3:8b"},
{"Heavy", true, "qwen3:8b"},
{"nope", false, ""},
{"", false, ""},
}
for _, c := range cases {
p, ok := AIProfileByName(c.in)
if ok != c.wantOK {
t.Errorf("AIProfileByName(%q) ok = %v, want %v", c.in, ok, c.wantOK)
continue
}
if ok && p.FastModel != c.wantTag {
t.Errorf("AIProfileByName(%q).FastModel = %q, want %q", c.in, p.FastModel, c.wantTag)
}
}
}
func TestBuiltinAIProfiles_Unique(t *testing.T) {
names := map[string]bool{}
for _, p := range BuiltinAIProfiles {
if p.Name == "" {
t.Error("profile with empty name")
}
if p.FastModel == "" || p.DeepModel == "" {
t.Errorf("profile %q missing models", p.Name)
}
if p.Description == "" {
t.Errorf("profile %q missing description", p.Name)
}
if names[p.Name] {
t.Errorf("duplicate profile name: %q", p.Name)
}
names[p.Name] = true
}
}
func TestApplyAIProfile_RespectsOverrides(t *testing.T) {
cfg := &Config{
AIFastModel: "user-chose-this:1b",
AIDeepModel: "user-chose-that:7b",
}
ApplyAIProfile(cfg, AIProfileHeavy, true, true)
if cfg.AIFastModel != "user-chose-this:1b" {
t.Errorf("overrideFast was ignored: %q", cfg.AIFastModel)
}
if cfg.AIDeepModel != "user-chose-that:7b" {
t.Errorf("overrideDeep was ignored: %q", cfg.AIDeepModel)
}
if cfg.AIProfile != "heavy" {
t.Errorf("AIProfile not set to heavy, got %q", cfg.AIProfile)
}
}
func TestApplyAIProfile_FillsUnsetFields(t *testing.T) {
cfg := &Config{}
ApplyAIProfile(cfg, AIProfileBalanced, false, false)
if cfg.AIFastModel != "qwen3:4b" {
t.Errorf("FastModel not applied: %q", cfg.AIFastModel)
}
if cfg.AIDeepModel != "qwen3-coder:30b" {
t.Errorf("DeepModel not applied: %q", cfg.AIDeepModel)
}
if cfg.AIProfile != "balanced" {
t.Errorf("AIProfile not set: %q", cfg.AIProfile)
}
}
func TestApplyAIProfile_NilConfigNoop(t *testing.T) {
ApplyAIProfile(nil, AIProfileLean, false, false) // must not panic
}
func TestApplyAIProfile_PartialOverride(t *testing.T) {
cfg := &Config{AIFastModel: "custom:1b"}
ApplyAIProfile(cfg, AIProfileHeavy, true, false)
if cfg.AIFastModel != "custom:1b" {
t.Errorf("FastModel overridden: %q", cfg.AIFastModel)
}
if cfg.AIDeepModel != "qwen3-coder:30b" {
t.Errorf("DeepModel not applied: %q", cfg.AIDeepModel)
}
}