Files
god-eye/internal/http/factory.go
T
Vyntral 8356eb573d 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

257 lines
6.7 KiB
Go

package http
import (
"crypto/tls"
"net"
"net/http"
"sync"
"time"
"god-eye/internal/proxyconf"
)
// ClientFactory manages shared HTTP clients with connection pooling
type ClientFactory struct {
// Shared transports for connection reuse
secureTransport *http.Transport
insecureTransport *http.Transport
// Pre-configured clients
defaultClient *http.Client
fastClient *http.Client
noRedirect *http.Client
insecureClient *http.Client
mu sync.RWMutex
}
var (
factory *ClientFactory
factoryOnce sync.Once
// proxyURL captures the most recent SetProxy() value, read at factory
// construction time. Callers MUST invoke SetProxy BEFORE any code path
// that triggers GetFactory — otherwise the factory is built with a
// direct dialer and subsequent proxy changes won't be picked up.
//
// In main.go this is safe: we call SetProxy right after flag parsing,
// before any module starts.
proxyURL string
proxyMu sync.RWMutex
)
// SetProxy configures the outbound proxy for every HTTP client the
// factory hands out. Must be called BEFORE GetFactory() / any module
// uses a shared client. Supported schemes: http, https, socks5, socks5h.
// Empty string disables proxying.
func SetProxy(u string) error {
if err := proxyconf.Validate(u); err != nil {
return err
}
proxyMu.Lock()
proxyURL = u
proxyMu.Unlock()
return nil
}
// CurrentProxy returns the currently-configured proxy URL, or empty when
// none. Useful for status/debug output.
func CurrentProxy() string {
proxyMu.RLock()
defer proxyMu.RUnlock()
return proxyURL
}
// GetFactory returns the singleton client factory
func GetFactory() *ClientFactory {
factoryOnce.Do(func() {
factory = newClientFactory()
})
return factory
}
func newClientFactory() *ClientFactory {
proxyMu.RLock()
cfgProxy := proxyURL
proxyMu.RUnlock()
baseDialer := &net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}
dialCtx, err := proxyconf.BuildDialer(cfgProxy, baseDialer)
if err != nil {
// Bad proxy URL at this point is a programming error (we validated
// in SetProxy). Fall back to direct rather than crashing.
dialCtx = baseDialer.DialContext
}
proxyFunc, _ := proxyconf.BuildProxyFunc(cfgProxy)
// Secure transport with TLS verification
secureTransport := &http.Transport{
DialContext: dialCtx,
Proxy: proxyFunc,
MaxIdleConns: 200,
MaxIdleConnsPerHost: 20,
MaxConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
ForceAttemptHTTP2: true,
ExpectContinueTimeout: 1 * time.Second,
}
// Insecure transport (for scanning targets with invalid certs)
insecureTransport := &http.Transport{
DialContext: dialCtx,
Proxy: proxyFunc,
MaxIdleConns: 200,
MaxIdleConnsPerHost: 20,
MaxConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS10, // Support older servers
},
ForceAttemptHTTP2: true,
ExpectContinueTimeout: 1 * time.Second,
}
return &ClientFactory{
secureTransport: secureTransport,
insecureTransport: insecureTransport,
defaultClient: &http.Client{
Transport: insecureTransport,
Timeout: 15 * time.Second,
},
fastClient: &http.Client{
Transport: insecureTransport,
Timeout: 5 * time.Second,
},
noRedirect: &http.Client{
Transport: insecureTransport,
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
insecureClient: &http.Client{
Transport: insecureTransport,
Timeout: 10 * time.Second,
},
}
}
// Default returns the default client with 15s timeout
func (f *ClientFactory) Default() *http.Client {
return f.defaultClient
}
// Fast returns a client with 5s timeout for quick checks
func (f *ClientFactory) Fast() *http.Client {
return f.fastClient
}
// NoRedirect returns a client that doesn't follow redirects
func (f *ClientFactory) NoRedirect() *http.Client {
return f.noRedirect
}
// Insecure returns a client with TLS verification disabled
func (f *ClientFactory) Insecure() *http.Client {
return f.insecureClient
}
// WithTimeout creates a client with custom timeout (reuses transport)
func (f *ClientFactory) WithTimeout(timeout time.Duration) *http.Client {
return &http.Client{
Transport: f.insecureTransport,
Timeout: timeout,
}
}
// WithTimeoutNoRedirect creates a client with custom timeout that doesn't follow redirects
func (f *ClientFactory) WithTimeoutNoRedirect(timeout time.Duration) *http.Client {
return &http.Client{
Transport: f.insecureTransport,
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
// Secure returns a client with TLS verification enabled (for passive sources)
func (f *ClientFactory) Secure() *http.Client {
return &http.Client{
Transport: f.secureTransport,
Timeout: 30 * time.Second,
}
}
// SecureWithTimeout creates a secure client with custom timeout
func (f *ClientFactory) SecureWithTimeout(timeout time.Duration) *http.Client {
return &http.Client{
Transport: f.secureTransport,
Timeout: timeout,
}
}
// CloseIdleConnections closes idle connections in all transports
func (f *ClientFactory) CloseIdleConnections() {
f.secureTransport.CloseIdleConnections()
f.insecureTransport.CloseIdleConnections()
}
// Stats returns connection pool statistics
type PoolStats struct {
SecureIdleConns int
InsecureIdleConns int
}
// GetStats returns current pool statistics (approximation)
func (f *ClientFactory) GetStats() PoolStats {
// Note: Go's http.Transport doesn't expose detailed stats
// This is a placeholder for future monitoring
return PoolStats{}
}
// Convenience functions for direct access
// DefaultClient returns the default shared client
func DefaultClient() *http.Client {
return GetFactory().Default()
}
// FastClient returns the fast shared client (5s timeout)
func FastClient() *http.Client {
return GetFactory().Fast()
}
// NoRedirectClient returns a client that doesn't follow redirects
func NoRedirectClient() *http.Client {
return GetFactory().NoRedirect()
}
// InsecureClient returns a client with TLS verification disabled
func InsecureClient() *http.Client {
return GetFactory().Insecure()
}
// SecureClient returns a client with TLS verification enabled
func SecureClient() *http.Client {
return GetFactory().Secure()
}
// ClientWithTimeout returns a client with custom timeout
func ClientWithTimeout(timeout time.Duration) *http.Client {
return GetFactory().WithTimeout(timeout)
}