@@ -3,6 +3,7 @@ package grafana
33import  (
44	"context" 
55	"fmt" 
6+ 	"io" 
67	"net/http" 
78	"strings" 
89
@@ -23,13 +24,22 @@ var _ detectors.Detector = (*Scanner)(nil)
2324var  (
2425	defaultClient  =  common .SaneHttpClient ()
2526	// Make sure that your group is surrounded in boundary characters such as below to reduce false positives. 
26- 	keyPat  =  regexp .MustCompile (`\b(glc_ [A-Za-z0-9+\/]{50,150}\={0,2 })` )
27+ 	keyPat  =  regexp .MustCompile (`\b(glc_eyJ [A-Za-z0-9+\/=]{60,160 })` )
2728)
2829
30+ func  (s  Scanner ) getClient () * http.Client  {
31+ 	client  :=  s .client 
32+ 	if  client  ==  nil  {
33+ 		client  =  defaultClient 
34+ 	}
35+ 
36+ 	return  client 
37+ }
38+ 
2939// Keywords are used for efficiently pre-filtering chunks. 
3040// Use identifiers in the secret preferably, or the provider name. 
3141func  (s  Scanner ) Keywords () []string  {
32- 	return  []string {"glc_ " }
42+ 	return  []string {"glc_eyJ " }
3343}
3444
3545// FromData will find and optionally verify Grafana secrets in a given set of bytes. 
@@ -47,29 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
4757		}
4858
4959		if  verify  {
50- 			client  :=  s .client 
51- 			if  client  ==  nil  {
52- 				client  =  defaultClient 
53- 			}
54- 			req , err  :=  http .NewRequestWithContext (ctx , "GET" , "https://grafana.com/api/v1/tokens?region=us" , nil )
55- 			if  err  !=  nil  {
56- 				continue 
57- 			}
58- 			req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , resMatch ))
59- 			res , err  :=  client .Do (req )
60- 			if  err  ==  nil  {
61- 				defer  res .Body .Close ()
62- 				if  res .StatusCode  >=  200  &&  res .StatusCode  <  300  ||  res .StatusCode  ==  403  {
63- 					s1 .Verified  =  true 
64- 				} else  if  res .StatusCode  ==  401  {
65- 					// The secret is determinately not verified (nothing to do) 
66- 				} else  {
67- 					err  =  fmt .Errorf ("unexpected HTTP response status %d" , res .StatusCode )
68- 					s1 .SetVerificationError (err , resMatch )
69- 				}
70- 			} else  {
71- 				s1 .SetVerificationError (err , resMatch )
72- 			}
60+ 			isVerified , verificationErr  :=  verifyGrafanaKey (ctx , s .getClient (), resMatch )
61+ 			s1 .Verified  =  isVerified 
62+ 			s1 .SetVerificationError (verificationErr , resMatch )
7363		}
7464
7565		results  =  append (results , s1 )
@@ -85,3 +75,37 @@ func (s Scanner) Type() detectorspb.DetectorType {
8575func  (s  Scanner ) Description () string  {
8676	return  "Grafana is an open-source platform for monitoring and observability. Grafana API keys can be used to access and manage Grafana resources." 
8777}
78+ 
79+ func  verifyGrafanaKey (ctx  context.Context , client  * http.Client , token  string ) (bool , error ) {
80+ 	req , err  :=  http .NewRequestWithContext (ctx , http .MethodGet , "https://grafana.com/api/v1/tokens?region=us" , http .NoBody )
81+ 	if  err  !=  nil  {
82+ 		return  false , err 
83+ 	}
84+ 
85+ 	req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , token ))
86+ 
87+ 	resp , err  :=  client .Do (req )
88+ 	if  err  !=  nil  {
89+ 		return  false , err 
90+ 	}
91+ 
92+ 	defer  func () {
93+ 		_ , _  =  io .Copy (io .Discard , resp .Body )
94+ 		_  =  resp .Body .Close ()
95+ 	}()
96+ 
97+ 	switch  resp .StatusCode  {
98+ 	case  http .StatusOK :
99+ 		return  true , nil 
100+ 	case  http .StatusUnauthorized :
101+ 		bodyBytes , err  :=  io .ReadAll (resp .Body )
102+ 		if  err  !=  nil  {
103+ 			return  false , err 
104+ 		}
105+ 
106+ 		// token is valid but has restricted permissions 
107+ 		return  strings .Contains (string (bodyBytes ), "Unauthorized" ), nil 
108+ 	default :
109+ 		return  false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
110+ 	}
111+ }
0 commit comments