// Package passive is the Fase 0.6 adapter that wraps the v1 passive sources // (internal/sources) as a single Module. It fans out queries to all 20 public // sources in parallel and emits a SubdomainDiscovered event for each result. // // In Fase 1 (Discovery Supremacy) each source will become its own Module with // independent configuration, error reporting, and rate limiting. This // adapter preserves v1 behavior so we reach feature parity immediately. package passive import ( "context" "strings" "sync" "god-eye/internal/eventbus" "god-eye/internal/module" "god-eye/internal/sources" "god-eye/internal/store" ) // ModuleName is the registry identifier. const ModuleName = "passive.v1-aggregate" type passiveModule struct{} // Register the module in the default registry. Callers import this package // for side effects via the modules meta-package (see internal/modules/all). func Register() { module.Register(&passiveModule{}) } func (*passiveModule) Name() string { return ModuleName } func (*passiveModule) Phase() module.Phase { return module.PhaseDiscovery } func (*passiveModule) Consumes() []eventbus.EventType { return nil } func (*passiveModule) Produces() []eventbus.EventType { return []eventbus.EventType{eventbus.EventSubdomainDiscovered, eventbus.EventModuleError} } func (*passiveModule) DefaultEnabled() bool { return true } // sourceList mirrors the v1 scanner.Run list. Order is preserved for stable // logging. var sourceList = []struct { name string fn func(string) ([]string, error) }{ {"crt.sh", sources.FetchCrtsh}, {"Certspotter", sources.FetchCertspotter}, {"AlienVault", sources.FetchAlienVault}, {"HackerTarget", sources.FetchHackerTarget}, {"URLScan", sources.FetchURLScan}, {"RapidDNS", sources.FetchRapidDNS}, {"Anubis", sources.FetchAnubis}, {"ThreatMiner", sources.FetchThreatMiner}, {"DNSRepo", sources.FetchDNSRepo}, {"SubdomainCenter", sources.FetchSubdomainCenter}, {"Wayback", sources.FetchWayback}, {"CommonCrawl", sources.FetchCommonCrawl}, {"Sitedossier", sources.FetchSitedossier}, {"Riddler", sources.FetchRiddler}, {"Robtex", sources.FetchRobtex}, {"DNSHistory", sources.FetchDNSHistory}, {"ArchiveToday", sources.FetchArchiveToday}, {"JLDC", sources.FetchJLDC}, {"SynapsInt", sources.FetchSynapsInt}, {"CensysFree", sources.FetchCensysFree}, // v2.0 additions — free, no API key, fail-open. Dormant v1 sources // re-activated + 4 net-new endpoints. {"BufferOver", sources.FetchBufferOver}, // dormant v1 {"DNSDumpster", sources.FetchDNSDumpster}, // dormant v1 {"Omnisint", sources.FetchOmnisint}, // v2 new {"HudsonRock", sources.FetchHudsonRock}, // v2 new {"WebArchiveCDX", sources.FetchWebArchiveCDX}, // v2 new {"Digitorus", sources.FetchDigitorus}, // v2 new } func (m *passiveModule) Run(mctx module.Context) error { target := mctx.Target if target == "" { return nil } var wg sync.WaitGroup // Dedup across sources before emitting — the store will also dedup, but // emitting duplicates just burns bus bandwidth. seen := make(map[string]struct{}) var seenMu sync.Mutex for _, src := range sourceList { src := src wg.Add(1) go func() { defer wg.Done() // Respect ctx cancellation between slow sources. if err := mctx.Ctx.Err(); err != nil { return } subs, err := src.fn(target) if err != nil { mctx.Bus.Publish(mctx.Ctx, eventbus.ModuleError{ EventMeta: eventbus.EventMeta{Source: ModuleName + ":" + src.name, Target: target}, Module: ModuleName + ":" + src.name, Err: err.Error(), }) return } for _, sub := range subs { sub = strings.ToLower(strings.TrimSpace(sub)) if sub == "" { continue } if !strings.HasSuffix(sub, target) { continue } seenMu.Lock() if _, dup := seen[sub]; dup { seenMu.Unlock() continue } seen[sub] = struct{}{} seenMu.Unlock() // Persist into the store so downstream resolution phases // can find the subdomain even if they subscribed too late // to receive the SubdomainDiscovered event. methodTag := "passive:" + src.name _ = mctx.Store.Upsert(mctx.Ctx, sub, func(h *store.Host) { store.AddDiscoveryMethod(h, methodTag) }) mctx.Bus.Publish(mctx.Ctx, eventbus.NewSubdomainDiscovered( ModuleName+":"+src.name, sub, methodTag, )) } }() } // Wait for sources OR cancellation. done := make(chan struct{}) go func() { wg.Wait(); close(done) }() select { case <-done: case <-mctx.Ctx.Done(): } _ = context.Canceled // keep import return nil }