88from testing .mocks import mock_file_object
99
1010
11+ STANDARD_NEGATIVES = [
12+ # FOLLOWED_BY_COLON_RE
13+ 'theapikey: ""' , # Nothing in the quotes
14+ 'theapikey: "somefakekey"' , # 'fake' in the secret
15+ 'theapikeyforfoo:hopenobodyfindsthisone' , # Characters between apikey and :
16+ # FOLLOWED_BY_EQUAL_SIGNS_RE
17+ 'some_key = "real_secret"' , # We cannot make 'key' a Keyword, too noisy
18+ 'my_password = foo(hey)you' , # Has a ( followed by a )
19+ "my_password = request.json_body['hey']" , # Has a [ followed by a ]
20+ 'my_password = ""' , # Nothing in the quotes
21+ "my_password = ''" , # Nothing in the quotes
22+ 'my_password = True' , # 'True' is a known false-positive
23+ 'my_password = "fakesecret"' , # 'fake' in the secret
24+ 'login(username=username, password=password)' , # secret is password)
25+ 'open(self, password = ""):' , # secrets is ""):
26+ 'open(self, password = ""):' , # secrets is ""):
27+ # FOLLOWED_BY_QUOTES_AND_SEMICOLON_RE
28+ 'private_key "";' , # Nothing in the quotes
29+ 'private_key \' "no spaces\' ;' , # Has whitespace in the secret
30+ 'private_key "fake";' , # 'fake' in the secret
31+ 'private_key "hopenobodyfindsthisone\' ;' , # Double-quote does not match single-quote
32+ 'private_key \' hopenobodyfindsthisone";' , # Single-quote does not match double-quote
33+ 'password: ${link}' , # Has a ${ followed by a }
34+ ]
35+ STANDARD_POSITIVES = {
36+ # FOLLOWED_BY_COLON_RE
37+ "'theapikey': 'h}o)p${e]nob(ody[finds>-_$#thisone'" ,
38+ '"theapikey": "h}o)p${e]nob(ody[finds>-_$#thisone"' ,
39+ 'apikey: h}o)p${e]nob(ody[finds>-_$#thisone' ,
40+ 'apikey:h}o)p${e]nob(ody[finds>-_$#thisone' ,
41+ 'theapikey:h}o)p${e]nob(ody[finds>-_$#thisone' ,
42+ 'apikey: "h}o)p${e]nob(ody[finds>-_$#thisone"' ,
43+ "apikey: 'h}o)p${e]nob(ody[finds>-_$#thisone'" ,
44+ # FOLLOWED_BY_EQUAL_SIGNS_RE
45+ 'some_dict["secret"] = "h}o)p${e]nob(ody[finds>-_$#thisone"' ,
46+ "some_dict['secret'] = h}o)p${e]nob(ody[finds>-_$#thisone" ,
47+ 'my_password=h}o)p${e]nob(ody[finds>-_$#thisone' ,
48+ 'my_password= h}o)p${e]nob(ody[finds>-_$#thisone' ,
49+ 'my_password =h}o)p${e]nob(ody[finds>-_$#thisone' ,
50+ 'my_password = h}o)p${e]nob(ody[finds>-_$#thisone' ,
51+ 'my_password =h}o)p${e]nob(ody[finds>-_$#thisone' ,
52+ 'the_password=h}o)p${e]nob(ody[finds>-_$#thisone\n ' ,
53+ 'the_password= "h}o)p${e]nob(ody[finds>-_$#thisone"\n ' ,
54+ 'the_password=\' h}o)p${e]nob(ody[finds>-_$#thisone\' \n ' ,
55+ # FOLLOWED_BY_QUOTES_AND_SEMICOLON_RE
56+ 'apikey "h}o)p${e]nob(ody[finds>-_$#thisone";' , # Double-quotes
57+ 'fooapikeyfoo "h}o)p${e]nob(ody[finds>-_$#thisone";' , # Double-quotes
58+ 'fooapikeyfoo"h}o)p${e]nob(ody[finds>-_$#thisone";' , # Double-quotes
59+ 'private_key \' h}o)p${e]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
60+ 'fooprivate_keyfoo\' h}o)p${e]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
61+ 'fooprivate_key\' h}o)p${e]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
62+ }
63+
64+
1165class TestKeywordDetector (object ):
1266
1367 @pytest .mark .parametrize (
1468 'file_content' ,
15- [
16- # FOLLOWED_BY_COLON_RE
17- "'theapikey': 'ho)pe]nob(ody[finds>-_$#thisone'" ,
18- '"theapikey": "ho)pe]nob(ody[finds>-_$#thisone"' ,
19- 'apikey: ho)pe]nob(ody[finds>-_$#thisone' ,
20- 'apikey:ho)pe]nob(ody[finds>-_$#thisone' ,
21- 'theapikey:ho)pe]nob(ody[finds>-_$#thisone' ,
22- 'apikey: "ho)pe]nob(ody[finds>-_$#thisone"' ,
23- "apikey: 'ho)pe]nob(ody[finds>-_$#thisone'" ,
24- # FOLLOWED_BY_EQUAL_SIGNS_RE
25- 'some_dict["secret"] = "ho)pe]nob(ody[finds>-_$#thisone"' ,
26- "some_dict['secret'] = ho)pe]nob(ody[finds>-_$#thisone" ,
27- 'my_password=ho)pe]nob(ody[finds>-_$#thisone' ,
28- 'my_password= ho)pe]nob(ody[finds>-_$#thisone' ,
29- 'my_password =ho)pe]nob(ody[finds>-_$#thisone' ,
30- 'my_password = ho)pe]nob(ody[finds>-_$#thisone' ,
31- 'my_password =ho)pe]nob(ody[finds>-_$#thisone' ,
32- 'the_password=ho)pe]nob(ody[finds>-_$#thisone\n ' ,
33- 'the_password= "ho)pe]nob(ody[finds>-_$#thisone"\n ' ,
34- 'the_password=\' ho)pe]nob(ody[finds>-_$#thisone\' \n ' ,
35- # FOLLOWED_BY_QUOTES_AND_SEMICOLON_RE
36- 'apikey "ho)pe]nob(ody[finds>-_$#thisone";' , # Double-quotes
37- 'fooapikeyfoo "ho)pe]nob(ody[finds>-_$#thisone";' , # Double-quotes
38- 'fooapikeyfoo"ho)pe]nob(ody[finds>-_$#thisone";' , # Double-quotes
39- 'private_key \' ho)pe]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
40- 'fooprivate_keyfoo\' ho)pe]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
41- 'fooprivate_key\' ho)pe]nob(ody[finds>-_$#thisone\' ;' , # Single-quotes
42- ],
69+ STANDARD_POSITIVES ,
4370 )
44- def test_analyze_positives (self , file_content ):
71+ def test_analyze_standard_positives (self , file_content ):
4572 logic = KeywordDetector ()
4673
4774 f = mock_file_object (file_content )
@@ -51,29 +78,25 @@ def test_analyze_positives(self, file_content):
5178 assert 'mock_filename' == potential_secret .filename
5279 assert (
5380 potential_secret .secret_hash
54- == PotentialSecret .hash_secret ('ho)pe ]nob(ody[finds>-_$#thisone' )
81+ == PotentialSecret .hash_secret ('h}o)p${e ]nob(ody[finds>-_$#thisone' )
5582 )
5683
5784 @pytest .mark .parametrize (
5885 'file_content' ,
59- [
86+ STANDARD_POSITIVES - {
6087 # FOLLOWED_BY_COLON_QUOTES_REQUIRED_RE
61- "'theapikey': 'hope]nobody[finds>-_$#thisone'" ,
62- '"theapikey": "hope]nobody[finds>-_$#thisone"' ,
63- 'apikey: "hope]nobody[finds>-_$#thisone"' ,
64- "apikey: 'hope]nobody[finds>-_$#thisone'" ,
88+ 'apikey: h}o)p${e]nob(ody[finds>-_$#thisone' ,
89+ 'apikey:h}o)p${e]nob(ody[finds>-_$#thisone' ,
90+ 'theapikey:h}o)p${e]nob(ody[finds>-_$#thisone' ,
6591 # FOLLOWED_BY_EQUAL_SIGNS_QUOTES_REQUIRED_RE
66- 'some_dict["secret"] = "hope]nobody[finds>-_$#thisone"' ,
67- 'the_password= "hope]nobody[finds>-_$#thisone"\n ' ,
68- 'the_password=\' hope]nobody[finds>-_$#thisone\' \n ' ,
69- # FOLLOWED_BY_QUOTES_AND_SEMICOLON_RE
70- 'apikey "hope]nobody[finds>-_$#thisone";' , # Double-quotes
71- 'fooapikeyfoo "hope]nobody[finds>-_$#thisone";' , # Double-quotes
72- 'fooapikeyfoo"hope]nobody[finds>-_$#thisone";' , # Double-quotes
73- 'private_key \' hope]nobody[finds>-_$#thisone\' ;' , # Single-quotes
74- 'fooprivate_keyfoo\' hope]nobody[finds>-_$#thisone\' ;' , # Single-quotes
75- 'fooprivate_key\' hope]nobody[finds>-_$#thisone\' ;' , # Single-quotes
76- ],
92+ "some_dict['secret'] = h}o)p${e]nob(ody[finds>-_$#thisone" ,
93+ 'my_password=h}o)p${e]nob(ody[finds>-_$#thisone' ,
94+ 'my_password= h}o)p${e]nob(ody[finds>-_$#thisone' ,
95+ 'my_password =h}o)p${e]nob(ody[finds>-_$#thisone' ,
96+ 'my_password = h}o)p${e]nob(ody[finds>-_$#thisone' ,
97+ 'my_password =h}o)p${e]nob(ody[finds>-_$#thisone' ,
98+ 'the_password=h}o)p${e]nob(ody[finds>-_$#thisone\n ' ,
99+ },
77100 )
78101 def test_analyze_python_positives (self , file_content ):
79102 logic = KeywordDetector ()
@@ -85,45 +108,38 @@ def test_analyze_python_positives(self, file_content):
85108 assert 'mock_filename.py' == potential_secret .filename
86109 assert (
87110 potential_secret .secret_hash
88- == PotentialSecret .hash_secret ('hope]nobody [finds>-_$#thisone' )
111+ == PotentialSecret .hash_secret ('h}o)p${e]nob(ody [finds>-_$#thisone' )
89112 )
90113
91114 @pytest .mark .parametrize (
92- 'file_content' ,
93- [
115+ 'negative' ,
116+ STANDARD_NEGATIVES ,
117+ )
118+ def test_analyze_standard_negatives (self , negative ):
119+ logic = KeywordDetector ()
120+
121+ f = mock_file_object (negative )
122+ output = logic .analyze (f , 'mock_filename.foo' )
123+ assert len (output ) == 0
124+
125+ @pytest .mark .parametrize (
126+ 'js_negative' ,
127+ STANDARD_NEGATIVES + [
94128 # FOLLOWED_BY_COLON_RE
95- 'private_key "";' , # Nothing in the quotes
96- 'private_key \' "no spaces\' ;' , # Has whitespace in the secret
97- 'private_key "fake";' , # 'fake' in the secret
98- 'private_key "hopenobodyfindsthisone\' ;' , # Double-quote does not match single-quote
99- 'private_key \' hopenobodyfindsthisone";' , # Single-quote does not match double-quote
100- # FOLLOWED_BY_QUOTES_AND_SEMICOLON_RE
101- 'theapikey: ""' , # Nothing in the quotes
102- 'theapikey: "somefakekey"' , # 'fake' in the secret
103- 'theapikeyforfoo:hopenobodyfindsthisone' , # Characters between apikey and :
104- # FOLLOWED_BY_EQUAL_SIGNS_RE
105- 'some_key = "real_secret"' , # We cannot make 'key' a Keyword, too noisy
106- 'my_password = foo(hey)you' , # Has a ( followed by a )
107- "my_password = request.json_body['hey']" , # Has a [ followed by a ]
108- 'my_password = ""' , # Nothing in the quotes
109- "my_password = ''" , # Nothing in the quotes
110- 'my_password = True' , # 'True' is a known false-positive
111- 'my_password = "fakesecret"' , # 'fake' in the secret
112- 'login(username=username, password=password)' , # secret is password)
113- 'open(self, password = ""):' , # secrets is ""):
114- # '',
129+ 'apiKey: this.apiKey,' ,
130+ "apiKey: fs.readFileSync('foo'," ,
115131 ],
116132 )
117- def test_analyze_negatives (self , file_content ):
133+ def test_analyze_javascript_negatives (self , js_negative ):
118134 logic = KeywordDetector ()
119135
120- f = mock_file_object (file_content )
121- output = logic .analyze (f , 'mock_filename.foo ' )
136+ f = mock_file_object (js_negative )
137+ output = logic .analyze (f , 'mock_filename.js ' )
122138 assert len (output ) == 0
123139
124140 @pytest .mark .parametrize (
125141 'secret_starting_with_dollar_sign' ,
126- [
142+ STANDARD_NEGATIVES + [
127143 # FOLLOWED_BY_EQUAL_SIGNS_RE
128144 '$password = $input;' ,
129145 ],
@@ -137,7 +153,7 @@ def test_analyze_php_negatives(self, secret_starting_with_dollar_sign):
137153
138154 @pytest .mark .parametrize (
139155 'secret_with_no_quote' ,
140- [
156+ STANDARD_NEGATIVES + [
141157 # FOLLOWED_BY_COLON_QUOTES_REQUIRED_RE
142158 'apikey: hope]nobody[finds>-_$#thisone' ,
143159 'apikey:hope]nobody[finds>-_$#thisone' ,
0 commit comments