detect_test.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  1. package detect
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/google/go-cmp/cmp"
  8. "github.com/spf13/viper"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. regexp "github.com/wasilibs/go-re2"
  12. "github.com/zricethezav/gitleaks/v8/config"
  13. "github.com/zricethezav/gitleaks/v8/report"
  14. "github.com/zricethezav/gitleaks/v8/sources"
  15. )
  16. const maxDecodeDepth = 8
  17. const configPath = "../testdata/config/"
  18. const repoBasePath = "../testdata/repos/"
  19. const b64TestValues = `
  20. # Decoded
  21. -----BEGIN PRIVATE KEY-----
  22. 135f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb
  23. u+QDkg0spw==
  24. -----END PRIVATE KEY-----
  25. # Encoded
  26. private_key: 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCjQzNWYvYlJVQkhyYkhxTFkveFMzSTdPdGgrOHJnRyswdEJ3Zk1jYmswNVNneHE2UVV6U1lJUUFvcCtXdnNUd2syc1IrQzM4ZzBNbmIKdStRRGtnMHNwdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K'
  27. # Double Encoded: b64 encoded aws config inside a jwt
  28. eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29uZmlnIjoiVzJSbFptRjFiSFJkQ25KbFoybHZiaUE5SUhWekxXVmhjM1F0TWdwaGQzTmZZV05qWlhOelgydGxlVjlwWkNBOUlFRlRTVUZKVDFOR1QwUk9UamRNV0UweE1FcEpDbUYzYzE5elpXTnlaWFJmWVdOalpYTnpYMnRsZVNBOUlIZEtZV3h5V0ZWMGJrWkZUVWt2U3pkTlJFVk9SeTlpVUhoU1ptbERXVVZHVlVORWJFVllNVUVLIiwiaWF0IjoxNTE2MjM5MDIyfQ.8gxviXEOuIBQk2LvTYHSf-wXVhnEKC3h4yM5nlOF4zA
  29. # A small secret at the end to make sure that as the other ones above shrink
  30. # when decoded, the positions are taken into consideratoin for overlaps
  31. c21hbGwtc2VjcmV0
  32. # This tests how it handles when the match bounds go outside the decoded value
  33. secret=ZGVjb2RlZC1zZWNyZXQtdmFsdWU=
  34. # The above encoded again
  35. c2VjcmV0PVpHVmpiMlJsWkMxelpXTnlaWFF0ZG1Gc2RXVT0=
  36. `
  37. func TestDetect(t *testing.T) {
  38. tests := []struct {
  39. cfgName string
  40. baselinePath string
  41. fragment Fragment
  42. // NOTE: for expected findings, all line numbers will be 0
  43. // because line deltas are added _after_ the finding is created.
  44. // I.e., if the finding is from a --no-git file, the line number will be
  45. // increase by 1 in DetectFromFiles(). If the finding is from git,
  46. // the line number will be increased by the patch delta.
  47. expectedFindings []report.Finding
  48. wantError error
  49. }{
  50. {
  51. cfgName: "simple",
  52. fragment: Fragment{
  53. Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // gitleaks:allow"`,
  54. FilePath: "tmp.go",
  55. },
  56. },
  57. {
  58. cfgName: "simple",
  59. fragment: Fragment{
  60. Raw: `awsToken := \
  61. \"AKIALALEMEL33243OKIA\ // gitleaks:allow"
  62. `,
  63. FilePath: "tmp.go",
  64. },
  65. },
  66. {
  67. cfgName: "simple",
  68. fragment: Fragment{
  69. Raw: `awsToken := \"AKIALALEMEL33243OKIA\"
  70. // gitleaks:allow"
  71. `,
  72. FilePath: "tmp.go",
  73. },
  74. expectedFindings: []report.Finding{
  75. {
  76. Description: "AWS Access Key",
  77. Secret: "AKIALALEMEL33243OKIA",
  78. Match: "AKIALALEMEL33243OKIA",
  79. File: "tmp.go",
  80. Line: `awsToken := \"AKIALALEMEL33243OKIA\"`,
  81. RuleID: "aws-access-key",
  82. Tags: []string{"key", "AWS"},
  83. StartLine: 0,
  84. EndLine: 0,
  85. StartColumn: 15,
  86. EndColumn: 34,
  87. Entropy: 3.1464393,
  88. },
  89. },
  90. },
  91. {
  92. cfgName: "escaped_character_group",
  93. fragment: Fragment{
  94. Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  95. FilePath: "tmp.go",
  96. },
  97. expectedFindings: []report.Finding{
  98. {
  99. Description: "PyPI upload token",
  100. Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  101. Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  102. Line: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  103. File: "tmp.go",
  104. RuleID: "pypi-upload-token",
  105. Tags: []string{"key", "pypi"},
  106. StartLine: 0,
  107. EndLine: 0,
  108. StartColumn: 1,
  109. EndColumn: 86,
  110. Entropy: 1.9606875,
  111. },
  112. },
  113. },
  114. {
  115. cfgName: "simple",
  116. fragment: Fragment{
  117. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  118. FilePath: "tmp.go",
  119. },
  120. expectedFindings: []report.Finding{
  121. {
  122. Description: "AWS Access Key",
  123. Secret: "AKIALALEMEL33243OLIA",
  124. Match: "AKIALALEMEL33243OLIA",
  125. Line: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  126. File: "tmp.go",
  127. RuleID: "aws-access-key",
  128. Tags: []string{"key", "AWS"},
  129. StartLine: 0,
  130. EndLine: 0,
  131. StartColumn: 15,
  132. EndColumn: 34,
  133. Entropy: 3.0841837,
  134. },
  135. },
  136. },
  137. {
  138. cfgName: "simple",
  139. fragment: Fragment{
  140. Raw: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  141. FilePath: "tmp.sh",
  142. },
  143. expectedFindings: []report.Finding{
  144. {
  145. Description: "Sidekiq Secret",
  146. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;",
  147. Secret: "cafebabe:deadbeef",
  148. Line: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  149. File: "tmp.sh",
  150. RuleID: "sidekiq-secret",
  151. Tags: []string{},
  152. Entropy: 2.6098502,
  153. StartLine: 0,
  154. EndLine: 0,
  155. StartColumn: 8,
  156. EndColumn: 60,
  157. },
  158. },
  159. },
  160. {
  161. cfgName: "simple",
  162. fragment: Fragment{
  163. Raw: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  164. FilePath: "tmp.sh",
  165. },
  166. expectedFindings: []report.Finding{
  167. {
  168. Description: "Sidekiq Secret",
  169. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=\"cafebabe:deadbeef\"",
  170. Secret: "cafebabe:deadbeef",
  171. File: "tmp.sh",
  172. Line: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  173. RuleID: "sidekiq-secret",
  174. Tags: []string{},
  175. Entropy: 2.6098502,
  176. StartLine: 0,
  177. EndLine: 0,
  178. StartColumn: 21,
  179. EndColumn: 74,
  180. },
  181. },
  182. },
  183. {
  184. cfgName: "simple",
  185. fragment: Fragment{
  186. Raw: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  187. FilePath: "tmp.sh",
  188. },
  189. expectedFindings: []report.Finding{
  190. {
  191. Description: "Sidekiq Sensitive URL",
  192. Match: "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:",
  193. Secret: "cafeb4b3:d3adb33f",
  194. File: "tmp.sh",
  195. Line: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  196. RuleID: "sidekiq-sensitive-url",
  197. Tags: []string{},
  198. Entropy: 2.984234,
  199. StartLine: 0,
  200. EndLine: 0,
  201. StartColumn: 8,
  202. EndColumn: 58,
  203. },
  204. },
  205. },
  206. {
  207. cfgName: "allow_aws_re",
  208. fragment: Fragment{
  209. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  210. FilePath: "tmp.go",
  211. },
  212. },
  213. {
  214. cfgName: "allow_path",
  215. fragment: Fragment{
  216. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  217. FilePath: "tmp.go",
  218. },
  219. },
  220. {
  221. cfgName: "allow_commit",
  222. fragment: Fragment{
  223. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  224. FilePath: "tmp.go",
  225. CommitSHA: "allowthiscommit",
  226. },
  227. },
  228. {
  229. cfgName: "entropy_group",
  230. fragment: Fragment{
  231. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  232. FilePath: "tmp.go",
  233. },
  234. expectedFindings: []report.Finding{
  235. {
  236. Description: "Discord API key",
  237. Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  238. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  239. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  240. File: "tmp.go",
  241. RuleID: "discord-api-key",
  242. Tags: []string{},
  243. Entropy: 3.7906237,
  244. StartLine: 0,
  245. EndLine: 0,
  246. StartColumn: 7,
  247. EndColumn: 93,
  248. },
  249. },
  250. },
  251. {
  252. cfgName: "generic_with_py_path",
  253. fragment: Fragment{
  254. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  255. FilePath: "tmp.go",
  256. },
  257. },
  258. {
  259. cfgName: "generic_with_py_path",
  260. fragment: Fragment{
  261. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  262. FilePath: "tmp.py",
  263. },
  264. expectedFindings: []report.Finding{
  265. {
  266. Description: "Generic API Key",
  267. Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  268. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  269. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  270. File: "tmp.py",
  271. RuleID: "generic-api-key",
  272. Tags: []string{},
  273. Entropy: 3.7906237,
  274. StartLine: 0,
  275. EndLine: 0,
  276. StartColumn: 22,
  277. EndColumn: 93,
  278. },
  279. },
  280. },
  281. {
  282. cfgName: "path_only",
  283. fragment: Fragment{
  284. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  285. FilePath: "tmp.py",
  286. },
  287. expectedFindings: []report.Finding{
  288. {
  289. Description: "Python Files",
  290. Match: "file detected: tmp.py",
  291. File: "tmp.py",
  292. RuleID: "python-files-only",
  293. Tags: []string{},
  294. },
  295. },
  296. },
  297. {
  298. cfgName: "bad_entropy_group",
  299. fragment: Fragment{
  300. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  301. FilePath: "tmp.go",
  302. },
  303. wantError: fmt.Errorf("discord-api-key: invalid regex secret group 5, max regex secret group 3"),
  304. },
  305. {
  306. cfgName: "simple",
  307. fragment: Fragment{
  308. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  309. FilePath: filepath.Join(configPath, "simple.toml"),
  310. },
  311. },
  312. {
  313. cfgName: "allow_global_aws_re",
  314. fragment: Fragment{
  315. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  316. FilePath: "tmp.go",
  317. },
  318. },
  319. {
  320. cfgName: "generic_with_py_path",
  321. fragment: Fragment{
  322. Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  323. FilePath: "tmp.py",
  324. },
  325. },
  326. {
  327. cfgName: "path_only",
  328. baselinePath: ".baseline.json",
  329. fragment: Fragment{
  330. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  331. FilePath: ".baseline.json",
  332. },
  333. },
  334. {
  335. cfgName: "base64_encoded",
  336. fragment: Fragment{
  337. Raw: b64TestValues,
  338. FilePath: "tmp.go",
  339. },
  340. expectedFindings: []report.Finding{
  341. { // Plain text key captured by normal rule
  342. Description: "Private Key",
  343. Secret: "-----BEGIN PRIVATE KEY-----\n135f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb\nu+QDkg0spw==\n-----END PRIVATE KEY-----",
  344. Match: "-----BEGIN PRIVATE KEY-----\n135f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb\nu+QDkg0spw==\n-----END PRIVATE KEY-----",
  345. File: "tmp.go",
  346. Line: "\n-----BEGIN PRIVATE KEY-----\n135f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb\nu+QDkg0spw==\n-----END PRIVATE KEY-----",
  347. RuleID: "private-key",
  348. Tags: []string{"key", "private"},
  349. StartLine: 2,
  350. EndLine: 5,
  351. StartColumn: 2,
  352. EndColumn: 26,
  353. Entropy: 5.350665,
  354. },
  355. { // Encoded key captured by custom b64 regex rule
  356. Description: "Private Key",
  357. Secret: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCjQzNWYvYlJVQkhyYkhxTFkveFMzSTdPdGgrOHJnRyswdEJ3Zk1jYmswNVNneHE2UVV6U1lJUUFvcCtXdnNUd2syc1IrQzM4ZzBNbmIKdStRRGtnMHNwdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K",
  358. Match: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCjQzNWYvYlJVQkhyYkhxTFkveFMzSTdPdGgrOHJnRyswdEJ3Zk1jYmswNVNneHE2UVV6U1lJUUFvcCtXdnNUd2syc1IrQzM4ZzBNbmIKdStRRGtnMHNwdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K",
  359. File: "tmp.go",
  360. Line: "\nprivate_key: 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCjQzNWYvYlJVQkhyYkhxTFkveFMzSTdPdGgrOHJnRyswdEJ3Zk1jYmswNVNneHE2UVV6U1lJUUFvcCtXdnNUd2syc1IrQzM4ZzBNbmIKdStRRGtnMHNwdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K'",
  361. RuleID: "b64-encoded-private-key",
  362. Tags: []string{"key", "private"},
  363. StartLine: 8,
  364. EndLine: 8,
  365. StartColumn: 16,
  366. EndColumn: 207,
  367. Entropy: 5.3861146,
  368. },
  369. { // Encoded key captured by plain text rule using the decoder
  370. Description: "Private Key",
  371. Secret: "-----BEGIN PRIVATE KEY-----\n435f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb\nu+QDkg0spw==\n-----END PRIVATE KEY-----",
  372. Match: "-----BEGIN PRIVATE KEY-----\n435f/bRUBHrbHqLY/xS3I7Oth+8rgG+0tBwfMcbk05Sgxq6QUzSYIQAop+WvsTwk2sR+C38g0Mnb\nu+QDkg0spw==\n-----END PRIVATE KEY-----",
  373. File: "tmp.go",
  374. Line: "\nprivate_key: 'LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCjQzNWYvYlJVQkhyYkhxTFkveFMzSTdPdGgrOHJnRyswdEJ3Zk1jYmswNVNneHE2UVV6U1lJUUFvcCtXdnNUd2syc1IrQzM4ZzBNbmIKdStRRGtnMHNwdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K'",
  375. RuleID: "private-key",
  376. Tags: []string{"key", "private", "decoded:base64", "decode-depth:1"},
  377. StartLine: 8,
  378. EndLine: 8,
  379. StartColumn: 16,
  380. EndColumn: 207,
  381. Entropy: 5.350665,
  382. },
  383. { // Encoded AWS config with a access key id inside a JWT
  384. Description: "AWS IAM Unique Identifier",
  385. Secret: "ASIAIOSFODNN7LXM10JI",
  386. Match: " ASIAIOSFODNN7LXM10JI",
  387. File: "tmp.go",
  388. Line: "\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29uZmlnIjoiVzJSbFptRjFiSFJkQ25KbFoybHZiaUE5SUhWekxXVmhjM1F0TWdwaGQzTmZZV05qWlhOelgydGxlVjlwWkNBOUlFRlRTVUZKVDFOR1QwUk9UamRNV0UweE1FcEpDbUYzYzE5elpXTnlaWFJmWVdOalpYTnpYMnRsZVNBOUlIZEtZV3h5V0ZWMGJrWkZUVWt2U3pkTlJFVk9SeTlpVUhoU1ptbERXVVZHVlVORWJFVllNVUVLIiwiaWF0IjoxNTE2MjM5MDIyfQ.8gxviXEOuIBQk2LvTYHSf-wXVhnEKC3h4yM5nlOF4zA",
  389. RuleID: "aws-iam-unique-identifier",
  390. Tags: []string{"aws", "identifier", "decoded:base64", "decode-depth:2"},
  391. StartLine: 11,
  392. EndLine: 11,
  393. StartColumn: 39,
  394. EndColumn: 344,
  395. Entropy: 3.6841838,
  396. },
  397. { // Encoded AWS config with a secret access key inside a JWT
  398. Description: "AWS Secret Access Key",
  399. Secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEFUCDlEX1A",
  400. Match: "aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEFUCDlEX1A",
  401. File: "tmp.go",
  402. Line: "\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29uZmlnIjoiVzJSbFptRjFiSFJkQ25KbFoybHZiaUE5SUhWekxXVmhjM1F0TWdwaGQzTmZZV05qWlhOelgydGxlVjlwWkNBOUlFRlRTVUZKVDFOR1QwUk9UamRNV0UweE1FcEpDbUYzYzE5elpXTnlaWFJmWVdOalpYTnpYMnRsZVNBOUlIZEtZV3h5V0ZWMGJrWkZUVWt2U3pkTlJFVk9SeTlpVUhoU1ptbERXVVZHVlVORWJFVllNVUVLIiwiaWF0IjoxNTE2MjM5MDIyfQ.8gxviXEOuIBQk2LvTYHSf-wXVhnEKC3h4yM5nlOF4zA",
  403. RuleID: "aws-secret-access-key",
  404. Tags: []string{"aws", "secret", "decoded:base64", "decode-depth:2"},
  405. StartLine: 11,
  406. EndLine: 11,
  407. StartColumn: 39,
  408. EndColumn: 344,
  409. Entropy: 4.721928,
  410. },
  411. { // Encoded Small secret at the end to make sure it's picked up by the decoding
  412. Description: "Small Secret",
  413. Secret: "small-secret",
  414. Match: "small-secret",
  415. File: "tmp.go",
  416. Line: "\nc21hbGwtc2VjcmV0",
  417. RuleID: "small-secret",
  418. Tags: []string{"small", "secret", "decoded:base64", "decode-depth:1"},
  419. StartLine: 15,
  420. EndLine: 15,
  421. StartColumn: 2,
  422. EndColumn: 17,
  423. Entropy: 3.0849626,
  424. },
  425. { // Secret where the decoded match goes outside the encoded value
  426. Description: "Overlapping",
  427. Secret: "decoded-secret-value",
  428. Match: "secret=decoded-secret-value",
  429. File: "tmp.go",
  430. Line: "\nsecret=ZGVjb2RlZC1zZWNyZXQtdmFsdWU=",
  431. RuleID: "overlapping",
  432. Tags: []string{"overlapping", "decoded:base64", "decode-depth:1"},
  433. StartLine: 18,
  434. EndLine: 18,
  435. StartColumn: 2,
  436. EndColumn: 36,
  437. Entropy: 3.3037016,
  438. },
  439. { // Secret where the decoded match goes outside the encoded value and then encoded again
  440. Description: "Overlapping",
  441. Secret: "decoded-secret-value",
  442. Match: "secret=decoded-secret-value",
  443. File: "tmp.go",
  444. Line: "\nc2VjcmV0PVpHVmpiMlJsWkMxelpXTnlaWFF0ZG1Gc2RXVT0=",
  445. RuleID: "overlapping",
  446. Tags: []string{"overlapping", "decoded:base64", "decode-depth:2"},
  447. StartLine: 20,
  448. EndLine: 20,
  449. StartColumn: 2,
  450. EndColumn: 49,
  451. Entropy: 3.3037016,
  452. },
  453. },
  454. },
  455. }
  456. for _, tt := range tests {
  457. t.Run(fmt.Sprintf("%s - %s", tt.cfgName, tt.fragment.FilePath), func(t *testing.T) {
  458. viper.Reset()
  459. viper.AddConfigPath(configPath)
  460. viper.SetConfigName(tt.cfgName)
  461. viper.SetConfigType("toml")
  462. err := viper.ReadInConfig()
  463. require.NoError(t, err)
  464. var vc config.ViperConfig
  465. err = viper.Unmarshal(&vc)
  466. require.NoError(t, err)
  467. cfg, err := vc.Translate()
  468. cfg.Path = filepath.Join(configPath, tt.cfgName+".toml")
  469. assert.Equal(t, tt.wantError, err)
  470. d := NewDetector(cfg)
  471. d.MaxDecodeDepth = maxDecodeDepth
  472. d.baselinePath = tt.baselinePath
  473. findings := d.Detect(tt.fragment)
  474. assert.ElementsMatch(t, tt.expectedFindings, findings)
  475. })
  476. }
  477. }
  478. // TestFromGit tests the FromGit function
  479. func TestFromGit(t *testing.T) {
  480. tests := []struct {
  481. cfgName string
  482. source string
  483. logOpts string
  484. expectedFindings []report.Finding
  485. }{
  486. {
  487. source: filepath.Join(repoBasePath, "small"),
  488. cfgName: "simple",
  489. expectedFindings: []report.Finding{
  490. {
  491. Description: "AWS Access Key",
  492. StartLine: 20,
  493. EndLine: 20,
  494. StartColumn: 19,
  495. EndColumn: 38,
  496. Line: "\n awsToken := \"AKIALALEMEL33243OLIA\"",
  497. Secret: "AKIALALEMEL33243OLIA",
  498. Match: "AKIALALEMEL33243OLIA",
  499. File: "main.go",
  500. Date: "2021-11-02T23:37:53Z",
  501. Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587",
  502. Author: "Zachary Rice",
  503. Email: "zricer@protonmail.com",
  504. Message: "Accidentally add a secret",
  505. RuleID: "aws-access-key",
  506. Tags: []string{"key", "AWS"},
  507. Entropy: 3.0841837,
  508. Fingerprint: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587:main.go:aws-access-key:20",
  509. },
  510. {
  511. Description: "AWS Access Key",
  512. StartLine: 9,
  513. EndLine: 9,
  514. StartColumn: 17,
  515. EndColumn: 36,
  516. Secret: "AKIALALEMEL33243OLIA",
  517. Match: "AKIALALEMEL33243OLIA",
  518. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  519. File: "foo/foo.go",
  520. Date: "2021-11-02T23:48:06Z",
  521. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  522. Author: "Zach Rice",
  523. Email: "zricer@protonmail.com",
  524. Message: "adding foo package with secret",
  525. RuleID: "aws-access-key",
  526. Tags: []string{"key", "AWS"},
  527. Entropy: 3.0841837,
  528. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  529. },
  530. },
  531. },
  532. {
  533. source: filepath.Join(repoBasePath, "small"),
  534. logOpts: "--all foo...",
  535. cfgName: "simple",
  536. expectedFindings: []report.Finding{
  537. {
  538. Description: "AWS Access Key",
  539. StartLine: 9,
  540. EndLine: 9,
  541. StartColumn: 17,
  542. EndColumn: 36,
  543. Secret: "AKIALALEMEL33243OLIA",
  544. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  545. Match: "AKIALALEMEL33243OLIA",
  546. Date: "2021-11-02T23:48:06Z",
  547. File: "foo/foo.go",
  548. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  549. Author: "Zach Rice",
  550. Email: "zricer@protonmail.com",
  551. Message: "adding foo package with secret",
  552. RuleID: "aws-access-key",
  553. Tags: []string{"key", "AWS"},
  554. Entropy: 3.0841837,
  555. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  556. },
  557. },
  558. },
  559. }
  560. moveDotGit(t, "dotGit", ".git")
  561. defer moveDotGit(t, ".git", "dotGit")
  562. for _, tt := range tests {
  563. viper.AddConfigPath(configPath)
  564. viper.SetConfigName("simple")
  565. viper.SetConfigType("toml")
  566. err := viper.ReadInConfig()
  567. require.NoError(t, err)
  568. var vc config.ViperConfig
  569. err = viper.Unmarshal(&vc)
  570. require.NoError(t, err)
  571. cfg, err := vc.Translate()
  572. require.NoError(t, err)
  573. detector := NewDetector(cfg)
  574. var ignorePath string
  575. info, err := os.Stat(tt.source)
  576. require.NoError(t, err)
  577. if info.IsDir() {
  578. ignorePath = filepath.Join(tt.source, ".gitleaksignore")
  579. } else {
  580. ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
  581. }
  582. err = detector.AddGitleaksIgnore(ignorePath)
  583. require.NoError(t, err)
  584. gitCmd, err := sources.NewGitLogCmd(tt.source, tt.logOpts)
  585. require.NoError(t, err)
  586. findings, err := detector.DetectGit(gitCmd)
  587. require.NoError(t, err)
  588. for _, f := range findings {
  589. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  590. }
  591. assert.ElementsMatch(t, tt.expectedFindings, findings)
  592. }
  593. }
  594. func TestFromGitStaged(t *testing.T) {
  595. tests := []struct {
  596. cfgName string
  597. source string
  598. logOpts string
  599. expectedFindings []report.Finding
  600. }{
  601. {
  602. source: filepath.Join(repoBasePath, "staged"),
  603. cfgName: "simple",
  604. expectedFindings: []report.Finding{
  605. {
  606. Description: "AWS Access Key",
  607. StartLine: 7,
  608. EndLine: 7,
  609. StartColumn: 18,
  610. EndColumn: 37,
  611. Line: "\n\taws_token2 := \"AKIALALEMEL33243OLIA\" // this one is not",
  612. Match: "AKIALALEMEL33243OLIA",
  613. Secret: "AKIALALEMEL33243OLIA",
  614. File: "api/api.go",
  615. SymlinkFile: "",
  616. Commit: "",
  617. Entropy: 3.0841837,
  618. Author: "",
  619. Email: "",
  620. Date: "0001-01-01T00:00:00Z",
  621. Message: "",
  622. Tags: []string{
  623. "key",
  624. "AWS",
  625. },
  626. RuleID: "aws-access-key",
  627. Fingerprint: "api/api.go:aws-access-key:7",
  628. },
  629. },
  630. },
  631. }
  632. moveDotGit(t, "dotGit", ".git")
  633. defer moveDotGit(t, ".git", "dotGit")
  634. for _, tt := range tests {
  635. viper.AddConfigPath(configPath)
  636. viper.SetConfigName("simple")
  637. viper.SetConfigType("toml")
  638. err := viper.ReadInConfig()
  639. require.NoError(t, err)
  640. var vc config.ViperConfig
  641. err = viper.Unmarshal(&vc)
  642. require.NoError(t, err)
  643. cfg, err := vc.Translate()
  644. require.NoError(t, err)
  645. detector := NewDetector(cfg)
  646. err = detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore"))
  647. require.NoError(t, err)
  648. gitCmd, err := sources.NewGitDiffCmd(tt.source, true)
  649. require.NoError(t, err)
  650. findings, err := detector.DetectGit(gitCmd)
  651. require.NoError(t, err)
  652. for _, f := range findings {
  653. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  654. }
  655. assert.ElementsMatch(t, tt.expectedFindings, findings)
  656. }
  657. }
  658. // TestFromFiles tests the FromFiles function
  659. func TestFromFiles(t *testing.T) {
  660. tests := []struct {
  661. cfgName string
  662. source string
  663. expectedFindings []report.Finding
  664. }{
  665. {
  666. source: filepath.Join(repoBasePath, "nogit"),
  667. cfgName: "simple",
  668. expectedFindings: []report.Finding{
  669. {
  670. Description: "AWS Access Key",
  671. StartLine: 20,
  672. EndLine: 20,
  673. StartColumn: 16,
  674. EndColumn: 35,
  675. Match: "AKIALALEMEL33243OLIA",
  676. Secret: "AKIALALEMEL33243OLIA",
  677. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  678. File: "../testdata/repos/nogit/main.go",
  679. SymlinkFile: "",
  680. RuleID: "aws-access-key",
  681. Tags: []string{"key", "AWS"},
  682. Entropy: 3.0841837,
  683. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  684. },
  685. },
  686. },
  687. {
  688. source: filepath.Join(repoBasePath, "nogit", "main.go"),
  689. cfgName: "simple",
  690. expectedFindings: []report.Finding{
  691. {
  692. Description: "AWS Access Key",
  693. StartLine: 20,
  694. EndLine: 20,
  695. StartColumn: 16,
  696. EndColumn: 35,
  697. Match: "AKIALALEMEL33243OLIA",
  698. Secret: "AKIALALEMEL33243OLIA",
  699. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  700. File: "../testdata/repos/nogit/main.go",
  701. RuleID: "aws-access-key",
  702. Tags: []string{"key", "AWS"},
  703. Entropy: 3.0841837,
  704. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  705. },
  706. },
  707. },
  708. {
  709. source: filepath.Join(repoBasePath, "nogit", "api.go"),
  710. cfgName: "simple",
  711. expectedFindings: []report.Finding{},
  712. },
  713. {
  714. source: filepath.Join(repoBasePath, "nogit", ".env.prod"),
  715. cfgName: "generic",
  716. expectedFindings: []report.Finding{
  717. {
  718. Description: "Generic API Key",
  719. StartLine: 4,
  720. EndLine: 4,
  721. StartColumn: 5,
  722. EndColumn: 35,
  723. Match: "PASSWORD=8ae31cacf141669ddfb5da",
  724. Secret: "8ae31cacf141669ddfb5da",
  725. Line: "\nDB_PASSWORD=8ae31cacf141669ddfb5da",
  726. File: "../testdata/repos/nogit/.env.prod",
  727. RuleID: "generic-api-key",
  728. Tags: []string{},
  729. Entropy: 3.5383105,
  730. Fingerprint: "../testdata/repos/nogit/.env.prod:generic-api-key:4",
  731. },
  732. },
  733. },
  734. }
  735. for _, tt := range tests {
  736. viper.AddConfigPath(configPath)
  737. viper.SetConfigName(tt.cfgName)
  738. viper.SetConfigType("toml")
  739. err := viper.ReadInConfig()
  740. require.NoError(t, err)
  741. var vc config.ViperConfig
  742. err = viper.Unmarshal(&vc)
  743. require.NoError(t, err)
  744. cfg, _ := vc.Translate()
  745. detector := NewDetector(cfg)
  746. var ignorePath string
  747. info, err := os.Stat(tt.source)
  748. require.NoError(t, err)
  749. if info.IsDir() {
  750. ignorePath = filepath.Join(tt.source, ".gitleaksignore")
  751. } else {
  752. ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
  753. }
  754. err = detector.AddGitleaksIgnore(ignorePath)
  755. require.NoError(t, err)
  756. detector.FollowSymlinks = true
  757. paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true, cfg.Allowlist.PathAllowed)
  758. require.NoError(t, err)
  759. findings, err := detector.DetectFiles(paths)
  760. require.NoError(t, err)
  761. assert.ElementsMatch(t, tt.expectedFindings, findings)
  762. }
  763. }
  764. func TestDetectWithSymlinks(t *testing.T) {
  765. tests := []struct {
  766. cfgName string
  767. source string
  768. expectedFindings []report.Finding
  769. }{
  770. {
  771. source: filepath.Join(repoBasePath, "symlinks/file_symlink"),
  772. cfgName: "simple",
  773. expectedFindings: []report.Finding{
  774. {
  775. Description: "Asymmetric Private Key",
  776. StartLine: 1,
  777. EndLine: 1,
  778. StartColumn: 1,
  779. EndColumn: 35,
  780. Match: "-----BEGIN OPENSSH PRIVATE KEY-----",
  781. Secret: "-----BEGIN OPENSSH PRIVATE KEY-----",
  782. Line: "-----BEGIN OPENSSH PRIVATE KEY-----",
  783. File: "../testdata/repos/symlinks/source_file/id_ed25519",
  784. SymlinkFile: "../testdata/repos/symlinks/file_symlink/symlinked_id_ed25519",
  785. RuleID: "apkey",
  786. Tags: []string{"key", "AsymmetricPrivateKey"},
  787. Entropy: 3.587164,
  788. Fingerprint: "../testdata/repos/symlinks/source_file/id_ed25519:apkey:1",
  789. },
  790. },
  791. },
  792. }
  793. for _, tt := range tests {
  794. viper.AddConfigPath(configPath)
  795. viper.SetConfigName("simple")
  796. viper.SetConfigType("toml")
  797. err := viper.ReadInConfig()
  798. require.NoError(t, err)
  799. var vc config.ViperConfig
  800. err = viper.Unmarshal(&vc)
  801. require.NoError(t, err)
  802. cfg, _ := vc.Translate()
  803. detector := NewDetector(cfg)
  804. detector.FollowSymlinks = true
  805. paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true, cfg.Allowlist.PathAllowed)
  806. require.NoError(t, err)
  807. findings, err := detector.DetectFiles(paths)
  808. require.NoError(t, err)
  809. assert.ElementsMatch(t, tt.expectedFindings, findings)
  810. }
  811. }
  812. func TestDetectRuleAllowlist(t *testing.T) {
  813. cases := map[string]struct {
  814. fragment Fragment
  815. allowlist config.Allowlist
  816. expected []report.Finding
  817. }{
  818. // Commit / path
  819. "commit allowed": {
  820. fragment: Fragment{
  821. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  822. },
  823. allowlist: config.Allowlist{
  824. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  825. },
  826. },
  827. "path allowed": {
  828. fragment: Fragment{
  829. FilePath: "package-lock.json",
  830. },
  831. allowlist: config.Allowlist{
  832. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  833. },
  834. },
  835. "commit AND path allowed": {
  836. fragment: Fragment{
  837. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  838. FilePath: "package-lock.json",
  839. },
  840. allowlist: config.Allowlist{
  841. MatchCondition: config.AllowlistMatchAnd,
  842. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  843. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  844. },
  845. },
  846. "commit AND path NOT allowed": {
  847. fragment: Fragment{
  848. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  849. FilePath: "package.json",
  850. },
  851. allowlist: config.Allowlist{
  852. MatchCondition: config.AllowlistMatchAnd,
  853. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  854. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  855. },
  856. expected: []report.Finding{
  857. {
  858. StartColumn: 50,
  859. EndColumn: 60,
  860. Line: "let username = 'james@mail.com';\nlet password = 'Summer2024!';",
  861. Match: "Summer2024!",
  862. Secret: "Summer2024!",
  863. File: "package.json",
  864. Entropy: 3.095795154571533,
  865. RuleID: "test-rule",
  866. },
  867. },
  868. },
  869. "commit AND path NOT allowed - other conditions": {
  870. fragment: Fragment{
  871. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  872. FilePath: "package-lock.json",
  873. },
  874. allowlist: config.Allowlist{
  875. MatchCondition: config.AllowlistMatchAnd,
  876. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  877. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  878. Regexes: []*regexp.Regexp{regexp.MustCompile("password")},
  879. },
  880. expected: []report.Finding{
  881. {
  882. StartColumn: 50,
  883. EndColumn: 60,
  884. Line: "let username = 'james@mail.com';\nlet password = 'Summer2024!';",
  885. Match: "Summer2024!",
  886. Secret: "Summer2024!",
  887. File: "package-lock.json",
  888. Entropy: 3.095795154571533,
  889. RuleID: "test-rule",
  890. },
  891. },
  892. },
  893. "commit OR path allowed": {
  894. fragment: Fragment{
  895. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  896. FilePath: "package-lock.json",
  897. },
  898. allowlist: config.Allowlist{
  899. MatchCondition: config.AllowlistMatchOr,
  900. Commits: []string{"704178e7dca77ff143778a31cff0fc192d59b030"},
  901. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  902. },
  903. },
  904. // Regex / stopwords
  905. "regex allowed": {
  906. fragment: Fragment{},
  907. allowlist: config.Allowlist{
  908. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)summer.+`)},
  909. },
  910. },
  911. "stopwords allowed": {
  912. fragment: Fragment{},
  913. allowlist: config.Allowlist{
  914. StopWords: []string{"summer"},
  915. },
  916. },
  917. "regex AND stopword allowed": {
  918. fragment: Fragment{},
  919. allowlist: config.Allowlist{
  920. MatchCondition: config.AllowlistMatchAnd,
  921. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)summer.+`)},
  922. StopWords: []string{"2024"},
  923. },
  924. },
  925. "regex AND stopword allowed - other conditions": {
  926. fragment: Fragment{
  927. CommitSHA: "41edf1f7f612199f401ccfc3144c2ebd0d7aeb48",
  928. FilePath: "config.js",
  929. },
  930. allowlist: config.Allowlist{
  931. MatchCondition: config.AllowlistMatchAnd,
  932. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  933. Paths: []*regexp.Regexp{regexp.MustCompile(`config.js`)},
  934. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)summer.+`)},
  935. StopWords: []string{"2024"},
  936. },
  937. },
  938. "regex AND stopword NOT allowed - non-git, other conditions": {
  939. fragment: Fragment{
  940. FilePath: "config.js",
  941. },
  942. allowlist: config.Allowlist{
  943. MatchCondition: config.AllowlistMatchAnd,
  944. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  945. Paths: []*regexp.Regexp{regexp.MustCompile(`config.js`)},
  946. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)summer.+`)},
  947. StopWords: []string{"2024"},
  948. },
  949. expected: []report.Finding{
  950. {
  951. StartColumn: 50,
  952. EndColumn: 60,
  953. Line: "let username = 'james@mail.com';\nlet password = 'Summer2024!';",
  954. Match: "Summer2024!",
  955. Secret: "Summer2024!",
  956. File: "config.js",
  957. Entropy: 3.095795154571533,
  958. RuleID: "test-rule",
  959. },
  960. },
  961. },
  962. "regex AND stopword NOT allowed": {
  963. fragment: Fragment{},
  964. allowlist: config.Allowlist{
  965. MatchCondition: config.AllowlistMatchAnd,
  966. Regexes: []*regexp.Regexp{
  967. regexp.MustCompile(`(?i)winter.+`),
  968. },
  969. StopWords: []string{"2024"},
  970. },
  971. expected: []report.Finding{
  972. {
  973. StartColumn: 50,
  974. EndColumn: 60,
  975. Line: "let username = 'james@mail.com';\nlet password = 'Summer2024!';",
  976. Match: "Summer2024!",
  977. Secret: "Summer2024!",
  978. Entropy: 3.095795154571533,
  979. RuleID: "test-rule",
  980. },
  981. },
  982. },
  983. "regex AND stopword NOT allowed - other conditions": {
  984. fragment: Fragment{
  985. CommitSHA: "a060c9d2d5e90c992763f1bd4c3cd2a6f121241b",
  986. FilePath: "config.js",
  987. },
  988. allowlist: config.Allowlist{
  989. MatchCondition: config.AllowlistMatchAnd,
  990. Commits: []string{"41edf1f7f612199f401ccfc3144c2ebd0d7aeb48"},
  991. Paths: []*regexp.Regexp{regexp.MustCompile(`package-lock.json`)},
  992. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)winter.+`)},
  993. StopWords: []string{"2024"},
  994. },
  995. expected: []report.Finding{
  996. {
  997. StartColumn: 50,
  998. EndColumn: 60,
  999. Line: "let username = 'james@mail.com';\nlet password = 'Summer2024!';",
  1000. Match: "Summer2024!",
  1001. Secret: "Summer2024!",
  1002. File: "config.js",
  1003. Entropy: 3.095795154571533,
  1004. RuleID: "test-rule",
  1005. },
  1006. },
  1007. },
  1008. "regex OR stopword allowed": {
  1009. fragment: Fragment{},
  1010. allowlist: config.Allowlist{
  1011. MatchCondition: config.AllowlistMatchOr,
  1012. Regexes: []*regexp.Regexp{regexp.MustCompile(`(?i)summer.+`)},
  1013. StopWords: []string{"winter"},
  1014. },
  1015. },
  1016. }
  1017. raw := `let username = 'james@mail.com';
  1018. let password = 'Summer2024!';`
  1019. for name, tc := range cases {
  1020. t.Run(name, func(t *testing.T) {
  1021. rule := config.Rule{
  1022. RuleID: "test-rule",
  1023. Regex: regexp.MustCompile(`Summer2024!`),
  1024. Allowlists: []config.Allowlist{
  1025. tc.allowlist,
  1026. },
  1027. }
  1028. d, err := NewDetectorDefaultConfig()
  1029. require.NoError(t, err)
  1030. f := tc.fragment
  1031. f.Raw = raw
  1032. actual := d.detectRule(f, raw, rule, []EncodedSegment{})
  1033. if diff := cmp.Diff(tc.expected, actual); diff != "" {
  1034. t.Errorf("diff: (-want +got)\n%s", diff)
  1035. }
  1036. })
  1037. }
  1038. }
  1039. func moveDotGit(t *testing.T, from, to string) {
  1040. t.Helper()
  1041. repoDirs, err := os.ReadDir("../testdata/repos")
  1042. require.NoError(t, err)
  1043. for _, dir := range repoDirs {
  1044. if to == ".git" {
  1045. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
  1046. if os.IsNotExist(err) {
  1047. // dont want to delete the only copy of .git accidentally
  1048. continue
  1049. }
  1050. os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
  1051. }
  1052. if !dir.IsDir() {
  1053. continue
  1054. }
  1055. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
  1056. if os.IsNotExist(err) {
  1057. continue
  1058. }
  1059. err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
  1060. fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
  1061. require.NoError(t, err)
  1062. }
  1063. }