mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-07-03 02:45:55 +02:00
8356eb573d
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.
257 lines
6.7 KiB
Go
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)
|
|
}
|