@@ -2,10 +2,9 @@ package clickhelp
22
33import (
44 "context"
5- b64 "encoding/base64"
65 "fmt"
6+ "io"
77 "net/http"
8- "strings"
98
109 regexp "github.com/wasilibs/go-re2"
1110
@@ -25,55 +24,57 @@ var (
2524 client = common .SaneHttpClient ()
2625
2726 // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
28- serverPat = regexp .MustCompile (`\b([0-9A-Za-z]{3,20}. try. clickhelp.co)\b` )
29- emailPat = regexp .MustCompile (`\b([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-z]+)\b` )
30- keyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"clickhelp" }) + `\b([0-9A-Za-z]{24})\b` )
27+ portalPat = regexp .MustCompile (`\b([0-9A-Za-z- ]{3,20}\.(?: try\.)? clickhelp\ .co)\b` )
28+ emailPat = regexp .MustCompile (common . EmailPattern )
29+ keyPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"clickhelp" , "key" , "token" , "api" , "secret" }) + `\b([0-9A-Za-z]{24})\b` )
3130)
3231
32+ func (s Scanner ) Type () detectorspb.DetectorType {
33+ return detectorspb .DetectorType_ClickHelp
34+ }
35+
36+ func (s Scanner ) Description () string {
37+ return "ClickHelp is a documentation tool that allows users to create and manage online documentation. ClickHelp API keys can be used to access and modify documentation data."
38+ }
39+
3340// Keywords are used for efficiently pre-filtering chunks.
3441// Use identifiers in the secret preferably, or the provider name.
3542func (s Scanner ) Keywords () []string {
36- return []string {"clickhelp" }
43+ return []string {"clickhelp.co " }
3744}
3845
3946// FromData will find and optionally verify Clickhelp secrets in a given set of bytes.
4047func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
4148 dataStr := string (data )
4249
43- serverMatches := serverPat .FindAllStringSubmatch (dataStr , - 1 )
44- emailMatches := emailPat .FindAllStringSubmatch (dataStr , - 1 )
45- keyMatches := keyPat .FindAllStringSubmatch (dataStr , - 1 )
50+ var uniquePortalLinks , uniqueEmails , uniqueAPIKeys = make (map [string ]struct {}), make (map [string ]struct {}), make (map [string ]struct {})
4651
47- for _ , match := range serverMatches {
48- resServer := strings .TrimSpace (match [1 ])
52+ for _ , match := range portalPat .FindAllStringSubmatch (dataStr , - 1 ) {
53+ uniquePortalLinks [match [1 ]] = struct {}{}
54+ }
55+
56+ for _ , match := range emailPat .FindAllStringSubmatch (dataStr , - 1 ) {
57+ uniqueEmails [match [1 ]] = struct {}{}
58+ }
4959
50- for _ , emailMatch := range emailMatches {
51- resEmail := strings .TrimSpace (emailMatch [1 ])
52- for _ , keyMatch := range keyMatches {
53- resKey := strings .TrimSpace (keyMatch [1 ])
60+ for _ , match := range keyPat .FindAllStringSubmatch (dataStr , - 1 ) {
61+ uniqueAPIKeys [match [1 ]] = struct {}{}
62+ }
5463
64+ for portalLink := range uniquePortalLinks {
65+ for email := range uniqueEmails {
66+ for apiKey := range uniqueAPIKeys {
5567 s1 := detectors.Result {
5668 DetectorType : detectorspb .DetectorType_ClickHelp ,
57- Raw : []byte (resServer ),
58- RawV2 : []byte (resServer + resEmail ),
69+ Raw : []byte (portalLink ),
70+ RawV2 : []byte (portalLink + email ),
5971 }
6072
6173 if verify {
62- data := fmt .Sprintf ("%s:%s" , resEmail , resKey )
63- sEnc := b64 .StdEncoding .EncodeToString ([]byte (data ))
64- req , err := http .NewRequestWithContext (ctx , "GET" , fmt .Sprintf ("https://%s/api/v1/projects" , resServer ), nil )
65- if err != nil {
66- continue
67- }
68- req .Header .Add ("Content-Type" , "application/json" )
69- req .Header .Add ("Authorization" , fmt .Sprintf ("Basic %s" , sEnc ))
70- res , err := client .Do (req )
71- if err == nil {
72- defer res .Body .Close ()
73- if res .StatusCode >= 200 && res .StatusCode < 300 {
74- s1 .Verified = true
75- }
76- }
74+ isVerified , verificationErr := verifyClickHelp (ctx , client , portalLink , email , apiKey )
75+ s1 .Verified = isVerified
76+ s1 .SetVerificationError (verificationErr )
77+ s1 .SetPrimarySecretValue (apiKey ) // line number will point to api key
7778 }
7879
7980 results = append (results , s1 )
@@ -84,10 +85,31 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
8485 return results , nil
8586}
8687
87- func (s Scanner ) Type () detectorspb.DetectorType {
88- return detectorspb .DetectorType_ClickHelp
89- }
88+ func verifyClickHelp (ctx context.Context , client * http.Client , portalLink , email , apiKey string ) (bool , error ) {
89+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , fmt .Sprintf ("https://%s/api/v1/projects" , portalLink ), http .NoBody )
90+ if err != nil {
91+ return false , err
92+ }
9093
91- func (s Scanner ) Description () string {
92- return "ClickHelp is a documentation tool that allows users to create and manage online documentation. ClickHelp API keys can be used to access and modify documentation data."
94+ req .Header .Add ("Content-Type" , "application/json" )
95+ req .SetBasicAuth (email , apiKey )
96+
97+ resp , err := client .Do (req )
98+ if err != nil {
99+ return false , err
100+ }
101+
102+ defer func () {
103+ _ , _ = io .Copy (io .Discard , resp .Body )
104+ _ = resp .Body .Close ()
105+ }()
106+
107+ switch resp .StatusCode {
108+ case http .StatusOK :
109+ return true , nil
110+ case http .StatusUnauthorized :
111+ return false , nil
112+ default :
113+ return false , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
114+ }
93115}
0 commit comments