44 "context"
55 "encoding/json"
66 "fmt"
7+ "io"
78 "net/http"
89
910 regexp "github.com/wasilibs/go-re2"
@@ -23,7 +24,6 @@ var _ detectors.Detector = (*Scanner)(nil)
2324
2425var (
2526 defaultClient = common .SaneHttpClient ()
26- identifierPat = regexp .MustCompile (`(?i)sid.{0,20}AC[0-9a-f]{32}` ) // Should we have this? Seems restrictive.
2727 sidPat = regexp .MustCompile (`\bAC[0-9a-f]{32}\b` )
2828 keyPat = regexp .MustCompile (`\b[0-9a-f]{32}\b` )
2929)
@@ -38,22 +38,24 @@ type service struct {
3838 AccountSID string `json:"account_sid"` // account sid
3939}
4040
41+ func (s Scanner ) getClient () * http.Client {
42+ if s .client != nil {
43+ return s .client
44+ }
45+
46+ return defaultClient
47+ }
48+
4149// Keywords are used for efficiently pre-filtering chunks.
4250// Use identifiers in the secret preferably, or the provider name.
4351func (s Scanner ) Keywords () []string {
44- return []string {"sid" }
52+ return []string {"sid" , "twilio" }
4553}
4654
4755// FromData will find and optionally verify Twilio secrets in a given set of bytes.
4856func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
4957 dataStr := string (data )
5058
51- identifierMatches := identifierPat .FindAllString (dataStr , - 1 )
52-
53- if len (identifierMatches ) == 0 {
54- return
55- }
56-
5759 keyMatches := keyPat .FindAllString (dataStr , - 1 )
5860 sidMatches := sidPat .FindAllString (dataStr , - 1 )
5961
@@ -71,46 +73,20 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
7173 }
7274
7375 if verify {
74- client := s .client
75- if client == nil {
76- client = defaultClient
77- }
76+ extraData , isVerified , verificationErr := verifyTwilio (ctx , s .getClient (), key , sid )
77+ s1 .Verified = isVerified
78+ s1 .SetVerificationError (verificationErr )
7879
79- req , err := http .NewRequestWithContext (
80- ctx , "GET" , "https://verify.twilio.com/v2/Services" , nil )
81- if err != nil {
82- continue
80+ for key , value := range extraData {
81+ s1 .ExtraData [key ] = value
8382 }
84- req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
85- req .Header .Add ("Accept" , "*/*" )
86- req .SetBasicAuth (sid , key )
87- res , err := client .Do (req )
88- if err == nil {
89- defer res .Body .Close ()
90-
91- if res .StatusCode >= 200 && res .StatusCode < 300 {
92- s1 .Verified = true
93- s1 .AnalysisInfo = map [string ]string {"key" : key , "sid" : sid }
94- var serviceResponse serviceResponse
95- if err := json .NewDecoder (res .Body ).Decode (& serviceResponse ); err == nil && len (serviceResponse .Services ) > 0 { // no error in parsing and have at least one service
96- service := serviceResponse .Services [0 ]
97- s1 .ExtraData ["friendly_name" ] = service .FriendlyName
98- s1 .ExtraData ["account_sid" ] = service .AccountSID
99- }
100- } else if res .StatusCode == 401 || res .StatusCode == 403 {
101- // The secret is determinately not verified (nothing to do)
102- } else {
103- err = fmt .Errorf ("unexpected HTTP response status %d" , res .StatusCode )
104- s1 .SetVerificationError (err , key )
105- }
106- } else {
107- s1 .SetVerificationError (err , key )
83+
84+ if s1 .Verified {
85+ s1 .AnalysisInfo = map [string ]string {"key" : key , "sid" : sid }
10886 }
10987 }
11088
111- if len (keyMatches ) > 0 {
112- results = append (results , s1 )
113- }
89+ results = append (results , s1 )
11490 }
11591 }
11692
@@ -124,3 +100,40 @@ func (s Scanner) Type() detectorspb.DetectorType {
124100func (s Scanner ) Description () string {
125101 return "Twilio is a cloud communications platform that allows software developers to programmatically make and receive phone calls, send and receive text messages, and perform other communication functions using its web service APIs."
126102}
103+
104+ func verifyTwilio (ctx context.Context , client * http.Client , key , sid string ) (map [string ]string , bool , error ) {
105+ req , err := http .NewRequestWithContext (ctx , "GET" , "https://verify.twilio.com/v2/Services" , nil )
106+ if err != nil {
107+ return nil , false , nil
108+ }
109+
110+ req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
111+ req .Header .Add ("Accept" , "*/*" )
112+ req .SetBasicAuth (sid , key )
113+ resp , err := client .Do (req )
114+ if err != nil {
115+ return nil , false , nil
116+ }
117+ defer func () {
118+ _ , _ = io .Copy (io .Discard , resp .Body )
119+ _ = resp .Body .Close ()
120+ }()
121+
122+ switch resp .StatusCode {
123+ case http .StatusOK :
124+ extraData := make (map [string ]string )
125+ var serviceResponse serviceResponse
126+
127+ if err := json .NewDecoder (resp .Body ).Decode (& serviceResponse ); err == nil && len (serviceResponse .Services ) > 0 { // no error in parsing and have at least one service
128+ service := serviceResponse .Services [0 ]
129+ extraData ["friendly_name" ] = service .FriendlyName
130+ extraData ["account_sid" ] = service .AccountSID
131+ }
132+
133+ return extraData , true , nil
134+ case http .StatusUnauthorized , http .StatusForbidden :
135+ return nil , false , nil
136+ default :
137+ return nil , false , fmt .Errorf ("unexpected HTTP response status %d" , resp .StatusCode )
138+ }
139+ }
0 commit comments