Files
github-recon/gh-recon/deep.go
T
Hadi 5a9483411a v0.2.1
Signed-off-by: Hadi <[email protected]>
2025-05-20 15:28:32 +02:00

180 lines
4.1 KiB
Go

package ghrecon
import (
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"github.com/google/go-github/v72/github"
)
func folderExists(path string) bool {
if stat, err := os.Stat(path); err == nil && stat.IsDir() {
return true
}
return false
}
type EmailOccurrence struct {
Email string
FoundIn []string
}
func findEmailsAndOccurrencesInDir(rootPath string) ([]EmailOccurrence, error) {
emailLocations := make(map[string]map[string]bool)
emailRegex := regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
normalizedRootPath := filepath.Clean(rootPath)
err := filepath.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
fmt.Printf("Can't access %s: %v\n", path, err)
return err
}
if !d.IsDir() {
content, err := os.ReadFile(path)
if err != nil {
fmt.Printf("Can't read %s: %v\n", path, err)
return nil
}
currentFileEmails := emailRegex.FindAllString(string(content), -1)
if len(currentFileEmails) > 0 {
relativePath, errRel := filepath.Rel(normalizedRootPath, path)
if errRel != nil {
fmt.Printf("Can't find the relative path %s: %v\n", path, errRel)
relativePath = path
}
for _, email := range currentFileEmails {
if len(email) > 12 {
if _, ok := emailLocations[email]; !ok {
emailLocations[email] = make(map[string]bool)
}
emailLocations[email][relativePath] = true
}
}
}
}
return nil
})
if err != nil {
return nil, err
}
var results []EmailOccurrence
for email, pathSet := range emailLocations {
var paths []string
for path := range pathSet {
paths = append(paths, path)
}
results = append(results, EmailOccurrence{Email: email, FoundIn: paths})
}
return results, nil
}
type DeepResult struct {
Repository string
Owner string
Name string
}
func (r Recon) Deep(username, excludeRepos string) (response []DeepResult) {
excludeReposList := strings.Split(excludeRepos, ",")
repos, resp, err := r.client.Repositories.ListByUser(
r.ctx,
username,
&github.RepositoryListByUserOptions{
Type: "all",
},
)
if err != nil {
r.logger.Error("Failed to fetch repositories", "err", err)
return
}
r.PrintTitle("📦 Repositories")
if len(repos) == 0 {
r.PrintInfo("INFO", "No repositories found")
} else {
for _, repo := range repos {
response = append(response, DeepResult{
Repository: repo.GetCloneURL(),
Owner: repo.GetOwner().GetLogin(),
Name: repo.GetName(),
})
}
}
WaitForRateLimit(resp)
cmd := exec.Command("git", "--version")
if err := cmd.Run(); err != nil {
r.PrintInfo("ERROR", "Git is not installed, please install it to use this feature")
return
}
tmp_folder := "/tmp/ghrecon-" + username
for _, repo := range response {
if slices.Contains(excludeReposList, repo.Name) ||
slices.Contains(excludeReposList, repo.Owner+"/"+repo.Name) {
r.PrintInfo("INFO", "Skipping repository", repo.Owner+"/"+repo.Name)
continue
}
r.PrintInfo(
"Downloading repository",
repo.Owner+"/"+repo.Name,
)
destination := tmp_folder + "/" + repo.Owner + "/" + repo.Name
if folderExists(destination) {
r.PrintInfo("INFO", "Directory already exists, skipping")
continue
}
cmd := exec.Command(
"git",
"clone",
repo.Repository,
destination,
)
err := cmd.Run()
if err != nil {
r.logger.Error(
"ERROR",
"Failed to clone repository",
"err",
err,
"repo",
repo.Repository,
)
continue
}
}
r.PrintInfo("INFO", "Cloned all repositories to "+tmp_folder)
r.PrintInfo("INFO", "Now searching for emails in cloned repositories, this may take a while...")
results, err := findEmailsAndOccurrencesInDir(tmp_folder)
if err != nil {
r.logger.Error("Failed to find emails in directory", "err", err)
return
}
if len(results) == 0 {
r.PrintInfo("INFO", "No emails found")
} else {
r.PrintInfo("INFO", "Found emails:")
for _, email := range results {
r.PrintInfo("Email", email.Email)
r.PrintInfo("Found in", tmp_folder, email.FoundIn...)
}
}
r.PrintNewline()
return
}