Compare commits

..
8 Commits
Author SHA1 Message Date
Hadi c76953882c Nil when no result & better error handling
Signed-off-by: Hadi <[email protected]>
2025-09-17 20:39:53 +02:00
Hadi 94fd83ecce fix print with private interface
Signed-off-by: Hadi <[email protected]>
2025-09-17 20:26:31 +02:00
Hadi 2d01c1639f Fix isDir condition
Signed-off-by: Hadi <[email protected]>
2025-09-17 19:47:50 +02:00
Hadi 66030c1c16 add details to close friends
Signed-off-by: Hadi <[email protected]>
2025-09-17 19:31:54 +02:00
Hadi bb8f34055d fix nixos installation step
Signed-off-by: Hadi <[email protected]>
2025-09-16 19:35:38 +02:00
Hadi 26a1cfdd37 Change the way token is retrieve
Signed-off-by: Hadi <[email protected]>
2025-09-15 14:54:51 +02:00
Hadi 3064cdbf7a Change/get defaults
Signed-off-by: Hadi <[email protected]>
2025-09-15 14:40:55 +02:00
Hadi 52f9f1f3e7 Change settings
Signed-off-by: Hadi <[email protected]>
2025-09-15 14:37:21 +02:00
8 changed files with 87 additions and 45 deletions
+2 -2
View File
@@ -98,7 +98,7 @@ nix run github:anotherhadi/github-recon -- [--flags value] target_username_or_em
# then add it to your packages
environment.systemPackages = with pkgs; [ # or home.packages
github-recon
inputs.github-recon.defaultPackage.${pkgs.system}
];
```
@@ -123,7 +123,7 @@ github-recon [--flags value] target_username_or_email
--trufflehog Run trufflehog on cloned repositories (only for deep scan) (default true)
-S, --silent Suppress all non-essential output
--spoof-email Spoof email (only for email mode) (default true)
-a, --hide-avatar Hide the avatar in the output
-a, --print-avatar Show the avatar in the output
-j, --json string Write results to specified JSON file
```
+2 -2
View File
@@ -13,7 +13,7 @@ type EmailResult struct {
TargetType github_recon_settings.TargetType
Commits CommitsResult
Spoofing SpoofingResult
Spoofing *SpoofingResult
}
func Email(settings github_recon_settings.Settings) EmailResult {
@@ -33,7 +33,7 @@ func Email(settings github_recon_settings.Settings) EmailResult {
} else {
utils.PrintTitle(settings.Silent, "🎭 Spoofing test")
result.Spoofing = Spoofing(settings)
if result.Spoofing.AvatarURL != "" {
if result.Spoofing != nil && result.Spoofing.AvatarURL != "" {
utils.PrintAvatar(settings, result.Spoofing.AvatarURL)
}
utils.PrintStruct(settings, result.Spoofing, 0)
+14 -11
View File
@@ -26,7 +26,8 @@ func RandomString(n int) string {
return string(b)
}
func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
func Spoofing(s github_recon_settings.Settings) (response *SpoofingResult) {
response = &SpoofingResult{}
name := "gh-recon-spoofing-" + RandomString(8)
private := true
autoInit := true
@@ -41,6 +42,13 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
}
utils.WaitForRateLimit(s, resp)
defer func() {
_, err = s.Client.Repositories.Delete(s.Ctx, repo.Owner.GetLogin(), name)
if err != nil {
s.Logger.Error("Error while deleting repo", "err", err)
}
}()
branch := repo.GetDefaultBranch()
if branch == "" {
branch = "main"
@@ -57,7 +65,6 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
ref, resp, err := s.Client.Git.GetRef(s.Ctx, repo.Owner.GetLogin(), name, refName)
if err != nil {
s.Logger.Error("Error while getting ref", "err", err)
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
return
}
utils.WaitForRateLimit(s, resp)
@@ -65,7 +72,6 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
parentCommit, resp, err := s.Client.Git.GetCommit(s.Ctx, repo.Owner.GetLogin(), name, ref.GetObject().GetSHA())
if err != nil {
s.Logger.Error("Error while getting parent commit", "err", err)
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
return
}
utils.WaitForRateLimit(s, resp)
@@ -81,7 +87,6 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
newCommit, resp, err := s.Client.Git.CreateCommit(s.Ctx, repo.Owner.GetLogin(), name, commit, nil)
if err != nil {
s.Logger.Error("Error while creating spoofed empty commit", "err", err)
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
return
}
utils.WaitForRateLimit(s, resp)
@@ -90,7 +95,6 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
_, resp, err = s.Client.Git.UpdateRef(s.Ctx, repo.Owner.GetLogin(), name, ref, false)
if err != nil {
s.Logger.Error("Error while updating ref to spoofed commit", "err", err)
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
return
}
utils.WaitForRateLimit(s, resp)
@@ -98,23 +102,22 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
commits, _, err := s.Client.Repositories.ListCommits(s.Ctx, repo.Owner.GetLogin(), name, nil)
if err != nil {
s.Logger.Error("Error while listing commits", "err", err)
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
return
}
if len(commits) > 0 {
if len(commits) > 1 {
last := commits[0]
response.Username = last.GetAuthor().GetLogin()
response.Name = last.GetAuthor().GetName()
response.Email = last.GetAuthor().GetEmail()
response.Url = last.GetAuthor().GetHTMLURL()
response.AvatarURL = last.GetAuthor().GetAvatarURL()
} else {
s.Logger.Error("Only one commit found, something went wrong.", "commits", commits)
}
_, err = s.Client.Repositories.Delete(s.Ctx, repo.Owner.GetLogin(), name)
if err != nil {
s.Logger.Error("Error while deleting repo", "err", err)
if response.Username == "" && response.Name == "" && response.Email == "" {
return nil
}
return
}
+17
View File
@@ -13,7 +13,11 @@ import (
type CloseFriendsResult []CloseFriendResult
type CloseFriendResult struct {
Name string
Username string
Orgs []string
Company string
Location string
Score int
}
@@ -96,8 +100,12 @@ func CloseFriends(s github_recon_settings.Settings) (results CloseFriendsResult)
// Add candidate if they matched at least one condition
if score > 0 {
results = append(results, CloseFriendResult{
Name: candidateDetails.GetName(),
Username: candidateLogin,
Orgs: candidateOrgsToNames(candidateOrgs),
Score: score,
Location: candidateDetails.GetLocation(),
Company: candidateDetails.GetCompany(),
})
}
}
@@ -130,6 +138,15 @@ func checkIfUserFollows(s github_recon_settings.Settings, sourceUser, targetUser
}
return isFollowing, nil
}
func candidateOrgsToNames(orgs []*github.Organization) []string {
var orgNames []string
for _, org := range orgs {
if org.GetLogin() != "" {
orgNames = append(orgNames, org.GetLogin())
}
}
return orgNames
}
func getOrgs(s github_recon_settings.Settings, user string) ([]*github.Organization, error) {
orgs, resp, err := s.Client.Organizations.List(s.Ctx, user, nil)
+2 -2
View File
@@ -334,13 +334,13 @@ func findEmailsAndOccurrencesInDir(rootPath string, username string) (Emails, er
if err != nil {
return err
}
if !d.IsDir() {
if d.Type().IsRegular() {
if strings.Contains(path, ".git/logs/") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return err
return nil
}
currentFileEmails := emailRegex.FindAllString(string(content), -1)
+44 -25
View File
@@ -32,7 +32,7 @@ type Settings struct {
Silent bool
DeepScan bool
MaxDistance int
HideAvatar bool
PrintAvatar bool
SpoofEmail bool
Trufflehog bool
@@ -42,7 +42,34 @@ type Settings struct {
Ctx context.Context
}
func GetDefaultSettings() Settings {
return Settings{
Token: "null",
Target: "",
TargetType: TargetUsername,
ShowSource: false,
Refresh: false,
MaxRepoSize: 150,
ExcludedRepos: []string{},
JsonOutput: "",
Silent: false,
DeepScan: false,
MaxDistance: 20,
PrintAvatar: true,
SpoofEmail: true,
Trufflehog: true,
Client: github.NewClient(nil),
Logger: log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: false,
ReportTimestamp: false,
}),
Ctx: context.WithValue(context.Background(), github.SleepUntilPrimaryRateLimitResetWhenRateLimited, true),
}
}
func GetSettings() (settings Settings) {
settings = GetDefaultSettings()
//// Flag settings
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
@@ -56,74 +83,66 @@ func GetSettings() (settings Settings) {
flag.CommandLine.SortFlags = false
//// Flags
flag.StringVarP(&settings.Token, "token", "t", "null", "Github personal access token (e.g. ghp_aaa...). Can also be set via GITHUB_RECON_TOKEN environment variable. You also need to set the token in $HOME/.config/github-recon/env file if you want to use this tool without passing the token every time.")
flag.StringVarP(&settings.Token, "token", "t", settings.Token, "Github personal access token (e.g. ghp_aaa...). Can also be set via GITHUB_RECON_TOKEN environment variable. You also need to set the token in $HOME/.config/github-recon/env file if you want to use this tool without passing the token every time.")
// DeepScan
flag.BoolVarP(&settings.DeepScan, "deepscan", "d", false, "Enable deep scan (clone repos, regex search, analyse licenses, etc.)")
flag.BoolVarP(&settings.DeepScan, "deepscan", "d", settings.DeepScan, "Enable deep scan (clone repos, regex search, analyse licenses, etc.)")
flag.IntVar(
&settings.MaxRepoSize,
"max-size",
150,
settings.MaxRepoSize,
"Limit the size of repositories to scan (in MB) (only for deep scan)",
)
flag.StringSliceVarP(
&settings.ExcludedRepos,
"exclude-repo",
"e",
[]string{},
settings.ExcludedRepos,
"Exclude repos from deep scan (comma-separated list, only for deep scan)",
)
flag.BoolVarP(
&settings.Refresh,
"refresh",
"r",
false,
settings.Refresh,
"Refresh the cache (only for deep scan)",
)
flag.BoolVarP(
&settings.ShowSource,
"show-source",
"s",
false,
settings.ShowSource,
"Show where the information (authors, emails, etc) were found (only for deep scan)",
)
flag.IntVarP(
&settings.MaxDistance,
"max-distance",
"m",
20,
settings.MaxDistance,
"Maximum Levenshtein distance for matching usernames & emails (only for deep scan)",
)
flag.BoolVar(
&settings.Trufflehog,
"trufflehog",
true,
settings.Trufflehog,
"Run trufflehog on cloned repositories (only for deep scan)",
)
flag.BoolVarP(&settings.Silent, "silent", "S", false, "Suppress all non-essential output")
flag.BoolVarP(&settings.SpoofEmail, "spoof-email", "", true, "Spoof email (only for email mode)")
flag.BoolVarP(&settings.HideAvatar, "hide-avatar", "a", false, "Hide the avatar in the output")
flag.StringVarP(&settings.JsonOutput, "json", "j", "", "Write results to specified JSON file")
flag.BoolVarP(&settings.Silent, "silent", "S", settings.Silent, "Suppress all non-essential output")
flag.BoolVarP(&settings.SpoofEmail, "spoof-email", "", settings.SpoofEmail, "Spoof email (only for email mode)")
flag.BoolVarP(&settings.PrintAvatar, "print-avatar", "a", settings.PrintAvatar, "Show the avatar in the output")
flag.StringVarP(&settings.JsonOutput, "json", "j", settings.JsonOutput, "Write results to specified JSON file")
//// Parse
flag.Parse()
//// Setup
settings.Client = github.NewClient(nil)
settings.Logger = log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: false,
ReportTimestamp: false,
})
settings.Ctx = context.WithValue(context.Background(), github.SleepUntilPrimaryRateLimitResetWhenRateLimited, true)
//// Tail
nonFlagArgs := flag.Args()
if len(nonFlagArgs) > 1 {
settings.Logger.Error("Please provide only one target (username or email)")
flag.Usage()
os.Exit(1)
} else if len(nonFlagArgs) < 1 {
} else if len(nonFlagArgs) == 0 {
settings.Logger.Error("Please provide a target (username or email)")
flag.Usage()
os.Exit(1)
@@ -143,11 +162,11 @@ func GetSettings() (settings Settings) {
}
// If token is not set via flag, get it from env
if settings.Token == "null" {
settings.Token = getToken()
if settings.Token == "null" || settings.Token == "" {
settings.Token = GetToken()
}
if settings.Token == "null" {
if settings.Token == "null" || settings.Token == "" {
settings.Logger.Warn("No Github token provided. You might hit the rate limit. Check the help menu for more information.")
} else {
settings.Client = settings.Client.WithAuthToken(settings.Token)
+2 -1
View File
@@ -9,7 +9,8 @@ import (
flag "github.com/spf13/pflag"
)
func getToken() string {
// GetToken retrieves the GitHub token from the environment variable or config file
func GetToken() string {
token := os.Getenv("GITHUB_RECON_TOKEN")
if token != "" {
return token
+4 -2
View File
@@ -57,7 +57,9 @@ func PrintStruct(settings github_recon_settings.Settings, s any, indent int) {
for i := 0; i < v.NumField(); i++ {
field := t.Field(i).Name
value := v.Field(i)
if !value.CanInterface() {
continue
}
if !value.IsValid() || (value.Kind() == reflect.String && value.String() == "") {
continue
}
@@ -143,7 +145,7 @@ func PrintTitle(silent bool, title string) {
}
func PrintAvatar(settings github_recon_settings.Settings, url string) {
if settings.HideAvatar || url == "" || settings.Silent {
if !settings.PrintAvatar || url == "" || settings.Silent {
return
}