Skip to content

Commit 68f845f

Browse files
authored
Merge pull request #15 from CallMeGreg/callmegreg/more-validation
Added repo name validation and settings validation
2 parents 436b195 + 671493a commit 68f845f

File tree

4 files changed

+102
-44
lines changed

4 files changed

+102
-44
lines changed

cmd/alerts.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,19 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {
2222
// set scope & target based on the flag that was used:
2323
scope, target, err := getScopeAndTarget()
2424
if err != nil {
25-
fmt.Println(err)
26-
return
25+
return err
2726
}
2827

2928
// set the API URL based on the target:
3029
requestPath, err := createGitHubSecretAlertsAPIPath(scope, target)
3130
if err != nil {
32-
fmt.Println(err)
33-
return
31+
return err
3432
}
3533

3634
// update the URL to include query parameters based on specified flags:
3735
parsedURL, err := url.Parse(requestPath)
3836
if err != nil {
39-
fmt.Println(err)
40-
return
37+
return err
4138
}
4239
values := parsedURL.Query()
4340

@@ -49,7 +46,7 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {
4946
}
5047
per_page_int, err := strconv.Atoi(per_page)
5148
if err != nil {
52-
fmt.Println(err)
49+
return err
5350
}
5451
values.Set("per_page", per_page)
5552
// if provider was specified, filter results. Otherwise, return all results:
@@ -73,21 +70,17 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {
7370
opts := setOptions()
7471
client, err := api.NewRESTClient(opts)
7572
if err != nil {
76-
fmt.Println(err)
7773
return err
7874
}
7975

8076
for page := 1; page <= pages; page++ {
8177
fmt.Println("Processing page: " + strconv.Itoa(page))
8278
_, nextPage, err := callGitHubAPI(client, requestPath, &pageOfSecretAlerts, GET)
8379
if err != nil {
84-
fmt.Println("ERROR: Unable to get alerts for target: " + requestPath)
8580
return err
8681
}
87-
for _, secretAlert := range pageOfSecretAlerts {
88-
// add each secret alert in the response page to allSecretAlerts array
89-
allSecretAlerts = append(allSecretAlerts, secretAlert)
90-
}
82+
// add each secret alert in the response page to allSecretAlerts array
83+
allSecretAlerts = append(allSecretAlerts, pageOfSecretAlerts...)
9184
var hasNextPage bool
9285
if requestPath, hasNextPage = findNextPage(nextPage); !hasNextPage {
9386
break
@@ -114,7 +107,6 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {
114107
if len(sortedAlerts) > 0 && csvReport {
115108
err = generateCSVReport(sortedAlerts, scope, false)
116109
if err != nil {
117-
fmt.Println(err)
118110
return err
119111
}
120112
}

cmd/common.go

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func createGitHubSecretAlertsAPIPath(scope string, target string) (apiURL string
8484
replacer := strings.NewReplacer("{owner}", owner, "{repo}", repo)
8585
apiURL = replacer.Replace(repositoryAlertsURL)
8686
default:
87-
err = fmt.Errorf("Invalid API target.")
87+
err = fmt.Errorf("invalid API target")
8888
}
8989
return apiURL, err
9090
}
@@ -159,13 +159,11 @@ func callGitHubAPI(client *api.RESTClient, requestPath string, parseType interfa
159159
nextPage := response.Header.Get("Link")
160160
responseBody, err := io.ReadAll(response.Body)
161161
if err != nil {
162-
fmt.Println("ERROR: Unable to read next page link")
163162
return response.StatusCode, nextPage, err
164163
}
165164

166165
err = decodeJSONResponse(responseBody, &parseType)
167166
if err != nil {
168-
fmt.Println("ERROR: Unable to decode JSON response")
169167
return response.StatusCode, nextPage, err
170168
}
171169

@@ -176,7 +174,6 @@ func decodeJSONResponse(body []byte, parseType interface{}) error {
176174
decoder := json.NewDecoder(bytes.NewReader(body))
177175
err := decoder.Decode(&parseType)
178176
if err != nil {
179-
fmt.Println("ERROR: Unable to decode JSON response")
180177
return err
181178
}
182179

@@ -200,11 +197,11 @@ func validateProvider(provider string) (err error) {
200197
providerList = append(providerList, key)
201198
}
202199
for _, item := range providerList {
203-
if strings.ToLower(item) == strings.ToLower(provider) {
200+
if strings.EqualFold(item, provider) {
204201
return nil
205202
}
206203
}
207-
err = fmt.Errorf(Red("Invalid provider: " + provider + "\nValid providers are: " + strings.Join(providerList, ", ")))
204+
err = fmt.Errorf("Invalid provider: " + provider + "\nValid providers are: " + strings.Join(providerList, ", "))
208205
return err
209206
}
210207

@@ -250,6 +247,20 @@ func getScopeAndTarget() (scope string, target string, err error) {
250247
target = organization
251248
} else if repository != "" {
252249
scope = "repository"
250+
repoPattern := regexp.MustCompile(`^[^/]+/[^/]+$`)
251+
if !repoPattern.MatchString(repository) {
252+
err = errors.New("repository must follow the format 'owner/repository'")
253+
return "", "", err
254+
}
255+
// check if secret scanning is enabled for the repository:
256+
secretScanningEnabled, err := checkSecretScanningSetting(repository)
257+
if err != nil {
258+
return "", "", err
259+
}
260+
if !secretScanningEnabled {
261+
err = errors.New("Secret scanning is not enabled for the repository: " + repository)
262+
return "", "", err
263+
}
253264
target = repository
254265
}
255266
return scope, target, err
@@ -341,11 +352,10 @@ func generateCSVReport(alerts []Alert, scope string, validity_check bool) (err e
341352
now := time.Now()
342353
// Format the time as YYYYMMDD-HHMMSS
343354
timestamp := now.Format("20060102-150405")
344-
filename := "secretscanningreport-" + scope + "-" + timestamp + ".csv"
355+
filename := "SecretScanningReport-" + scope + "-" + timestamp + ".csv"
345356
// Create a CSV file
346357
file, err := os.Create(filename)
347358
if err != nil {
348-
fmt.Println("ERROR: Error creating CSV file.")
349359
return err
350360
}
351361
defer file.Close()
@@ -381,7 +391,6 @@ func generateCSVReport(alerts []Alert, scope string, validity_check bool) (err e
381391
counter++
382392
}
383393
if err := writer.Error(); err != nil {
384-
fmt.Println("ERROR: Error writing to CSV file.")
385394
return err
386395
}
387396
fmt.Println(Blue("CSV report generated: " + filename))
@@ -420,19 +429,17 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
420429
}
421430
client, err := api.NewHTTPClient(opts)
422431
if err != nil {
423-
fmt.Println("ERROR: Unable to create HTTP client.")
424432
return alerts, err
425433
}
426434
// send a request to the validation endpoint:
427435
var response *http.Response
428436
if secret_validation_method == "POST" {
429437
var body io.Reader
430-
req, err := http.NewRequest("POST", alert.Validity_endpoint, body)
438+
req, _ := http.NewRequest("POST", alert.Validity_endpoint, body)
431439
req.Header.Set("Authorization", "Bearer "+alert.Secret)
432440
req.Header.Set("Content-Type", secret_validation_content_type)
433441
req.Header.Set("User-Agent", "gh-secret-scanning")
434442
response, err = client.Do(req)
435-
// response, err = client.Post(alert.Validity_endpoint, secret_validation_content_type, body)
436443
if err != nil {
437444
fmt.Println("WARNING: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
438445
continue
@@ -462,7 +469,7 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
462469
} else {
463470
alert.Validity_boolean = false
464471
}
465-
if provider == "github" && alert.Validity_boolean == false && host != "github.com" {
472+
if provider == "github" && !alert.Validity_boolean && host != "github.com" {
466473
// also confirm validity with the provided GitHub Enterprise Server API:
467474
alert = checkEnterpriseServerAPI(alert, client, secret_validation_method, secret_validation_content_type)
468475
if alert.Validity_response_code == "200" {
@@ -510,7 +517,7 @@ func checkForExpectedBody(response *http.Response, expected_body_key string, exp
510517
func checkEnterpriseServerAPI(alert Alert, client *http.Client, secret_validation_method string, secret_validation_content_type string) (alertOutput Alert) {
511518
enterprise_server_api_endpoint := "https://" + host + "/api/v3/"
512519
// create a new http request:
513-
req, err := http.NewRequest("GET", enterprise_server_api_endpoint, nil)
520+
req, _ := http.NewRequest("GET", enterprise_server_api_endpoint, nil)
514521
req.Header.Set("Authorization", "Bearer "+alert.Secret)
515522
req.Header.Set("Content-Type", secret_validation_content_type)
516523
req.Header.Set("User-Agent", "gh-secret-scanning")
@@ -572,3 +579,63 @@ func createIssuesForValidAlerts(alerts []Alert) (err error) {
572579
fmt.Println(Blue("Created " + strconv.Itoa(issue_count) + " issue(s)."))
573580
return err
574581
}
582+
583+
func checkSecretScanningSetting(repository string) (secretScanningEnabled bool, err error) {
584+
// create a new client for the request:
585+
var opts api.ClientOptions
586+
opts.Headers = map[string]string{
587+
"User-Agent": "gh-secret-scanning",
588+
}
589+
client, err := api.NewHTTPClient(opts)
590+
if err != nil {
591+
return false, fmt.Errorf("unable to create HTTP client")
592+
}
593+
// make a GET request to the repository API:
594+
response, err := client.Get("https://api.github.com/repos/" + repository)
595+
596+
if err != nil {
597+
return false, fmt.Errorf("unable to get repository information for " + repository)
598+
}
599+
defer response.Body.Close()
600+
601+
// check if the response has a 200 status code:
602+
if response.StatusCode != http.StatusOK {
603+
return false, fmt.Errorf("non-200 status code received: %d", response.StatusCode)
604+
}
605+
606+
// parse the JSON response
607+
var result map[string]interface{}
608+
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
609+
return false, fmt.Errorf("unable to parse JSON response")
610+
}
611+
612+
// navigate to the required fields
613+
securityAndAnalysis, ok := result["security_and_analysis"].(map[string]interface{})
614+
if !ok {
615+
return false, fmt.Errorf("security_and_analysis field not found")
616+
}
617+
618+
advancedSecurity, ok := securityAndAnalysis["advanced_security"].(map[string]interface{})
619+
if !ok {
620+
return false, fmt.Errorf("advanced_security field not found")
621+
}
622+
623+
status, ok := advancedSecurity["status"].(string)
624+
if !ok || status != "enabled" {
625+
return false, fmt.Errorf("Advanced Security is not enabled for " + repository + Yellow("\nEnable GitHub Advanced Security for the repository here: https://github.com/"+repository+"/settings/security_analysis"))
626+
}
627+
628+
secretScanning, ok := securityAndAnalysis["secret_scanning"].(map[string]interface{})
629+
if !ok {
630+
return false, fmt.Errorf("secret_scanning field not found")
631+
}
632+
633+
secretScanningStatus, ok := secretScanning["status"].(string)
634+
if !ok || secretScanningStatus != "enabled" {
635+
return false, fmt.Errorf("secret scanning is not enabled for " + repository + Yellow("\nEnable secret scanning for the repository here: https://github.com/"+repository+"/settings/security_analysis"))
636+
} else {
637+
secretScanningEnabled = true
638+
}
639+
640+
return secretScanningEnabled, nil
641+
}

cmd/root.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"fmt"
55
"log"
6+
"strings"
67

78
"github.com/spf13/cobra"
89
)
@@ -37,13 +38,16 @@ func init() {
3738
// disable completion subcommand:
3839
rootCmd.CompletionOptions.DisableDefaultCmd = true
3940

41+
// silence usage output on error:
42+
rootCmd.SilenceUsage = true
43+
4044
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
4145
// warn user about --show-secret flag:
4246
if secret {
4347
fmt.Println(Yellow("WARNING: --show-secret flag is enabled. Full secret values will be displayed in PLAIN TEXT in the output. Would you like to continue? (y/n)"))
4448
var response string
4549
fmt.Scanln(&response)
46-
if response != "y" {
50+
if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" {
4751
log.Fatal("Exiting...")
4852
}
4953
}

cmd/verify.go

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,19 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
2828
// set scope & target based on the flag that was used:
2929
scope, target, err := getScopeAndTarget()
3030
if err != nil {
31-
fmt.Println(err)
32-
return
31+
return err
3332
}
3433

3534
// set the API URL based on the target:
3635
requestPath, err := createGitHubSecretAlertsAPIPath(scope, target)
3736
if err != nil {
38-
fmt.Println(err)
39-
return
37+
return err
4038
}
4139

4240
// update the URL to include query parameters based on specified flags:
4341
parsedURL, err := url.Parse(requestPath)
4442
if err != nil {
45-
fmt.Println(err)
46-
return
43+
return err
4744
}
4845
values := parsedURL.Query()
4946
var per_page string
@@ -54,7 +51,7 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
5451
}
5552
per_page_int, err := strconv.Atoi(per_page)
5653
if err != nil {
57-
fmt.Println(err)
54+
return err
5855
}
5956
values.Set("per_page", per_page)
6057
// if provider was specified, filter results for just that provider. Otherwise, target all supported providers:
@@ -75,21 +72,17 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
7572
opts := setOptions()
7673
client, err := api.NewRESTClient(opts)
7774
if err != nil {
78-
fmt.Println(err)
7975
return err
8076
}
8177

8278
for page := 1; page <= pages; page++ {
8379
fmt.Println("Processing page: " + strconv.Itoa(page))
8480
_, nextPage, err := callGitHubAPI(client, requestPath, &pageOfSecretAlerts, GET)
8581
if err != nil {
86-
fmt.Println("ERROR: Unable to get alerts for target: " + requestPath)
8782
return err
8883
}
89-
for _, secretAlert := range pageOfSecretAlerts {
90-
// add each secret alert in the response page to allSecretAlerts array
91-
allSecretAlerts = append(allSecretAlerts, secretAlert)
92-
}
84+
// add each secret alert in the response page to allSecretAlerts array
85+
allSecretAlerts = append(allSecretAlerts, pageOfSecretAlerts...)
9386
var hasNextPage bool
9487
if requestPath, hasNextPage = findNextPage(nextPage); !hasNextPage {
9588
break
@@ -110,13 +103,14 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
110103
// verify which secret alerts are confirmed valid:
111104
verifiedAlerts, err := verifyAlerts(sortedAlerts)
112105
if err != nil {
113-
// print to console
114106
fmt.Println("WARNING: issues encountered while sending verify requests.")
115107
}
108+
116109
// pretty print with validity status
117110
if !quiet {
118111
prettyPrintAlerts(verifiedAlerts, true)
119112
}
113+
120114
// optionally generate a csv report of the results:
121115
if len(sortedAlerts) > 0 && csvReport {
122116
err = generateCSVReport(sortedAlerts, scope, true)
@@ -125,6 +119,7 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
125119
return err
126120
}
127121
}
122+
128123
// optionally create an issue for each repository that contains at least one valid secret alert:
129124
if createIssues {
130125
err = createIssuesForValidAlerts(verifiedAlerts)
@@ -133,5 +128,5 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
133128
return err
134129
}
135130
}
136-
return err
131+
return
137132
}

0 commit comments

Comments
 (0)