4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+ "io"
7
8
"net/http"
8
9
9
10
regexp "github.com/wasilibs/go-re2"
@@ -23,7 +24,6 @@ var _ detectors.Detector = (*Scanner)(nil)
23
24
24
25
var (
25
26
defaultClient = common .SaneHttpClient ()
26
- identifierPat = regexp .MustCompile (`(?i)sid.{0,20}AC[0-9a-f]{32}` ) // Should we have this? Seems restrictive.
27
27
sidPat = regexp .MustCompile (`\bAC[0-9a-f]{32}\b` )
28
28
keyPat = regexp .MustCompile (`\b[0-9a-f]{32}\b` )
29
29
)
@@ -38,22 +38,24 @@ type service struct {
38
38
AccountSID string `json:"account_sid"` // account sid
39
39
}
40
40
41
+ func (s Scanner ) getClient () * http.Client {
42
+ if s .client != nil {
43
+ return s .client
44
+ }
45
+
46
+ return defaultClient
47
+ }
48
+
41
49
// Keywords are used for efficiently pre-filtering chunks.
42
50
// Use identifiers in the secret preferably, or the provider name.
43
51
func (s Scanner ) Keywords () []string {
44
- return []string {"sid" }
52
+ return []string {"sid" , "twilio" }
45
53
}
46
54
47
55
// FromData will find and optionally verify Twilio secrets in a given set of bytes.
48
56
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
49
57
dataStr := string (data )
50
58
51
- identifierMatches := identifierPat .FindAllString (dataStr , - 1 )
52
-
53
- if len (identifierMatches ) == 0 {
54
- return
55
- }
56
-
57
59
keyMatches := keyPat .FindAllString (dataStr , - 1 )
58
60
sidMatches := sidPat .FindAllString (dataStr , - 1 )
59
61
@@ -71,46 +73,20 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
71
73
}
72
74
73
75
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 )
78
79
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
83
82
}
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 }
108
86
}
109
87
}
110
88
111
- if len (keyMatches ) > 0 {
112
- results = append (results , s1 )
113
- }
89
+ results = append (results , s1 )
114
90
}
115
91
}
116
92
@@ -124,3 +100,40 @@ func (s Scanner) Type() detectorspb.DetectorType {
124
100
func (s Scanner ) Description () string {
125
101
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."
126
102
}
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