diff --git a/pkg/detectors/saucelabs/saucelabs.go b/pkg/detectors/saucelabs/saucelabs.go index 797390eb674d..e8e968f74c25 100644 --- a/pkg/detectors/saucelabs/saucelabs.go +++ b/pkg/detectors/saucelabs/saucelabs.go @@ -25,8 +25,11 @@ var ( // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. // as per signup page username can be between 2 to 70 characters and must only contain letters, numbers, or characters (_-.) - usernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"saucelabs", "username"}) + `\b([a-zA-Z0-9_\.-]{2,70})\b`) + usernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{"saucelabs", "username"}) + `\b([a-zA-Z0-9_\.-]{2,70})`) keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"saucelabs"}) + `\b([a-z0-9]{8}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12})\b`) + baseUrlPat = regexp.MustCompile(`\b(api\.(?:us|eu)-(?:west|east|central)-[0-9].saucelabs\.com)\b`) + + fixedBaseURL = "api.us-west-1.saucelabs.com" ) // Keywords are used for efficiently pre-filtering chunks. @@ -39,7 +42,7 @@ func (s Scanner) Keywords() []string { func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) - uniqueUserNameMatches, uniqueKeyMatches := make(map[string]struct{}), make(map[string]struct{}) + uniqueUserNameMatches, uniqueKeyMatches, uniqueBaseURLMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{}) for _, match := range usernamePat.FindAllStringSubmatch(dataStr, -1) { uniqueUserNameMatches[match[1]] = struct{}{} @@ -49,21 +52,36 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result uniqueKeyMatches[match[1]] = struct{}{} } + for _, match := range baseUrlPat.FindAllStringSubmatch(dataStr, -1) { + uniqueBaseURLMatches[match[1]] = struct{}{} + } + + // if no domain is found, add a fixed domain to try against + if len(uniqueBaseURLMatches) == 0 { + uniqueBaseURLMatches[fixedBaseURL] = struct{}{} + } + for userName := range uniqueUserNameMatches { for key := range uniqueKeyMatches { - s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_SauceLabs, - Raw: []byte(userName), - RawV2: []byte(userName + key), + for baseURL := range uniqueBaseURLMatches { + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_SauceLabs, + Raw: []byte(userName), + RawV2: []byte(userName + key), + ExtraData: map[string]string{ + // add base url in extradata to know which base url was used for verification + "Base URL": baseURL, + }, + } + + if verify { + isVerified, verificationErr := verifySauceLabKey(ctx, client, userName, key, baseURL) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, key) + } + + results = append(results, s1) } - - if verify { - isVerified, verificationErr := verifySauceLabKey(ctx, client, userName, key) - s1.Verified = isVerified - s1.SetVerificationError(verificationErr, key) - } - - results = append(results, s1) } } @@ -79,8 +97,10 @@ func (s Scanner) Description() string { return "A service for cross browser testing, API keys can create and access tests from potentially sensitive internal websites" } -func verifySauceLabKey(ctx context.Context, client *http.Client, userName, key string) (bool, error) { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.eu-central-1.saucelabs.com/team-management/v1/teams", nil) +func verifySauceLabKey(ctx context.Context, client *http.Client, userName, key, baseURL string) (bool, error) { + apiURL := fmt.Sprintf("https://%s/team-management/v1/teams", baseURL) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) if err != nil { return false, err }