mirror of
https://github.com/Vyntral/god-eye.git
synced 2026-05-16 05:29:11 +02:00
3a4c230aa7
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.
142 lines
3.8 KiB
Go
142 lines
3.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
godns "god-eye/internal/dns"
|
|
)
|
|
|
|
// --- built-in tools -------------------------------------------------------
|
|
//
|
|
// These tools cover the minimum needed for a planner to investigate
|
|
// discovered hosts without reinventing basic primitives. Fase 3 workers
|
|
// receive curated subsets via Toolset.
|
|
|
|
// HTTPRequestTool fetches an arbitrary URL and returns status, headers,
|
|
// and (truncated) body. Maximum 64KB body returned.
|
|
type HTTPRequestTool struct {
|
|
Client *http.Client
|
|
}
|
|
|
|
func NewHTTPRequestTool(timeoutSec int) *HTTPRequestTool {
|
|
return &HTTPRequestTool{
|
|
Client: &http.Client{
|
|
Timeout: time.Duration(timeoutSec) * time.Second,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (t *HTTPRequestTool) Name() string { return "http_request" }
|
|
func (t *HTTPRequestTool) Description() string { return "Fetch an HTTP(S) URL and return status + headers + first 64KB of body." }
|
|
|
|
func (t *HTTPRequestTool) Schema() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"url": map[string]interface{}{"type": "string"},
|
|
"method": map[string]interface{}{"type": "string", "default": "GET"},
|
|
"headers": map[string]interface{}{
|
|
"type": "object",
|
|
"additionalProperties": map[string]interface{}{"type": "string"},
|
|
},
|
|
},
|
|
"required": []string{"url"},
|
|
}
|
|
}
|
|
|
|
func (t *HTTPRequestTool) Call(ctx context.Context, args map[string]interface{}) (string, error) {
|
|
url, _ := args["url"].(string)
|
|
if url == "" {
|
|
return "", errors.New("url is required")
|
|
}
|
|
method, _ := args["method"].(string)
|
|
if method == "" {
|
|
method = "GET"
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, url, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if hdrs, ok := args["headers"].(map[string]interface{}); ok {
|
|
for k, v := range hdrs {
|
|
if s, ok := v.(string); ok {
|
|
req.Header.Set(k, s)
|
|
}
|
|
}
|
|
}
|
|
req.Header.Set("User-Agent", "god-eye-v2-agent")
|
|
|
|
resp, err := t.Client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
|
out := map[string]interface{}{
|
|
"status_code": resp.StatusCode,
|
|
"headers": flattenHeaders(resp.Header),
|
|
"body": string(body),
|
|
}
|
|
b, _ := json.Marshal(out)
|
|
return string(b), nil
|
|
}
|
|
|
|
// DNSResolveTool resolves a hostname to A/CNAME/PTR records.
|
|
type DNSResolveTool struct {
|
|
Resolvers []string
|
|
TimeoutSec int
|
|
}
|
|
|
|
func NewDNSResolveTool(resolvers []string, timeoutSec int) *DNSResolveTool {
|
|
if len(resolvers) == 0 {
|
|
resolvers = []string{"8.8.8.8:53", "1.1.1.1:53"}
|
|
}
|
|
return &DNSResolveTool{Resolvers: resolvers, TimeoutSec: timeoutSec}
|
|
}
|
|
|
|
func (t *DNSResolveTool) Name() string { return "dns_resolve" }
|
|
func (t *DNSResolveTool) Description() string { return "Resolve a hostname to A/CNAME/PTR records." }
|
|
|
|
func (t *DNSResolveTool) Schema() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"hostname": map[string]interface{}{"type": "string"},
|
|
},
|
|
"required": []string{"hostname"},
|
|
}
|
|
}
|
|
|
|
func (t *DNSResolveTool) Call(_ context.Context, args map[string]interface{}) (string, error) {
|
|
name, _ := args["hostname"].(string)
|
|
if name == "" {
|
|
return "", errors.New("hostname is required")
|
|
}
|
|
ips := godns.ResolveSubdomain(name, t.Resolvers, t.TimeoutSec)
|
|
cname := godns.ResolveCNAME(name, t.Resolvers, t.TimeoutSec)
|
|
out := map[string]interface{}{"ips": ips, "cname": cname}
|
|
b, _ := json.Marshal(out)
|
|
return string(b), nil
|
|
}
|
|
|
|
func flattenHeaders(h http.Header) map[string]string {
|
|
out := make(map[string]string, len(h))
|
|
for k, vs := range h {
|
|
if len(vs) == 0 {
|
|
continue
|
|
}
|
|
out[k] = vs[0]
|
|
}
|
|
return out
|
|
}
|