detect_test.go 36 KB

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