mirror of
https://github.com/anotherhadi/github-recon.git
synced 2026-10-05 19:08:24 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53dd8ea2d5 | ||
|
|
832e996708 | ||
|
|
8528fe5732 | ||
|
|
85ee3a6333 | ||
|
|
cb694d896d | ||
|
|
1dfa0c1620 | ||
|
|
ef0240fd65 | ||
|
|
d88bc6ae1e | ||
|
|
c460e5c24f | ||
|
|
e572d1326a | ||
|
|
0017d649d3 | ||
|
|
c163fb4ecd | ||
|
|
325397dfef | ||
|
|
dab0ba2b38 | ||
|
|
98769561c3 | ||
|
|
c76953882c | ||
|
|
94fd83ecce | ||
|
|
2d01c1639f | ||
|
|
66030c1c16 | ||
|
|
bb8f34055d | ||
|
|
26a1cfdd37 | ||
|
|
3064cdbf7a |
@@ -98,7 +98,7 @@ nix run github:anotherhadi/github-recon -- [--flags value] target_username_or_em
|
|||||||
|
|
||||||
# then add it to your packages
|
# then add it to your packages
|
||||||
environment.systemPackages = with pkgs; [ # or home.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)
|
--trufflehog Run trufflehog on cloned repositories (only for deep scan) (default true)
|
||||||
-S, --silent Suppress all non-essential output
|
-S, --silent Suppress all non-essential output
|
||||||
--spoof-email Spoof email (only for email mode) (default true)
|
--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
|
-j, --json string Write results to specified JSON file
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -195,8 +195,9 @@ privacy and security:
|
|||||||
information.
|
information.
|
||||||
- **Manage email exposure**: Use GitHub's settings to control which email
|
- **Manage email exposure**: Use GitHub's settings to control which email
|
||||||
addresses are visible on your profile and in commit history. You can also use
|
addresses are visible on your profile and in commit history. You can also use
|
||||||
a no-reply email address for commits. Delete/modify any sensitive information
|
a no-reply email address for commits, and an
|
||||||
in your commit history.
|
[alias email](https://proton.me/support/addresses-and-aliases) for your
|
||||||
|
account. Delete/modify any sensitive information in your commit history.
|
||||||
- **Be Mindful of Repository Content**: Avoid including sensitive information in
|
- **Be Mindful of Repository Content**: Avoid including sensitive information in
|
||||||
your repositories, such as API keys, passwords, emails or personal data. Use
|
your repositories, such as API keys, passwords, emails or personal data. Use
|
||||||
`.gitignore` to exclude files that contain sensitive information.
|
`.gitignore` to exclude files that contain sensitive information.
|
||||||
|
|||||||
+10
-2
@@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
recon_email "github.com/anotherhadi/github-recon/github-recon/email"
|
recon_email "github.com/anotherhadi/github-recon/github-recon/email"
|
||||||
recon_username "github.com/anotherhadi/github-recon/github-recon/username"
|
recon_username "github.com/anotherhadi/github-recon/github-recon/username"
|
||||||
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
||||||
@@ -8,7 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
settings := github_recon_settings.GetSettings()
|
settings, err := github_recon_settings.GetSettings()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.Silent {
|
if !settings.Silent {
|
||||||
utils.Header()
|
utils.Header()
|
||||||
@@ -23,7 +28,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settings.TargetType == github_recon_settings.TargetUsername {
|
if settings.TargetType == github_recon_settings.TargetUsername {
|
||||||
result := recon_username.Username(settings)
|
result, err := recon_username.Username(settings)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
writeJson(settings, result)
|
writeJson(settings, result)
|
||||||
} else {
|
} else {
|
||||||
result := recon_email.Email(settings)
|
result := recon_email.Email(settings)
|
||||||
|
|||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756787288,
|
"lastModified": 1758427187,
|
||||||
"narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=",
|
"narHash": "sha256-pHpxZ/IyCwoTQPtFIAG2QaxuSm8jWzrzBGjwQZIttJc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1",
|
"rev": "554be6495561ff07b6c724047bdd7e0716aa7b46",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
(system: f system (import nixpkgs {inherit system;}));
|
(system: f system (import nixpkgs {inherit system;}));
|
||||||
|
|
||||||
pname = "github-recon";
|
pname = "github-recon";
|
||||||
version = "2.1.0";
|
version = "1.5.3";
|
||||||
|
|
||||||
ldflags = ["-s" "-w"];
|
ldflags = ["-s" "-w"];
|
||||||
in {
|
in {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
cp $GOPATH/bin/cmd $out/bin/github-recon
|
cp $GOPATH/bin/cmd $out/bin/github-recon
|
||||||
'';
|
'';
|
||||||
|
|
||||||
vendorHash = "sha256-AD0h0k2n8gPqSBz5qqb0ZON/jWiSEWpeO97xR7cYSy8=";
|
vendorHash = "sha256-16mRhQyoyY3M98cWBURsEvvwVT6aR3JWk8YfAFNfm/A=";
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
meta = with pkgs.lib; {
|
||||||
description = "Retrieves and aggregates public OSINT data about a Github user using Go and the Github API. Finds hidden emails in commit history, previous usernames, friends, other Github accounts, and more.";
|
description = "Retrieves and aggregates public OSINT data about a Github user using Go and the Github API. Finds hidden emails in commit history, previous usernames, friends, other Github accounts, and more.";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type EmailResult struct {
|
|||||||
TargetType github_recon_settings.TargetType
|
TargetType github_recon_settings.TargetType
|
||||||
|
|
||||||
Commits CommitsResult
|
Commits CommitsResult
|
||||||
Spoofing SpoofingResult
|
Spoofing *SpoofingResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func Email(settings github_recon_settings.Settings) EmailResult {
|
func Email(settings github_recon_settings.Settings) EmailResult {
|
||||||
@@ -33,7 +33,7 @@ func Email(settings github_recon_settings.Settings) EmailResult {
|
|||||||
} else {
|
} else {
|
||||||
utils.PrintTitle(settings.Silent, "🎭 Spoofing test")
|
utils.PrintTitle(settings.Silent, "🎭 Spoofing test")
|
||||||
result.Spoofing = Spoofing(settings)
|
result.Spoofing = Spoofing(settings)
|
||||||
if result.Spoofing.AvatarURL != "" {
|
if result.Spoofing != nil && result.Spoofing.AvatarURL != "" {
|
||||||
utils.PrintAvatar(settings, result.Spoofing.AvatarURL)
|
utils.PrintAvatar(settings, result.Spoofing.AvatarURL)
|
||||||
}
|
}
|
||||||
utils.PrintStruct(settings, result.Spoofing, 0)
|
utils.PrintStruct(settings, result.Spoofing, 0)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package recon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
github_recon_settings "github.com/anotherhadi/github-recon/settings"
|
||||||
"github.com/anotherhadi/github-recon/utils"
|
"github.com/anotherhadi/github-recon/utils"
|
||||||
@@ -26,7 +27,8 @@ func RandomString(n int) string {
|
|||||||
return string(b)
|
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)
|
name := "gh-recon-spoofing-" + RandomString(8)
|
||||||
private := true
|
private := true
|
||||||
autoInit := true
|
autoInit := true
|
||||||
@@ -41,6 +43,13 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
|
|||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, resp)
|
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()
|
branch := repo.GetDefaultBranch()
|
||||||
if branch == "" {
|
if branch == "" {
|
||||||
branch = "main"
|
branch = "main"
|
||||||
@@ -57,7 +66,6 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
|
|||||||
ref, resp, err := s.Client.Git.GetRef(s.Ctx, repo.Owner.GetLogin(), name, refName)
|
ref, resp, err := s.Client.Git.GetRef(s.Ctx, repo.Owner.GetLogin(), name, refName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Error while getting ref", "err", err)
|
s.Logger.Error("Error while getting ref", "err", err)
|
||||||
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
@@ -65,7 +73,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())
|
parentCommit, resp, err := s.Client.Git.GetCommit(s.Ctx, repo.Owner.GetLogin(), name, ref.GetObject().GetSHA())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Error while getting parent commit", "err", err)
|
s.Logger.Error("Error while getting parent commit", "err", err)
|
||||||
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
@@ -81,7 +88,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)
|
newCommit, resp, err := s.Client.Git.CreateCommit(s.Ctx, repo.Owner.GetLogin(), name, commit, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Error while creating spoofed empty commit", "err", err)
|
s.Logger.Error("Error while creating spoofed empty commit", "err", err)
|
||||||
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
@@ -90,31 +96,35 @@ func Spoofing(s github_recon_settings.Settings) (response SpoofingResult) {
|
|||||||
_, resp, err = s.Client.Git.UpdateRef(s.Ctx, repo.Owner.GetLogin(), name, ref, false)
|
_, resp, err = s.Client.Git.UpdateRef(s.Ctx, repo.Owner.GetLogin(), name, ref, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Error while updating ref to spoofed commit", "err", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
|
|
||||||
|
const maxRetries = 5
|
||||||
|
const retryDelay = 2 * time.Second
|
||||||
|
for i := 0; i < maxRetries; i++ {
|
||||||
commits, _, err := s.Client.Repositories.ListCommits(s.Ctx, repo.Owner.GetLogin(), name, nil)
|
commits, _, err := s.Client.Repositories.ListCommits(s.Ctx, repo.Owner.GetLogin(), name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Error("Error while listing commits", "err", err)
|
s.Logger.Error("Error while listing commits", "err", err)
|
||||||
s.Logger.Warn("The temp repo was left undeleted", "repo", repo.GetHTMLURL())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(commits) > 0 {
|
if len(commits) > 1 {
|
||||||
last := commits[0]
|
last := commits[0]
|
||||||
response.Username = last.GetAuthor().GetLogin()
|
response.Username = last.GetAuthor().GetLogin()
|
||||||
response.Name = last.GetAuthor().GetName()
|
response.Name = last.GetAuthor().GetName()
|
||||||
response.Email = last.GetAuthor().GetEmail()
|
response.Email = last.GetAuthor().GetEmail()
|
||||||
response.Url = last.GetAuthor().GetHTMLURL()
|
response.Url = last.GetAuthor().GetHTMLURL()
|
||||||
response.AvatarURL = last.GetAuthor().GetAvatarURL()
|
response.AvatarURL = last.GetAuthor().GetAvatarURL()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.Client.Repositories.Delete(s.Ctx, repo.Owner.GetLogin(), name)
|
s.Logger.Info("Only one commit found, retrying...", "attempt", i+1)
|
||||||
if err != nil {
|
time.Sleep(retryDelay)
|
||||||
s.Logger.Error("Error while deleting repo", "err", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if response.Username == "" && response.Name == "" && response.Email == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import (
|
|||||||
type CloseFriendsResult []CloseFriendResult
|
type CloseFriendsResult []CloseFriendResult
|
||||||
|
|
||||||
type CloseFriendResult struct {
|
type CloseFriendResult struct {
|
||||||
|
Name string
|
||||||
Username string
|
Username string
|
||||||
|
Orgs []string
|
||||||
|
Company string
|
||||||
|
Location string
|
||||||
Score int
|
Score int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +100,12 @@ func CloseFriends(s github_recon_settings.Settings) (results CloseFriendsResult)
|
|||||||
// Add candidate if they matched at least one condition
|
// Add candidate if they matched at least one condition
|
||||||
if score > 0 {
|
if score > 0 {
|
||||||
results = append(results, CloseFriendResult{
|
results = append(results, CloseFriendResult{
|
||||||
|
Name: candidateDetails.GetName(),
|
||||||
Username: candidateLogin,
|
Username: candidateLogin,
|
||||||
|
Orgs: candidateOrgsToNames(candidateOrgs),
|
||||||
Score: score,
|
Score: score,
|
||||||
|
Location: candidateDetails.GetLocation(),
|
||||||
|
Company: candidateDetails.GetCompany(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,6 +138,15 @@ func checkIfUserFollows(s github_recon_settings.Settings, sourceUser, targetUser
|
|||||||
}
|
}
|
||||||
return isFollowing, nil
|
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) {
|
func getOrgs(s github_recon_settings.Settings, user string) ([]*github.Organization, error) {
|
||||||
orgs, resp, err := s.Client.Organizations.List(s.Ctx, user, nil)
|
orgs, resp, err := s.Client.Organizations.List(s.Ctx, user, nil)
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func DeepScan(s github_recon_settings.Settings) (response DeepScanResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repositories = append(repositories, Repositorie{
|
repositories = append(repositories, Repositorie{
|
||||||
Repository: repo.GetCloneURL(),
|
Repository: repo.GetHTMLURL(),
|
||||||
Owner: repo.GetOwner().GetLogin(),
|
Owner: repo.GetOwner().GetLogin(),
|
||||||
Name: repo.GetName(),
|
Name: repo.GetName(),
|
||||||
Size: repo.GetSize(),
|
Size: repo.GetSize(),
|
||||||
@@ -139,6 +139,11 @@ func DeepScan(s github_recon_settings.Settings) (response DeepScanResult) {
|
|||||||
}
|
}
|
||||||
s.Logger.Info("Cloned all repositories", "path", tmp_folder)
|
s.Logger.Info("Cloned all repositories", "path", tmp_folder)
|
||||||
|
|
||||||
|
if len(repositories) == 0 {
|
||||||
|
s.Logger.Info("No repositories found for the user, skipping deep scan.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
authorOccurrences := Authors{}
|
authorOccurrences := Authors{}
|
||||||
mapAuthorToIndex := make(map[string]int)
|
mapAuthorToIndex := make(map[string]int)
|
||||||
for _, repo := range repositories {
|
for _, repo := range repositories {
|
||||||
@@ -202,7 +207,7 @@ func DeepScan(s github_recon_settings.Settings) (response DeepScanResult) {
|
|||||||
Name: authorName,
|
Name: authorName,
|
||||||
Email: authorEmail,
|
Email: authorEmail,
|
||||||
FoundIn: []string{repoIdentifier},
|
FoundIn: []string{repoIdentifier},
|
||||||
Levenshtein: utils.LevenshteinDistance(s.Target, authorName),
|
Levenshtein: levenshteinDistanceAuthor(s.Target, authorName, authorEmail),
|
||||||
})
|
})
|
||||||
mapAuthorToIndex[trimmedLine] = len(authorOccurrences) - 1
|
mapAuthorToIndex[trimmedLine] = len(authorOccurrences) - 1
|
||||||
}
|
}
|
||||||
@@ -334,13 +339,13 @@ func findEmailsAndOccurrencesInDir(rootPath string, username string) (Emails, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !d.IsDir() {
|
if d.Type().IsRegular() {
|
||||||
if strings.Contains(path, ".git/logs/") {
|
if strings.Contains(path, ".git/logs/") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
content, err := os.ReadFile(path)
|
content, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFileEmails := emailRegex.FindAllString(string(content), -1)
|
currentFileEmails := emailRegex.FindAllString(string(content), -1)
|
||||||
@@ -380,3 +385,10 @@ func findEmailsAndOccurrencesInDir(rootPath string, username string) (Emails, er
|
|||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func levenshteinDistanceAuthor(target, name, email string) int {
|
||||||
|
if strings.Contains(email, "@") {
|
||||||
|
email = strings.SplitN(email, "@", 2)[0]
|
||||||
|
}
|
||||||
|
return slices.Min([]int{utils.LevenshteinDistance(target, name), utils.LevenshteinDistance(target, email)})
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,16 +27,21 @@ type UsernameResult struct {
|
|||||||
DeepScan DeepScanResult
|
DeepScan DeepScanResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func Username(settings github_recon_settings.Settings) UsernameResult {
|
func Username(settings github_recon_settings.Settings) (result UsernameResult, err error) {
|
||||||
|
result = UsernameResult{
|
||||||
result := UsernameResult{
|
|
||||||
Target: settings.Target,
|
Target: settings.Target,
|
||||||
TargetType: settings.TargetType,
|
TargetType: settings.TargetType,
|
||||||
DateTime: time.Now().String(),
|
DateTime: time.Now().String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.PrintTitle(settings.Silent, "👤 User informations")
|
utils.PrintTitle(settings.Silent, "👤 User informations")
|
||||||
result.User = User(settings)
|
result.User, err = User(settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.User == (UserResult{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
utils.PrintAvatar(settings, result.User.AvatarURL)
|
utils.PrintAvatar(settings, result.User.AvatarURL)
|
||||||
utils.PrintStruct(settings, result.User, 0)
|
utils.PrintStruct(settings, result.User, 0)
|
||||||
|
|
||||||
@@ -74,5 +79,5 @@ func Username(settings github_recon_settings.Settings) UsernameResult {
|
|||||||
utils.PrintStruct(settings, result.DeepScan, 0)
|
utils.PrintStruct(settings, result.DeepScan, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func Orgs(s github_recon_settings.Settings) (response OrgsResult) {
|
|||||||
for _, org := range orgs {
|
for _, org := range orgs {
|
||||||
o := OrgResult{
|
o := OrgResult{
|
||||||
Name: org.GetLogin(),
|
Name: org.GetLogin(),
|
||||||
URL: org.GetURL(),
|
URL: org.GetHTMLURL(),
|
||||||
Description: org.GetDescription(),
|
Description: org.GetDescription(),
|
||||||
}
|
}
|
||||||
response = append(response, o)
|
response = append(response, o)
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ type UserResult struct {
|
|||||||
Plan string
|
Plan string
|
||||||
}
|
}
|
||||||
|
|
||||||
func User(s github_recon_settings.Settings) (response UserResult) {
|
func User(s github_recon_settings.Settings) (response UserResult, err error) {
|
||||||
user, resp, err := s.Client.Users.Get(s.Ctx, s.Target)
|
user, resp, err := s.Client.Users.Get(s.Ctx, s.Target)
|
||||||
if resp.StatusCode == 404 {
|
if resp.StatusCode == 404 {
|
||||||
s.Logger.Fatal("User not found with username")
|
return UserResult{}, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Logger.Fatal("Failed to fetch user's information", "err", err)
|
return UserResult{}, fmt.Errorf("failed to fetch user's information")
|
||||||
}
|
}
|
||||||
|
|
||||||
u := UserResult{
|
u := UserResult{
|
||||||
@@ -65,5 +65,5 @@ func User(s github_recon_settings.Settings) (response UserResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils.WaitForRateLimit(s, resp)
|
utils.WaitForRateLimit(s, resp)
|
||||||
return u
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/anotherhadi/github-recon
|
module github.com/anotherhadi/github-recon
|
||||||
|
|
||||||
go 1.24.5
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
@@ -26,6 +26,6 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
golang.org/x/image v0.28.0 // indirect
|
golang.org/x/image v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
|||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
|
||||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
|||||||
+18
-18
@@ -32,7 +32,7 @@ type Settings struct {
|
|||||||
Silent bool
|
Silent bool
|
||||||
DeepScan bool
|
DeepScan bool
|
||||||
MaxDistance int
|
MaxDistance int
|
||||||
HideAvatar bool
|
PrintAvatar bool
|
||||||
SpoofEmail bool
|
SpoofEmail bool
|
||||||
Trufflehog bool
|
Trufflehog bool
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ func GetDefaultSettings() Settings {
|
|||||||
Silent: false,
|
Silent: false,
|
||||||
DeepScan: false,
|
DeepScan: false,
|
||||||
MaxDistance: 20,
|
MaxDistance: 20,
|
||||||
HideAvatar: false,
|
PrintAvatar: true,
|
||||||
SpoofEmail: true,
|
SpoofEmail: true,
|
||||||
Trufflehog: true,
|
Trufflehog: true,
|
||||||
|
|
||||||
@@ -68,11 +68,11 @@ func GetDefaultSettings() Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSettings() (settings Settings) {
|
func GetSettings() (settings Settings, err error) {
|
||||||
settings = GetDefaultSettings()
|
settings = GetDefaultSettings()
|
||||||
//// Flag settings
|
//// Flag settings
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, "Usage:\n", os.Args[0])
|
||||||
fmt.Fprintf(os.Stderr, "github-recon [flags] <target username or email>\n")
|
fmt.Fprintf(os.Stderr, "github-recon [flags] <target username or email>\n")
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
fmt.Fprintf(os.Stderr, "Flags:\n")
|
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||||
@@ -83,55 +83,55 @@ func GetSettings() (settings Settings) {
|
|||||||
flag.CommandLine.SortFlags = false
|
flag.CommandLine.SortFlags = false
|
||||||
|
|
||||||
//// Flags
|
//// 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
|
// 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(
|
flag.IntVar(
|
||||||
&settings.MaxRepoSize,
|
&settings.MaxRepoSize,
|
||||||
"max-size",
|
"max-size",
|
||||||
150,
|
settings.MaxRepoSize,
|
||||||
"Limit the size of repositories to scan (in MB) (only for deep scan)",
|
"Limit the size of repositories to scan (in MB) (only for deep scan)",
|
||||||
)
|
)
|
||||||
flag.StringSliceVarP(
|
flag.StringSliceVarP(
|
||||||
&settings.ExcludedRepos,
|
&settings.ExcludedRepos,
|
||||||
"exclude-repo",
|
"exclude-repo",
|
||||||
"e",
|
"e",
|
||||||
[]string{},
|
settings.ExcludedRepos,
|
||||||
"Exclude repos from deep scan (comma-separated list, only for deep scan)",
|
"Exclude repos from deep scan (comma-separated list, only for deep scan)",
|
||||||
)
|
)
|
||||||
flag.BoolVarP(
|
flag.BoolVarP(
|
||||||
&settings.Refresh,
|
&settings.Refresh,
|
||||||
"refresh",
|
"refresh",
|
||||||
"r",
|
"r",
|
||||||
false,
|
settings.Refresh,
|
||||||
"Refresh the cache (only for deep scan)",
|
"Refresh the cache (only for deep scan)",
|
||||||
)
|
)
|
||||||
flag.BoolVarP(
|
flag.BoolVarP(
|
||||||
&settings.ShowSource,
|
&settings.ShowSource,
|
||||||
"show-source",
|
"show-source",
|
||||||
"s",
|
"s",
|
||||||
false,
|
settings.ShowSource,
|
||||||
"Show where the information (authors, emails, etc) were found (only for deep scan)",
|
"Show where the information (authors, emails, etc) were found (only for deep scan)",
|
||||||
)
|
)
|
||||||
flag.IntVarP(
|
flag.IntVarP(
|
||||||
&settings.MaxDistance,
|
&settings.MaxDistance,
|
||||||
"max-distance",
|
"max-distance",
|
||||||
"m",
|
"m",
|
||||||
20,
|
settings.MaxDistance,
|
||||||
"Maximum Levenshtein distance for matching usernames & emails (only for deep scan)",
|
"Maximum Levenshtein distance for matching usernames & emails (only for deep scan)",
|
||||||
)
|
)
|
||||||
flag.BoolVar(
|
flag.BoolVar(
|
||||||
&settings.Trufflehog,
|
&settings.Trufflehog,
|
||||||
"trufflehog",
|
"trufflehog",
|
||||||
true,
|
settings.Trufflehog,
|
||||||
"Run trufflehog on cloned repositories (only for deep scan)",
|
"Run trufflehog on cloned repositories (only for deep scan)",
|
||||||
)
|
)
|
||||||
|
|
||||||
flag.BoolVarP(&settings.Silent, "silent", "S", false, "Suppress all non-essential output")
|
flag.BoolVarP(&settings.Silent, "silent", "S", settings.Silent, "Suppress all non-essential output")
|
||||||
flag.BoolVarP(&settings.SpoofEmail, "spoof-email", "", true, "Spoof email (only for email mode)")
|
flag.BoolVarP(&settings.SpoofEmail, "spoof-email", "", settings.SpoofEmail, "Spoof email (only for email mode)")
|
||||||
flag.BoolVarP(&settings.HideAvatar, "hide-avatar", "a", false, "Hide the avatar in the output")
|
flag.BoolVarP(&settings.PrintAvatar, "print-avatar", "a", settings.PrintAvatar, "Show the avatar in the output")
|
||||||
flag.StringVarP(&settings.JsonOutput, "json", "j", "", "Write results to specified JSON file")
|
flag.StringVarP(&settings.JsonOutput, "json", "j", settings.JsonOutput, "Write results to specified JSON file")
|
||||||
|
|
||||||
//// Parse
|
//// Parse
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -152,7 +152,7 @@ func GetSettings() (settings Settings) {
|
|||||||
settings.Target = strings.TrimPrefix(settings.Target, "@") // Remove the @ of the username
|
settings.Target = strings.TrimPrefix(settings.Target, "@") // Remove the @ of the username
|
||||||
|
|
||||||
if strings.Contains(settings.Target, " ") {
|
if strings.Contains(settings.Target, " ") {
|
||||||
settings.Logger.Fatal("Target cannot contain spaces")
|
err = fmt.Errorf("target cannot contain spaces")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(settings.Target, "@") {
|
if strings.Contains(settings.Target, "@") {
|
||||||
@@ -163,7 +163,7 @@ func GetSettings() (settings Settings) {
|
|||||||
|
|
||||||
// If token is not set via flag, get it from env
|
// If token is not set via flag, get it from env
|
||||||
if settings.Token == "null" || settings.Token == "" {
|
if settings.Token == "null" || settings.Token == "" {
|
||||||
settings.Token = getToken()
|
settings.Token = GetToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.Token == "null" || settings.Token == "" {
|
if settings.Token == "null" || settings.Token == "" {
|
||||||
|
|||||||
+2
-1
@@ -9,7 +9,8 @@ import (
|
|||||||
flag "github.com/spf13/pflag"
|
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")
|
token := os.Getenv("GITHUB_RECON_TOKEN")
|
||||||
if token != "" {
|
if token != "" {
|
||||||
return token
|
return token
|
||||||
|
|||||||
+4
-2
@@ -57,7 +57,9 @@ func PrintStruct(settings github_recon_settings.Settings, s any, indent int) {
|
|||||||
for i := 0; i < v.NumField(); i++ {
|
for i := 0; i < v.NumField(); i++ {
|
||||||
field := t.Field(i).Name
|
field := t.Field(i).Name
|
||||||
value := v.Field(i)
|
value := v.Field(i)
|
||||||
|
if !value.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !value.IsValid() || (value.Kind() == reflect.String && value.String() == "") {
|
if !value.IsValid() || (value.Kind() == reflect.String && value.String() == "") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -143,7 +145,7 @@ func PrintTitle(silent bool, title string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PrintAvatar(settings github_recon_settings.Settings, url string) {
|
func PrintAvatar(settings github_recon_settings.Settings, url string) {
|
||||||
if settings.HideAvatar || url == "" || settings.Silent {
|
if !settings.PrintAvatar || url == "" || settings.Silent {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user