mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-07-16 16:37:19 +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.
338 lines
11 KiB
Go
338 lines
11 KiB
Go
// Package eventbus provides a typed, context-aware pub/sub bus that decouples
|
|
// discovery, probing, analysis, and reporting modules in God's Eye v2.
|
|
//
|
|
// Design choices:
|
|
// - Events are typed structs implementing Event; dispatch is keyed on EventType.
|
|
// - Subscribers run handlers on their own goroutine with a buffered channel,
|
|
// so a slow handler cannot stall the producer.
|
|
// - Publish is non-blocking: if a subscriber buffer is full, the event is
|
|
// dropped for that subscriber and Stats.Dropped is incremented. Subscribers
|
|
// that care about lossless delivery must size their buffer accordingly.
|
|
// - Close stops accepting new events and drains outstanding ones before
|
|
// returning.
|
|
package eventbus
|
|
|
|
import "time"
|
|
|
|
// EventType identifies the kind of an event.
|
|
type EventType string
|
|
|
|
// Canonical event types. Modules should always use these constants rather than
|
|
// string literals to avoid typos and to make the full event vocabulary greppable.
|
|
const (
|
|
EventSubdomainDiscovered EventType = "subdomain.discovered"
|
|
EventDNSResolved EventType = "dns.resolved"
|
|
EventHTTPProbed EventType = "http.probed"
|
|
EventTechDetected EventType = "tech.detected"
|
|
EventTLSAnalyzed EventType = "tls.analyzed"
|
|
EventTakeoverCandidate EventType = "takeover.candidate"
|
|
EventTakeoverConfirmed EventType = "takeover.confirmed"
|
|
EventVulnerability EventType = "vulnerability"
|
|
EventSecret EventType = "secret"
|
|
EventCVEMatch EventType = "cve.match"
|
|
EventCloudAsset EventType = "cloud.asset"
|
|
EventAPIFinding EventType = "api.finding"
|
|
EventJSFile EventType = "js.file"
|
|
EventAIFinding EventType = "ai.finding"
|
|
EventPhaseStarted EventType = "phase.started"
|
|
EventPhaseCompleted EventType = "phase.completed"
|
|
EventModuleError EventType = "module.error"
|
|
EventScanStarted EventType = "scan.started"
|
|
EventScanCompleted EventType = "scan.completed"
|
|
)
|
|
|
|
// Severity levels used across vulnerability, secret, AI and CVE events.
|
|
type Severity string
|
|
|
|
const (
|
|
SeverityInfo Severity = "info"
|
|
SeverityLow Severity = "low"
|
|
SeverityMedium Severity = "medium"
|
|
SeverityHigh Severity = "high"
|
|
SeverityCritical Severity = "critical"
|
|
)
|
|
|
|
// Event is implemented by every event struct.
|
|
type Event interface {
|
|
Type() EventType
|
|
Meta() EventMeta
|
|
}
|
|
|
|
// EventMeta is shared metadata embedded in every event.
|
|
type EventMeta struct {
|
|
At time.Time // when the event was created
|
|
Source string // originating module name (e.g. "sources.crtsh", "dns.resolver")
|
|
Target string // logical target (typically the subdomain or host the event pertains to)
|
|
}
|
|
|
|
// Meta returns the shared metadata; implemented by embedding EventMeta.
|
|
func (m EventMeta) Meta() EventMeta { return m }
|
|
|
|
// now returns the current time; indirected for testability.
|
|
var now = time.Now
|
|
|
|
// newMeta builds an EventMeta with a populated timestamp.
|
|
func newMeta(source, target string) EventMeta {
|
|
return EventMeta{At: now(), Source: source, Target: target}
|
|
}
|
|
|
|
// --- Concrete event types --------------------------------------------------
|
|
|
|
// SubdomainDiscovered fires whenever any source (passive, brute, recursive,
|
|
// CT, etc.) identifies a subdomain that passes the "ends in target domain"
|
|
// filter. Multiple sources may discover the same subdomain — the bus does not
|
|
// dedup; that's the store's job.
|
|
type SubdomainDiscovered struct {
|
|
EventMeta
|
|
Subdomain string
|
|
Method string // "passive:crt.sh", "brute", "recursive", "ct-stream", etc.
|
|
}
|
|
|
|
func (SubdomainDiscovered) Type() EventType { return EventSubdomainDiscovered }
|
|
|
|
func NewSubdomainDiscovered(source, subdomain, method string) SubdomainDiscovered {
|
|
return SubdomainDiscovered{
|
|
EventMeta: newMeta(source, subdomain),
|
|
Subdomain: subdomain,
|
|
Method: method,
|
|
}
|
|
}
|
|
|
|
// DNSResolved fires after a subdomain is resolved. Empty IPs field signals
|
|
// an intentionally negative result (NXDOMAIN); absence of the event means
|
|
// "not yet resolved".
|
|
type DNSResolved struct {
|
|
EventMeta
|
|
Subdomain string
|
|
IPs []string
|
|
CNAME string
|
|
PTR string
|
|
}
|
|
|
|
func (DNSResolved) Type() EventType { return EventDNSResolved }
|
|
|
|
// HTTPProbed fires once per successful HTTP probe, including server banner,
|
|
// title, and technology signals. Security checks emit their own events.
|
|
type HTTPProbed struct {
|
|
EventMeta
|
|
URL string
|
|
StatusCode int
|
|
ContentLength int64
|
|
Title string
|
|
Server string
|
|
Technologies []string
|
|
Headers map[string]string
|
|
ResponseMs int64
|
|
TLSVersion string
|
|
TLSSelfSigned bool
|
|
}
|
|
|
|
func (HTTPProbed) Type() EventType { return EventHTTPProbed }
|
|
|
|
// VulnerabilityFound is the canonical finding event for any detected issue.
|
|
// Scanner modules (security checks, smuggling, SSRF, GraphQL, etc.) all emit
|
|
// this so the reporter/aggregator has a single type to consume.
|
|
type VulnerabilityFound struct {
|
|
EventMeta
|
|
ID string // stable identifier, e.g. "open-redirect", "cors-wildcard-creds"
|
|
Title string // short human-readable title
|
|
Description string // longer context
|
|
Severity Severity
|
|
URL string // affected URL
|
|
Evidence string // raw evidence (truncated if too large)
|
|
Remediation string // how to fix
|
|
CVEs []string // referenced CVEs if any
|
|
OWASP string // OWASP category (e.g. "A03:2021-Injection")
|
|
CVSS float64 // 0.0 if not scored
|
|
}
|
|
|
|
func (VulnerabilityFound) Type() EventType { return EventVulnerability }
|
|
|
|
// SecretFound fires when a credential, API key, or token is detected (in JS,
|
|
// response bodies, commits, etc.).
|
|
type SecretFound struct {
|
|
EventMeta
|
|
Kind string // "aws_access_key", "jwt", "stripe_live", "generic_hex"
|
|
Match string // redacted or truncated match — full value in Value if validated
|
|
Value string // full value, populated only when validation succeeded
|
|
Location string // where it was found (URL, file path, commit sha)
|
|
Validated bool // true if we verified the secret is live against its service
|
|
Severity Severity
|
|
Description string
|
|
}
|
|
|
|
func (SecretFound) Type() EventType { return EventSecret }
|
|
|
|
// CVEMatch fires when a CVE is correlated to a detected technology/version.
|
|
type CVEMatch struct {
|
|
EventMeta
|
|
CVE string
|
|
Technology string
|
|
Version string
|
|
Severity Severity
|
|
CVSS float64
|
|
Description string
|
|
URL string
|
|
InKEV bool // true if in CISA Known Exploited Vulnerabilities catalog
|
|
}
|
|
|
|
func (CVEMatch) Type() EventType { return EventCVEMatch }
|
|
|
|
// TakeoverCandidate fires when a CNAME or fingerprint points at a service
|
|
// that could potentially be taken over. TakeoverConfirmed fires after active
|
|
// verification (service claim test) succeeds.
|
|
type TakeoverCandidate struct {
|
|
EventMeta
|
|
Subdomain string
|
|
Service string // "GitHub Pages", "S3", "Heroku", etc.
|
|
CNAME string
|
|
Evidence string
|
|
}
|
|
|
|
func (TakeoverCandidate) Type() EventType { return EventTakeoverCandidate }
|
|
|
|
type TakeoverConfirmed struct {
|
|
EventMeta
|
|
Subdomain string
|
|
Service string
|
|
CNAME string
|
|
PoC string // curl/HTTP reproducer
|
|
}
|
|
|
|
func (TakeoverConfirmed) Type() EventType { return EventTakeoverConfirmed }
|
|
|
|
// CloudAssetFound fires for exposed/accessible cloud assets (S3 buckets,
|
|
// GCS buckets, Azure blobs, Firebase projects, etc.).
|
|
type CloudAssetFound struct {
|
|
EventMeta
|
|
Provider string // "AWS", "GCP", "Azure", "Firebase"
|
|
Kind string // "s3-bucket", "gcs-bucket", "lambda-url"
|
|
Name string
|
|
URL string
|
|
Status string // "public-read", "listable", "writable", "exists"
|
|
Permissions []string // detailed permissions if known
|
|
}
|
|
|
|
func (CloudAssetFound) Type() EventType { return EventCloudAsset }
|
|
|
|
// APIFinding fires for discovered/enumerated API surfaces (GraphQL, Swagger,
|
|
// Postman, misconfigured REST) with associated issues.
|
|
type APIFinding struct {
|
|
EventMeta
|
|
Kind string // "graphql-introspection", "swagger-exposed", "rest-cors", etc.
|
|
URL string
|
|
Issue string
|
|
Severity Severity
|
|
Endpoints []string
|
|
}
|
|
|
|
func (APIFinding) Type() EventType { return EventAPIFinding }
|
|
|
|
// TechDetected fires when a technology (framework, server, CMS, language) is
|
|
// identified with a version, feeding CVE matching and AI analysis.
|
|
type TechDetected struct {
|
|
EventMeta
|
|
Host string
|
|
Technology string
|
|
Version string
|
|
Category string // "web-server", "framework", "cms", "language", "waf"
|
|
Confidence float64
|
|
}
|
|
|
|
func (TechDetected) Type() EventType { return EventTechDetected }
|
|
|
|
// TLSAnalyzed fires with TLS certificate details, including appliance
|
|
// fingerprint when identifiable.
|
|
type TLSAnalyzed struct {
|
|
EventMeta
|
|
Host string
|
|
Version string
|
|
Issuer string
|
|
Expiry time.Time
|
|
SelfSigned bool
|
|
AltNames []string
|
|
Vendor string // FortiGate, Palo Alto, etc. (empty if no fingerprint)
|
|
Product string
|
|
ApplianceKind string // "firewall", "vpn", "loadbalancer", "waf"
|
|
InternalHosts []string
|
|
}
|
|
|
|
func (TLSAnalyzed) Type() EventType { return EventTLSAnalyzed }
|
|
|
|
// JSFileDiscovered fires when a JavaScript file is discovered and prepared
|
|
// for analysis (secret scanning, endpoint extraction, AI review).
|
|
type JSFileDiscovered struct {
|
|
EventMeta
|
|
URL string
|
|
Size int64
|
|
Host string
|
|
}
|
|
|
|
func (JSFileDiscovered) Type() EventType { return EventJSFile }
|
|
|
|
// AIFinding is emitted by any AI/agent module (cascade or multi-agent).
|
|
type AIFinding struct {
|
|
EventMeta
|
|
Subject string // subdomain/URL the finding pertains to
|
|
Agent string // "triage", "deep", "xss", "sqli", etc.
|
|
Model string // LLM model id
|
|
Severity Severity
|
|
Title string
|
|
Description string
|
|
Evidence string
|
|
CVEs []string
|
|
OWASP string
|
|
Confidence float64
|
|
}
|
|
|
|
func (AIFinding) Type() EventType { return EventAIFinding }
|
|
|
|
// PhaseStarted / PhaseCompleted frame pipeline phases (passive, brute,
|
|
// resolve, probe, ai, etc.) so UIs and progress trackers can react.
|
|
type PhaseStarted struct {
|
|
EventMeta
|
|
Phase string
|
|
}
|
|
|
|
func (PhaseStarted) Type() EventType { return EventPhaseStarted }
|
|
|
|
type PhaseCompleted struct {
|
|
EventMeta
|
|
Phase string
|
|
Duration time.Duration
|
|
Stats map[string]int64
|
|
}
|
|
|
|
func (PhaseCompleted) Type() EventType { return EventPhaseCompleted }
|
|
|
|
// ModuleError fires when a module encounters a non-fatal error (source
|
|
// unavailable, rate-limited, timeout). Use this for observability; do not
|
|
// log errors in modules directly.
|
|
type ModuleError struct {
|
|
EventMeta
|
|
Module string
|
|
Err string // stringified error
|
|
Fatal bool // true only when the module cannot continue
|
|
Context map[string]string
|
|
}
|
|
|
|
func (ModuleError) Type() EventType { return EventModuleError }
|
|
|
|
// ScanStarted / ScanCompleted bookend the whole run.
|
|
type ScanStarted struct {
|
|
EventMeta
|
|
Target string
|
|
Profile string
|
|
}
|
|
|
|
func (ScanStarted) Type() EventType { return EventScanStarted }
|
|
|
|
type ScanCompleted struct {
|
|
EventMeta
|
|
Target string
|
|
Duration time.Duration
|
|
Stats map[string]int64
|
|
}
|
|
|
|
func (ScanCompleted) Type() EventType { return EventScanCompleted }
|