generate_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package utils
  2. import (
  3. "testing"
  4. )
  5. func TestGenerateSemiGenericRegex(t *testing.T) {
  6. tests := []struct {
  7. name string
  8. identifiers []string
  9. secretRegex string
  10. isCaseInsensitive []bool
  11. validStrings []string
  12. invalidStrings []string
  13. }{
  14. {
  15. name: "secret is case sensitive, if isCaseInsensitive is false",
  16. identifiers: []string{"api_key"},
  17. secretRegex: `[a-z]{3}`,
  18. isCaseInsensitive: []bool{false},
  19. validStrings: []string{"api_key=xxx"},
  20. invalidStrings: []string{"api_key=XXX", "api_key=xXx"},
  21. },
  22. {
  23. name: "secret is case insensitive, if isCaseInsensitive is true",
  24. identifiers: []string{"api_key"},
  25. secretRegex: `[a-z]{3}`,
  26. isCaseInsensitive: []bool{true},
  27. validStrings: []string{"api_key=xxx", "api_key=XXX", "api_key=xXx"},
  28. invalidStrings: []string{"api_key=x!x"},
  29. },
  30. {
  31. name: "identifier is case insensitive, regardless of isCaseInsensitive",
  32. identifiers: []string{"api_key"},
  33. secretRegex: `[a-z]{3}`,
  34. isCaseInsensitive: []bool{true, false},
  35. validStrings: []string{"api_key=xxx", "ApI_KeY=xxx", "aPi_kEy=xxx", "API_KEY=xxx"},
  36. invalidStrings: []string{"api!key=xxx"},
  37. },
  38. {
  39. name: "identifier can be case sensitive",
  40. identifiers: []string{"(?-i:[Aa]pi_?[Kk]ey|API_?KEY)"},
  41. secretRegex: `[a-z]{3}`,
  42. isCaseInsensitive: []bool{true, false},
  43. validStrings: []string{"apikey=xxx", "ApiKey=xxx", "Apikey=xxx", "APIKEY=xxx", "api_key=xxx"},
  44. invalidStrings: []string{"ApIKeY=xxx", "aPikEy=xxx"},
  45. },
  46. {
  47. name: "identifier can be part of a longer word",
  48. identifiers: []string{"key"},
  49. secretRegex: `[a-z]{3}`,
  50. isCaseInsensitive: []bool{true, false},
  51. validStrings: []string{"mykey=xxx", "keys=xxx", "key1=xxx", "keystore=xxx", "monkey=xxx"},
  52. invalidStrings: []string{},
  53. },
  54. {
  55. name: "identifier may be followed by specific characters",
  56. identifiers: []string{"api_key"},
  57. secretRegex: `[a-z]{3}`,
  58. isCaseInsensitive: []bool{true, false},
  59. validStrings: []string{
  60. "api_key-----=xxx",
  61. "api_key.....=xxx",
  62. "api_key_____=xxx",
  63. "'''api_key'''=xxx",
  64. `"""api_key"""=xxx`,
  65. "api_key =xxx",
  66. "api_key\t\t\t\t\t=xxx",
  67. "api_key\n\n\n=xxx", // potentially invalid?,
  68. "api_key\r\n=xxx",
  69. // "api_key|||=xxx",
  70. },
  71. invalidStrings: []string{
  72. "api_key&=xxx",
  73. "$api_key$=xxx",
  74. "%api_key%=xxx",
  75. "api_key[0]=xxx",
  76. "api_key/*REMOVE*/=xxx",
  77. },
  78. },
  79. {
  80. name: "identifier and secret must be separated by specific operators",
  81. identifiers: []string{"api_key"},
  82. secretRegex: `[a-z]{3}`,
  83. isCaseInsensitive: []bool{true, false},
  84. validStrings: []string{
  85. "api_key=xxx",
  86. "api_key: xxx",
  87. "<api_key>xxx",
  88. "api_key:=xxx",
  89. "api_key:::=xxx",
  90. // "api_key||:=xxx", // this isn't anything
  91. // "api_key <= xxx",
  92. "api_key => xxx",
  93. "api_key ?= xxx",
  94. "api_key, xxx",
  95. },
  96. invalidStrings: []string{
  97. "api_keyxxx",
  98. "api_key\txxx", // potentially valid in a tab-separated file
  99. "api_key; xxx",
  100. "api_key<xxx>",
  101. "api_key&xxx",
  102. "api_key = true ? 'xxx' : 'yyy'",
  103. },
  104. },
  105. {
  106. name: "secret is limited by specific boundaries",
  107. identifiers: []string{"api_key"},
  108. secretRegex: `[a-z]{3}`,
  109. isCaseInsensitive: []bool{true, false},
  110. validStrings: []string{
  111. "api_key= xxx ",
  112. "api_key=xxx\n",
  113. "api_key=xxx\r\n",
  114. "api_key=\n\n\n\n\nxxx", // potentially invalid (e.g. .env.example)
  115. "api_key=\r\n\r\nxxx",
  116. "api_key=\t\t\t\txxx\t",
  117. "api_key======xxx;",
  118. "api_key='''xxx'''",
  119. `api_key="""xxx"""`,
  120. "api_key=```xxx```",
  121. `api_key="xxx'`, // could try to match only same opening and closing quotes, might not be worth the complexity
  122. `api_key="don't do it!"`,
  123. `api_key="xxx;notpartofthematch"`,
  124. },
  125. invalidStrings: []string{
  126. "api_key=_xxx",
  127. "api_key=xxx_",
  128. "api_key=$xxx",
  129. "api_key=%xxx%",
  130. "api_key=[xxx]",
  131. "api_key=(xxx)",
  132. "api_key=<xxx>",
  133. "api_key={xxx}",
  134. "<api_key>xxx</api_key>", // potentially valid
  135. "example.com?api_key=xxx&other=yyy", // potentially valid
  136. },
  137. },
  138. // Note: these test cases do not necessarily prescribe the expected behavior of the function,
  139. // but rather document the current behavior, to ensure that future changes are intentional.
  140. }
  141. for _, tt := range tests {
  142. t.Run(tt.name, func(t *testing.T) {
  143. for _, isCaseInsensitive := range tt.isCaseInsensitive {
  144. regex := GenerateSemiGenericRegex(tt.identifiers, tt.secretRegex, isCaseInsensitive)
  145. for _, validString := range tt.validStrings {
  146. if !regex.MatchString(validString) {
  147. t.Errorf("Expected match, but got none, \nfor GenerateSemiGenericRegex(%v, /%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
  148. tt.identifiers, tt.secretRegex, isCaseInsensitive, validString, regex)
  149. }
  150. }
  151. for _, invalidString := range tt.invalidStrings {
  152. if regex.MatchString(invalidString) {
  153. t.Errorf("Expected no match, but got one, \nfor GenerateSemiGenericRegex(%v, /%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
  154. tt.identifiers, tt.secretRegex, isCaseInsensitive, invalidString, regex)
  155. }
  156. }
  157. }
  158. })
  159. }
  160. }
  161. func TestGenerateUniqueTokenRegex(t *testing.T) {
  162. tests := []struct {
  163. name string
  164. secretRegex string
  165. isCaseInsensitive bool
  166. validStrings []string
  167. invalidStrings []string
  168. }{
  169. {
  170. name: "case sensitive secret",
  171. secretRegex: `[a-c]{3}`,
  172. isCaseInsensitive: false,
  173. validStrings: []string{"abc"},
  174. invalidStrings: []string{"ABC", "Abc"},
  175. },
  176. {
  177. name: "case insensitive secret",
  178. secretRegex: `[a-c]{3}`,
  179. isCaseInsensitive: true,
  180. validStrings: []string{"abc", "ABC", "Abc"},
  181. invalidStrings: []string{"123"},
  182. },
  183. {
  184. name: "allowed boundaries",
  185. secretRegex: `[a-c]{3}`,
  186. isCaseInsensitive: false,
  187. validStrings: []string{
  188. "abc",
  189. " abc ",
  190. "\nabc\n",
  191. "\r\nabc\r\n",
  192. "\tabc\t",
  193. "'abc'",
  194. `"abc"`,
  195. "```abc```",
  196. "my abc's",
  197. ".com?abc",
  198. },
  199. invalidStrings: []string{
  200. "abcabc",
  201. "_abc_",
  202. ".com?abc&def", // potentially valid
  203. "/*abc*/",
  204. "<abc>",
  205. "<str>abc</str>", // potentially valid
  206. "{{{abc}}}",
  207. "abc, d",
  208. },
  209. },
  210. // Note: these test cases do not necessarily prescribe the expected behavior of the function,
  211. // but rather document the current behavior, to ensure that future changes are intentional.
  212. }
  213. for _, tt := range tests {
  214. t.Run(tt.name, func(t *testing.T) {
  215. regex := GenerateUniqueTokenRegex(tt.secretRegex, tt.isCaseInsensitive)
  216. for _, validString := range tt.validStrings {
  217. if !regex.MatchString(validString) {
  218. t.Errorf("Expected match, but got none, \nfor GenerateUniqueTokenRegex(/%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
  219. tt.secretRegex, tt.isCaseInsensitive, validString, regex)
  220. }
  221. }
  222. for _, invalidString := range tt.invalidStrings {
  223. if regex.MatchString(invalidString) {
  224. t.Errorf("Expected no match, but got one, \nfor GenerateUniqueTokenRegex(/%v/, caseInsensitive=%v).MatchString(`%v`)\n%v",
  225. tt.secretRegex, tt.isCaseInsensitive, invalidString, regex)
  226. }
  227. }
  228. })
  229. }
  230. }