├── README.md ├── go.mod ├── go.sum ├── img └── screenshot.png └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # GithubID 2 | 3 | GithubID is an OSINT tool that can be used to retrieve the identities (name/email) that a given Github user has used in their public commits. 4 | 5 | Under the hood, this uses Github's GraphQL API to query for the commits a user has pushed and then extracts their contact information from them. 6 | 7 | ![Screenshot highlighting basic usage of this tool](./img/screenshot.png) 8 | 9 | ## Installation 10 | 11 | You can either download precompiled binaries from the [releases page](https://github.com/beescuit/githubid/releases/tag/v1.0.0) or run `go install github.com/beescuit/githubid@latest`. 12 | 13 | ## Usage 14 | 15 | You'll need a Github Token to use this tool. You can generate a Personal Access Token [here](https://github.com/settings/tokens). 16 | 17 | You can set your token as the `GH_TOKEN` environment variable or pass it through a flag: 18 | ```bash 19 | $ githubid -user torvalds -token 20 | ``` 21 | 22 | ## TODO 23 | 24 | - [ ] Add pagination support for the GraphQL call; 25 | - [ ] Add [gharchive.org](https://www.gharchive.org/)/bigquery support to allow finding deleted commits (accidentally got a $100 bill from google cloud and am too depressed to do this now); -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/beescuit/githubid 2 | 3 | go 1.22.4 4 | 5 | require ( 6 | github.com/machinebox/graphql v0.2.2 // indirect 7 | github.com/pkg/errors v0.9.1 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo= 2 | github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | -------------------------------------------------------------------------------- /img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beescuit/githubid/bdc772c97f91ac05e01e395d9b8f45d8a7216c6a/img/screenshot.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | 12 | "crypto/tls" 13 | 14 | "github.com/machinebox/graphql" 15 | ) 16 | 17 | var query = ` 18 | 19 | query ($userName: String!, $id: ID) { 20 | user(login: $userName) { 21 | repositoriesContributedTo( 22 | includeUserRepositories: true 23 | contributionTypes: COMMIT 24 | first: 100 25 | ) { 26 | pageInfo { 27 | hasNextPage 28 | endCursor 29 | } 30 | nodes { 31 | refs(first: 100, refPrefix: "refs/") { 32 | nodes { 33 | target { 34 | ... on Commit { 35 | history(author: { id: $id }) { 36 | pageInfo { 37 | hasNextPage 38 | endCursor 39 | } 40 | nodes { 41 | author { 42 | email 43 | name 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | ` 56 | 57 | func main() { 58 | username := flag.String("user", "", "(REQUIRED) Username of the target github account") 59 | printsource := flag.Bool("source", false, "Print commit URLs alongside discovered identities") 60 | showall := flag.Bool("all", false, "Print all commits (will repeat duplicate identities)") 61 | flagtoken := flag.String("token", "", "Github API Bearer token (can also be set from the GH_TOKEN env variable)") 62 | 63 | flag.Parse() 64 | 65 | if *username == "" { 66 | flag.PrintDefaults() 67 | os.Exit(0) 68 | } 69 | 70 | var token = "" 71 | 72 | if *flagtoken == "" { 73 | token = os.Getenv("GH_TOKEN") 74 | } else { 75 | token = *flagtoken 76 | } 77 | 78 | if token == "" { 79 | fmt.Println("Github token missing. Please generate one and set it through the -token flag or the GH_TOKEN environment variable") 80 | os.Exit(0) 81 | } 82 | 83 | http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 84 | userreq, err := http.NewRequest("GET", fmt.Sprintf("http://api.github.com/users/%s", *username), nil) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | userreq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 90 | 91 | httpclient := http.Client{} 92 | res, err := httpclient.Do(userreq) 93 | if err != nil { 94 | fmt.Printf("Error fetching user ID: %s\n", err) 95 | os.Exit(1) 96 | } 97 | 98 | if res.StatusCode == 401 { 99 | fmt.Println("Your Github token seems to be invalid.") 100 | os.Exit(1) 101 | } 102 | 103 | var userres struct { 104 | NodeID string `json:"node_id"` 105 | } 106 | 107 | err = json.NewDecoder(res.Body).Decode(&userres) 108 | if err != nil { 109 | fmt.Printf("Error parsing github api response: %s\n", err) 110 | os.Exit(1) 111 | } 112 | 113 | userid := userres.NodeID 114 | 115 | client := graphql.NewClient("https://api.github.com/graphql") 116 | 117 | req := graphql.NewRequest(query) 118 | 119 | req.Var("userName", username) 120 | req.Var("id", userid) 121 | 122 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 123 | 124 | var respData struct { 125 | User struct { 126 | RepositoriesContributedTo struct { 127 | PageInfo struct { 128 | HasNextPage bool 129 | EndCursor string 130 | } 131 | Nodes []struct { 132 | Refs struct { // 133 | Nodes []struct { 134 | Target struct { 135 | History struct { 136 | PageInfo struct { 137 | HasNextPage bool 138 | EndCursor string 139 | } 140 | Nodes []struct { 141 | CommitURL string 142 | Author struct { 143 | Email string 144 | Name string 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | err = client.Run(context.Background(), req, &respData) 156 | if err != nil { 157 | log.Fatalf("Failed to execute request: %v", err) 158 | } 159 | 160 | unique := make(map[string]bool) 161 | 162 | for _, repo := range respData.User.RepositoriesContributedTo.Nodes { 163 | for _, ref := range repo.Refs.Nodes { 164 | for _, commit := range ref.Target.History.Nodes { 165 | identity := fmt.Sprintf("%s <%s>", commit.Author.Name, commit.Author.Email) 166 | if _, exists := unique[identity]; *showall || !exists { 167 | unique[identity] = true 168 | if *printsource { 169 | fmt.Printf("%s - %s\n", identity, commit.CommitURL) 170 | } else { 171 | fmt.Println(identity) 172 | } 173 | } 174 | } 175 | } 176 | 177 | } 178 | } 179 | --------------------------------------------------------------------------------