@@ -16,10 +16,8 @@ type Scanner struct {
1616 detectors.DefaultMultiPartCredentialProvider
1717}
1818
19- // Ensure the Scanner satisfies the interface at compile time.
2019var _ detectors.Detector = (* Scanner )(nil )
2120
22- // GCP Oauth2 Client ID and secret regex patterns.
2321var (
2422 oauth2ClientID = regexp .MustCompile ("[0-9a-zA-Z\\ -_]{16,}\\ .apps\\ .googleusercontent\\ .com" )
2523 oauth2ClientSecret = regexp .MustCompile ("GOCSPX-[0-9a-zA-Z\\ -_]{20,}" )
@@ -29,10 +27,8 @@ const (
2927 gcpOAuthBadVerificationCodeError = "bad_verification_code"
3028)
3129
32- // Keywords are used for efficiently pre-filtering chunks.
33- // Use identifiers in the secret preferably, or the provider name.
3430func (s Scanner ) Keywords () []string {
35- return []string {".apps.googleusercontent.com" , "GOCSPX-" }
31+ return []string {".apps.googleusercontent.com" , "GOCSPX-" , "oauth2_client_id" , "oauth2_client_secret" }
3632}
3733
3834func (s Scanner ) Type () detectorspb.DetectorType {
@@ -43,40 +39,87 @@ func (s Scanner) Description() string {
4339 return "GCP OAuth2 credentials are sensitive strings (client ID and secret) issued by Google Cloud to identify your application and securely authorize its access to Google APIs on behalf of users."
4440}
4541
46- // FromData will find and optionally verify GitHub secrets in a given set of bytes.
4742func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
4843 dataStr := string (data )
4944
50- // Oauth2 client ID and secret
5145 oauth2ClientIDMatches := oauth2ClientID .FindAllStringSubmatch (dataStr , - 1 )
5246 oauth2ClientSecretMatches := oauth2ClientSecret .FindAllStringSubmatch (dataStr , - 1 )
5347
54- for _ , idMatch := range oauth2ClientIDMatches {
55- for _ , secretMatch := range oauth2ClientSecretMatches {
56-
57- s1 := detectors.Result {
58- DetectorType : detectorspb .DetectorType_GoogleOauth2 ,
59- Raw : []byte (idMatch [1 ]),
60- RawV2 : []byte (idMatch [1 ] + secretMatch [1 ]),
48+ seen := make (map [string ]bool )
49+
50+ pairedIDs := make (map [string ]bool )
51+ pairedSecrets := make (map [string ]bool )
52+
53+ if len (oauth2ClientIDMatches ) > 0 && len (oauth2ClientSecretMatches ) > 0 {
54+ for _ , idMatch := range oauth2ClientIDMatches {
55+ for _ , secretMatch := range oauth2ClientSecretMatches {
56+ clientID := idMatch [0 ]
57+ clientSecret := secretMatch [0 ]
58+ key := "pair:" + clientID + ":" + clientSecret
59+
60+ if ! seen [key ] {
61+ seen [key ] = true
62+ pairedIDs [clientID ] = true
63+ pairedSecrets [clientSecret ] = true
64+
65+ s1 := detectors.Result {
66+ DetectorType : detectorspb .DetectorType_GoogleOauth2 ,
67+ Raw : []byte (clientID ),
68+ RawV2 : []byte (clientID + clientSecret ),
69+ }
70+
71+ if verify {
72+ config := & clientcredentials.Config {
73+ ClientID : clientID ,
74+ ClientSecret : clientSecret ,
75+ TokenURL : google .Endpoint .TokenURL ,
76+ }
77+ _ , err := config .Token (ctx )
78+ if err != nil && strings .Contains (err .Error (), gcpOAuthBadVerificationCodeError ) {
79+ s1 .Verified = true
80+ }
81+ }
82+
83+ results = append (results , s1 )
84+ }
6185 }
86+ }
87+ }
6288
63- config := & clientcredentials.Config {
64- ClientID : idMatch [1 ],
65- ClientSecret : secretMatch [1 ],
66- TokenURL : google .Endpoint .TokenURL ,
67- }
68- if verify {
69- _ , err := config .Token (ctx )
70- // if client id and client secret is correct, it will return bad verification code error as we do not pass any verification code
71- if err != nil && strings .Contains (err .Error (), gcpOAuthBadVerificationCodeError ) {
72- // mark result as verified only in case of bad verification code error, for any other error the result will be unverified
73- s1 .Verified = true
89+ // Process orphan ClientID-only matches (not part of any pair)
90+ if len (oauth2ClientIDMatches ) > 0 && len (pairedIDs ) == 0 {
91+ for _ , idMatch := range oauth2ClientIDMatches {
92+ clientID := idMatch [0 ]
93+ key := "id:" + clientID
94+
95+ if ! pairedIDs [clientID ] && ! seen [key ] {
96+ seen [key ] = true
97+ s1 := detectors.Result {
98+ DetectorType : detectorspb .DetectorType_GoogleOauth2 ,
99+ Raw : []byte (clientID ),
100+ RawV2 : []byte (clientID ),
74101 }
102+ results = append (results , s1 )
75103 }
76-
77- results = append (results , s1 )
78104 }
79105 }
80106
107+ // Process orphan ClientSecret-only matches (not part of any pair)
108+ if len (oauth2ClientSecretMatches ) > 0 && len (pairedSecrets ) == 0 {
109+ for _ , secretMatch := range oauth2ClientSecretMatches {
110+ clientSecret := secretMatch [0 ]
111+ key := "secret:" + clientSecret
112+
113+ if ! pairedSecrets [clientSecret ] && ! seen [key ] {
114+ seen [key ] = true
115+ s1 := detectors.Result {
116+ DetectorType : detectorspb .DetectorType_GoogleOauth2 ,
117+ Raw : []byte (clientSecret ),
118+ RawV2 : []byte (clientSecret ),
119+ }
120+ results = append (results , s1 )
121+ }
122+ }
123+ }
81124 return
82125}
0 commit comments