package recon import ( "fmt" "net/http" "sort" "strings" ) var adminSurfacePaths = []string{ "/wp-admin", "/wp-admin/", "/admin", "/admin/", "/admin/login", "/administrator", "/api", "/api/", "/api/v1", "/graphql", "/graphql/", "/swagger", "/swagger/", "/swagger/index.html", "/swagger-ui", "/swagger-ui/", "/actuator", "/actuator/", "/actuator/health", "/.env", "/.env.local", "/server-status", "/server-status/", } func ProbeAdminSurface(host string, port int, scheme string) []AdminSurfaceFinding { scheme = normalizeScheme(scheme, port) if port <= 0 { port = defaultPortForScheme(scheme) } base := fmt.Sprintf("%s://%s", scheme, joinHostPort(host, port)) seen := map[string]bool{} var out []AdminSurfaceFinding for _, path := range adminSurfacePaths { key := strings.ToLower(path) if seen[key] { continue } seen[key] = true rawURL := strings.TrimRight(base, "/") + path status, _, _, err := fetchPage(rawURL) if err != nil { continue } signal := adminSurfaceSignal(status) if signal == "" { continue } out = append(out, AdminSurfaceFinding{ Path: path, URL: rawURL, StatusCode: status, Signal: signal, }) } sort.Slice(out, func(i, j int) bool { if out[i].Signal != out[j].Signal { return out[i].Signal == "green" } return out[i].Path < out[j].Path }) return out } func adminSurfaceSignal(status int) string { switch status { case http.StatusOK: return "green" case http.StatusUnauthorized, http.StatusForbidden: return "gray" default: return "" } }