detect_test.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. package detect
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/spf13/viper"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/require"
  10. "github.com/zricethezav/gitleaks/v8/config"
  11. "github.com/zricethezav/gitleaks/v8/report"
  12. "github.com/zricethezav/gitleaks/v8/sources"
  13. )
  14. const configPath = "../testdata/config/"
  15. const repoBasePath = "../testdata/repos/"
  16. func TestDetect(t *testing.T) {
  17. tests := []struct {
  18. cfgName string
  19. baselinePath string
  20. fragment Fragment
  21. // NOTE: for expected findings, all line numbers will be 0
  22. // because line deltas are added _after_ the finding is created.
  23. // I.e., if the finding is from a --no-git file, the line number will be
  24. // increase by 1 in DetectFromFiles(). If the finding is from git,
  25. // the line number will be increased by the patch delta.
  26. expectedFindings []report.Finding
  27. wantError error
  28. }{
  29. {
  30. cfgName: "simple",
  31. fragment: Fragment{
  32. Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // gitleaks:allow"`,
  33. FilePath: "tmp.go",
  34. },
  35. expectedFindings: []report.Finding{},
  36. },
  37. {
  38. cfgName: "simple",
  39. fragment: Fragment{
  40. Raw: `awsToken := \
  41. \"AKIALALEMEL33243OKIA\ // gitleaks:allow"
  42. `,
  43. FilePath: "tmp.go",
  44. },
  45. expectedFindings: []report.Finding{},
  46. },
  47. {
  48. cfgName: "simple",
  49. fragment: Fragment{
  50. Raw: `awsToken := \"AKIALALEMEL33243OKIA\"
  51. // gitleaks:allow"
  52. `,
  53. FilePath: "tmp.go",
  54. },
  55. expectedFindings: []report.Finding{
  56. {
  57. Description: "AWS Access Key",
  58. Secret: "AKIALALEMEL33243OKIA",
  59. Match: "AKIALALEMEL33243OKIA",
  60. File: "tmp.go",
  61. Line: `awsToken := \"AKIALALEMEL33243OKIA\"`,
  62. RuleID: "aws-access-key",
  63. Tags: []string{"key", "AWS"},
  64. StartLine: 0,
  65. EndLine: 0,
  66. StartColumn: 15,
  67. EndColumn: 34,
  68. Entropy: 3.1464393,
  69. },
  70. },
  71. },
  72. {
  73. cfgName: "escaped_character_group",
  74. fragment: Fragment{
  75. Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  76. FilePath: "tmp.go",
  77. },
  78. expectedFindings: []report.Finding{
  79. {
  80. Description: "PyPI upload token",
  81. Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  82. Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  83. Line: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  84. File: "tmp.go",
  85. RuleID: "pypi-upload-token",
  86. Tags: []string{"key", "pypi"},
  87. StartLine: 0,
  88. EndLine: 0,
  89. StartColumn: 1,
  90. EndColumn: 86,
  91. Entropy: 1.9606875,
  92. },
  93. },
  94. },
  95. {
  96. cfgName: "simple",
  97. fragment: Fragment{
  98. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  99. FilePath: "tmp.go",
  100. },
  101. expectedFindings: []report.Finding{
  102. {
  103. Description: "AWS Access Key",
  104. Secret: "AKIALALEMEL33243OLIA",
  105. Match: "AKIALALEMEL33243OLIA",
  106. Line: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  107. File: "tmp.go",
  108. RuleID: "aws-access-key",
  109. Tags: []string{"key", "AWS"},
  110. StartLine: 0,
  111. EndLine: 0,
  112. StartColumn: 15,
  113. EndColumn: 34,
  114. Entropy: 3.0841837,
  115. },
  116. },
  117. },
  118. {
  119. cfgName: "simple",
  120. fragment: Fragment{
  121. Raw: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  122. FilePath: "tmp.sh",
  123. },
  124. expectedFindings: []report.Finding{
  125. {
  126. Description: "Sidekiq Secret",
  127. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;",
  128. Secret: "cafebabe:deadbeef",
  129. Line: `export BUNDLE_ENTERPRISE__CONTRIBSYS__COM=cafebabe:deadbeef;`,
  130. File: "tmp.sh",
  131. RuleID: "sidekiq-secret",
  132. Tags: []string{},
  133. Entropy: 2.6098502,
  134. StartLine: 0,
  135. EndLine: 0,
  136. StartColumn: 8,
  137. EndColumn: 60,
  138. },
  139. },
  140. },
  141. {
  142. cfgName: "simple",
  143. fragment: Fragment{
  144. Raw: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  145. FilePath: "tmp.sh",
  146. },
  147. expectedFindings: []report.Finding{
  148. {
  149. Description: "Sidekiq Secret",
  150. Match: "BUNDLE_ENTERPRISE__CONTRIBSYS__COM=\"cafebabe:deadbeef\"",
  151. Secret: "cafebabe:deadbeef",
  152. File: "tmp.sh",
  153. Line: `echo hello1; export BUNDLE_ENTERPRISE__CONTRIBSYS__COM="cafebabe:deadbeef" && echo hello2`,
  154. RuleID: "sidekiq-secret",
  155. Tags: []string{},
  156. Entropy: 2.6098502,
  157. StartLine: 0,
  158. EndLine: 0,
  159. StartColumn: 21,
  160. EndColumn: 74,
  161. },
  162. },
  163. },
  164. {
  165. cfgName: "simple",
  166. fragment: Fragment{
  167. Raw: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  168. FilePath: "tmp.sh",
  169. },
  170. expectedFindings: []report.Finding{
  171. {
  172. Description: "Sidekiq Sensitive URL",
  173. Match: "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:",
  174. Secret: "cafeb4b3:d3adb33f",
  175. File: "tmp.sh",
  176. Line: `url = "http://cafeb4b3:d3adb33f@enterprise.contribsys.com:80/path?param1=true&param2=false#heading1"`,
  177. RuleID: "sidekiq-sensitive-url",
  178. Tags: []string{},
  179. Entropy: 2.984234,
  180. StartLine: 0,
  181. EndLine: 0,
  182. StartColumn: 8,
  183. EndColumn: 58,
  184. },
  185. },
  186. },
  187. {
  188. cfgName: "allow_aws_re",
  189. fragment: Fragment{
  190. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  191. FilePath: "tmp.go",
  192. },
  193. expectedFindings: []report.Finding{},
  194. },
  195. {
  196. cfgName: "allow_path",
  197. fragment: Fragment{
  198. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  199. FilePath: "tmp.go",
  200. },
  201. expectedFindings: []report.Finding{},
  202. },
  203. {
  204. cfgName: "allow_commit",
  205. fragment: Fragment{
  206. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  207. FilePath: "tmp.go",
  208. CommitSHA: "allowthiscommit",
  209. },
  210. expectedFindings: []report.Finding{},
  211. },
  212. {
  213. cfgName: "entropy_group",
  214. fragment: Fragment{
  215. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  216. FilePath: "tmp.go",
  217. },
  218. expectedFindings: []report.Finding{
  219. {
  220. Description: "Discord API key",
  221. Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  222. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  223. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  224. File: "tmp.go",
  225. RuleID: "discord-api-key",
  226. Tags: []string{},
  227. Entropy: 3.7906237,
  228. StartLine: 0,
  229. EndLine: 0,
  230. StartColumn: 7,
  231. EndColumn: 93,
  232. },
  233. },
  234. },
  235. {
  236. cfgName: "generic_with_py_path",
  237. fragment: Fragment{
  238. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  239. FilePath: "tmp.go",
  240. },
  241. expectedFindings: []report.Finding{},
  242. },
  243. {
  244. cfgName: "generic_with_py_path",
  245. fragment: Fragment{
  246. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  247. FilePath: "tmp.py",
  248. },
  249. expectedFindings: []report.Finding{
  250. {
  251. Description: "Generic API Key",
  252. Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  253. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  254. Line: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  255. File: "tmp.py",
  256. RuleID: "generic-api-key",
  257. Tags: []string{},
  258. Entropy: 3.7906237,
  259. StartLine: 0,
  260. EndLine: 0,
  261. StartColumn: 22,
  262. EndColumn: 93,
  263. },
  264. },
  265. },
  266. {
  267. cfgName: "path_only",
  268. fragment: Fragment{
  269. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  270. FilePath: "tmp.py",
  271. },
  272. expectedFindings: []report.Finding{
  273. {
  274. Description: "Python Files",
  275. Match: "file detected: tmp.py",
  276. File: "tmp.py",
  277. RuleID: "python-files-only",
  278. Tags: []string{},
  279. },
  280. },
  281. },
  282. {
  283. cfgName: "bad_entropy_group",
  284. fragment: Fragment{
  285. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  286. FilePath: "tmp.go",
  287. },
  288. expectedFindings: []report.Finding{},
  289. wantError: fmt.Errorf("discord-api-key: invalid regex secret group 5, max regex secret group 3"),
  290. },
  291. {
  292. cfgName: "simple",
  293. fragment: Fragment{
  294. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  295. FilePath: filepath.Join(configPath, "simple.toml"),
  296. },
  297. expectedFindings: []report.Finding{},
  298. },
  299. {
  300. cfgName: "allow_global_aws_re",
  301. fragment: Fragment{
  302. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  303. FilePath: "tmp.go",
  304. },
  305. expectedFindings: []report.Finding{},
  306. },
  307. {
  308. cfgName: "generic_with_py_path",
  309. fragment: Fragment{
  310. Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  311. FilePath: "tmp.py",
  312. },
  313. expectedFindings: []report.Finding{},
  314. },
  315. {
  316. cfgName: "path_only",
  317. baselinePath: ".baseline.json",
  318. fragment: Fragment{
  319. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  320. FilePath: ".baseline.json",
  321. },
  322. expectedFindings: []report.Finding{},
  323. },
  324. }
  325. for _, tt := range tests {
  326. viper.Reset()
  327. viper.AddConfigPath(configPath)
  328. viper.SetConfigName(tt.cfgName)
  329. viper.SetConfigType("toml")
  330. err := viper.ReadInConfig()
  331. require.NoError(t, err)
  332. var vc config.ViperConfig
  333. err = viper.Unmarshal(&vc)
  334. require.NoError(t, err)
  335. cfg, err := vc.Translate()
  336. cfg.Path = filepath.Join(configPath, tt.cfgName+".toml")
  337. assert.Equal(t, tt.wantError, err)
  338. d := NewDetector(cfg)
  339. d.baselinePath = tt.baselinePath
  340. findings := d.Detect(tt.fragment)
  341. assert.ElementsMatch(t, tt.expectedFindings, findings)
  342. }
  343. }
  344. // TestFromGit tests the FromGit function
  345. func TestFromGit(t *testing.T) {
  346. tests := []struct {
  347. cfgName string
  348. source string
  349. logOpts string
  350. expectedFindings []report.Finding
  351. }{
  352. {
  353. source: filepath.Join(repoBasePath, "small"),
  354. cfgName: "simple",
  355. expectedFindings: []report.Finding{
  356. {
  357. Description: "AWS Access Key",
  358. StartLine: 20,
  359. EndLine: 20,
  360. StartColumn: 19,
  361. EndColumn: 38,
  362. Line: "\n awsToken := \"AKIALALEMEL33243OLIA\"",
  363. Secret: "AKIALALEMEL33243OLIA",
  364. Match: "AKIALALEMEL33243OLIA",
  365. File: "main.go",
  366. Date: "2021-11-02T23:37:53Z",
  367. Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587",
  368. Author: "Zachary Rice",
  369. Email: "zricer@protonmail.com",
  370. Message: "Accidentally add a secret",
  371. RuleID: "aws-access-key",
  372. Tags: []string{"key", "AWS"},
  373. Entropy: 3.0841837,
  374. Fingerprint: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587:main.go:aws-access-key:20",
  375. },
  376. {
  377. Description: "AWS Access Key",
  378. StartLine: 9,
  379. EndLine: 9,
  380. StartColumn: 17,
  381. EndColumn: 36,
  382. Secret: "AKIALALEMEL33243OLIA",
  383. Match: "AKIALALEMEL33243OLIA",
  384. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  385. File: "foo/foo.go",
  386. Date: "2021-11-02T23:48:06Z",
  387. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  388. Author: "Zach Rice",
  389. Email: "zricer@protonmail.com",
  390. Message: "adding foo package with secret",
  391. RuleID: "aws-access-key",
  392. Tags: []string{"key", "AWS"},
  393. Entropy: 3.0841837,
  394. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  395. },
  396. },
  397. },
  398. {
  399. source: filepath.Join(repoBasePath, "small"),
  400. logOpts: "--all foo...",
  401. cfgName: "simple",
  402. expectedFindings: []report.Finding{
  403. {
  404. Description: "AWS Access Key",
  405. StartLine: 9,
  406. EndLine: 9,
  407. StartColumn: 17,
  408. EndColumn: 36,
  409. Secret: "AKIALALEMEL33243OLIA",
  410. Line: "\n\taws_token := \"AKIALALEMEL33243OLIA\"",
  411. Match: "AKIALALEMEL33243OLIA",
  412. Date: "2021-11-02T23:48:06Z",
  413. File: "foo/foo.go",
  414. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  415. Author: "Zach Rice",
  416. Email: "zricer@protonmail.com",
  417. Message: "adding foo package with secret",
  418. RuleID: "aws-access-key",
  419. Tags: []string{"key", "AWS"},
  420. Entropy: 3.0841837,
  421. Fingerprint: "491504d5a31946ce75e22554cc34203d8e5ff3ca:foo/foo.go:aws-access-key:9",
  422. },
  423. },
  424. },
  425. }
  426. moveDotGit(t, "dotGit", ".git")
  427. defer moveDotGit(t, ".git", "dotGit")
  428. for _, tt := range tests {
  429. viper.AddConfigPath(configPath)
  430. viper.SetConfigName("simple")
  431. viper.SetConfigType("toml")
  432. err := viper.ReadInConfig()
  433. require.NoError(t, err)
  434. var vc config.ViperConfig
  435. err = viper.Unmarshal(&vc)
  436. require.NoError(t, err)
  437. cfg, err := vc.Translate()
  438. require.NoError(t, err)
  439. detector := NewDetector(cfg)
  440. var ignorePath string
  441. info, err := os.Stat(tt.source)
  442. require.NoError(t, err)
  443. if info.IsDir() {
  444. ignorePath = filepath.Join(tt.source, ".gitleaksignore")
  445. } else {
  446. ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
  447. }
  448. err = detector.AddGitleaksIgnore(ignorePath)
  449. require.NoError(t, err)
  450. gitCmd, err := sources.NewGitLogCmd(tt.source, tt.logOpts)
  451. require.NoError(t, err)
  452. findings, err := detector.DetectGit(gitCmd)
  453. require.NoError(t, err)
  454. for _, f := range findings {
  455. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  456. }
  457. assert.ElementsMatch(t, tt.expectedFindings, findings)
  458. }
  459. }
  460. func TestFromGitStaged(t *testing.T) {
  461. tests := []struct {
  462. cfgName string
  463. source string
  464. logOpts string
  465. expectedFindings []report.Finding
  466. }{
  467. {
  468. source: filepath.Join(repoBasePath, "staged"),
  469. cfgName: "simple",
  470. expectedFindings: []report.Finding{
  471. {
  472. Description: "AWS Access Key",
  473. StartLine: 7,
  474. EndLine: 7,
  475. StartColumn: 18,
  476. EndColumn: 37,
  477. Line: "\n\taws_token2 := \"AKIALALEMEL33243OLIA\" // this one is not",
  478. Match: "AKIALALEMEL33243OLIA",
  479. Secret: "AKIALALEMEL33243OLIA",
  480. File: "api/api.go",
  481. SymlinkFile: "",
  482. Commit: "",
  483. Entropy: 3.0841837,
  484. Author: "",
  485. Email: "",
  486. Date: "0001-01-01T00:00:00Z",
  487. Message: "",
  488. Tags: []string{
  489. "key",
  490. "AWS",
  491. },
  492. RuleID: "aws-access-key",
  493. Fingerprint: "api/api.go:aws-access-key:7",
  494. },
  495. },
  496. },
  497. }
  498. moveDotGit(t, "dotGit", ".git")
  499. defer moveDotGit(t, ".git", "dotGit")
  500. for _, tt := range tests {
  501. viper.AddConfigPath(configPath)
  502. viper.SetConfigName("simple")
  503. viper.SetConfigType("toml")
  504. err := viper.ReadInConfig()
  505. require.NoError(t, err)
  506. var vc config.ViperConfig
  507. err = viper.Unmarshal(&vc)
  508. require.NoError(t, err)
  509. cfg, err := vc.Translate()
  510. require.NoError(t, err)
  511. detector := NewDetector(cfg)
  512. err = detector.AddGitleaksIgnore(filepath.Join(tt.source, ".gitleaksignore"))
  513. require.NoError(t, err)
  514. gitCmd, err := sources.NewGitDiffCmd(tt.source, true)
  515. require.NoError(t, err)
  516. findings, err := detector.DetectGit(gitCmd)
  517. require.NoError(t, err)
  518. for _, f := range findings {
  519. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  520. }
  521. assert.ElementsMatch(t, tt.expectedFindings, findings)
  522. }
  523. }
  524. // TestFromFiles tests the FromFiles function
  525. func TestFromFiles(t *testing.T) {
  526. tests := []struct {
  527. cfgName string
  528. source string
  529. expectedFindings []report.Finding
  530. }{
  531. {
  532. source: filepath.Join(repoBasePath, "nogit"),
  533. cfgName: "simple",
  534. expectedFindings: []report.Finding{
  535. {
  536. Description: "AWS Access Key",
  537. StartLine: 20,
  538. EndLine: 20,
  539. StartColumn: 16,
  540. EndColumn: 35,
  541. Match: "AKIALALEMEL33243OLIA",
  542. Secret: "AKIALALEMEL33243OLIA",
  543. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  544. File: "../testdata/repos/nogit/main.go",
  545. SymlinkFile: "",
  546. RuleID: "aws-access-key",
  547. Tags: []string{"key", "AWS"},
  548. Entropy: 3.0841837,
  549. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  550. },
  551. },
  552. },
  553. {
  554. source: filepath.Join(repoBasePath, "nogit", "main.go"),
  555. cfgName: "simple",
  556. expectedFindings: []report.Finding{
  557. {
  558. Description: "AWS Access Key",
  559. StartLine: 20,
  560. EndLine: 20,
  561. StartColumn: 16,
  562. EndColumn: 35,
  563. Match: "AKIALALEMEL33243OLIA",
  564. Secret: "AKIALALEMEL33243OLIA",
  565. Line: "\n\tawsToken := \"AKIALALEMEL33243OLIA\"",
  566. File: "../testdata/repos/nogit/main.go",
  567. RuleID: "aws-access-key",
  568. Tags: []string{"key", "AWS"},
  569. Entropy: 3.0841837,
  570. Fingerprint: "../testdata/repos/nogit/main.go:aws-access-key:20",
  571. },
  572. },
  573. },
  574. {
  575. source: filepath.Join(repoBasePath, "nogit", "api.go"),
  576. cfgName: "simple",
  577. expectedFindings: []report.Finding{},
  578. },
  579. {
  580. source: filepath.Join(repoBasePath, "nogit", ".env.prod"),
  581. cfgName: "generic",
  582. expectedFindings: []report.Finding{
  583. {
  584. Description: "Generic API Key",
  585. StartLine: 4,
  586. EndLine: 4,
  587. StartColumn: 5,
  588. EndColumn: 35,
  589. Match: "PASSWORD=8ae31cacf141669ddfb5da",
  590. Secret: "8ae31cacf141669ddfb5da",
  591. Line: "\nDB_PASSWORD=8ae31cacf141669ddfb5da",
  592. File: "../testdata/repos/nogit/.env.prod",
  593. RuleID: "generic-api-key",
  594. Tags: []string{},
  595. Entropy: 3.5383105,
  596. Fingerprint: "../testdata/repos/nogit/.env.prod:generic-api-key:4",
  597. },
  598. },
  599. },
  600. }
  601. for _, tt := range tests {
  602. viper.AddConfigPath(configPath)
  603. viper.SetConfigName(tt.cfgName)
  604. viper.SetConfigType("toml")
  605. err := viper.ReadInConfig()
  606. require.NoError(t, err)
  607. var vc config.ViperConfig
  608. err = viper.Unmarshal(&vc)
  609. require.NoError(t, err)
  610. cfg, _ := vc.Translate()
  611. detector := NewDetector(cfg)
  612. var ignorePath string
  613. info, err := os.Stat(tt.source)
  614. require.NoError(t, err)
  615. if info.IsDir() {
  616. ignorePath = filepath.Join(tt.source, ".gitleaksignore")
  617. } else {
  618. ignorePath = filepath.Join(filepath.Dir(tt.source), ".gitleaksignore")
  619. }
  620. err = detector.AddGitleaksIgnore(ignorePath)
  621. require.NoError(t, err)
  622. detector.FollowSymlinks = true
  623. paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true)
  624. require.NoError(t, err)
  625. findings, err := detector.DetectFiles(paths)
  626. require.NoError(t, err)
  627. assert.ElementsMatch(t, tt.expectedFindings, findings)
  628. }
  629. }
  630. func TestDetectWithSymlinks(t *testing.T) {
  631. tests := []struct {
  632. cfgName string
  633. source string
  634. expectedFindings []report.Finding
  635. }{
  636. {
  637. source: filepath.Join(repoBasePath, "symlinks/file_symlink"),
  638. cfgName: "simple",
  639. expectedFindings: []report.Finding{
  640. {
  641. Description: "Asymmetric Private Key",
  642. StartLine: 1,
  643. EndLine: 1,
  644. StartColumn: 1,
  645. EndColumn: 35,
  646. Match: "-----BEGIN OPENSSH PRIVATE KEY-----",
  647. Secret: "-----BEGIN OPENSSH PRIVATE KEY-----",
  648. Line: "-----BEGIN OPENSSH PRIVATE KEY-----",
  649. File: "../testdata/repos/symlinks/source_file/id_ed25519",
  650. SymlinkFile: "../testdata/repos/symlinks/file_symlink/symlinked_id_ed25519",
  651. RuleID: "apkey",
  652. Tags: []string{"key", "AsymmetricPrivateKey"},
  653. Entropy: 3.587164,
  654. Fingerprint: "../testdata/repos/symlinks/source_file/id_ed25519:apkey:1",
  655. },
  656. },
  657. },
  658. }
  659. for _, tt := range tests {
  660. viper.AddConfigPath(configPath)
  661. viper.SetConfigName("simple")
  662. viper.SetConfigType("toml")
  663. err := viper.ReadInConfig()
  664. require.NoError(t, err)
  665. var vc config.ViperConfig
  666. err = viper.Unmarshal(&vc)
  667. require.NoError(t, err)
  668. cfg, _ := vc.Translate()
  669. detector := NewDetector(cfg)
  670. detector.FollowSymlinks = true
  671. paths, err := sources.DirectoryTargets(tt.source, detector.Sema, true)
  672. require.NoError(t, err)
  673. findings, err := detector.DetectFiles(paths)
  674. require.NoError(t, err)
  675. assert.ElementsMatch(t, tt.expectedFindings, findings)
  676. }
  677. }
  678. func moveDotGit(t *testing.T, from, to string) {
  679. t.Helper()
  680. repoDirs, err := os.ReadDir("../testdata/repos")
  681. require.NoError(t, err)
  682. for _, dir := range repoDirs {
  683. if to == ".git" {
  684. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
  685. if os.IsNotExist(err) {
  686. // dont want to delete the only copy of .git accidentally
  687. continue
  688. }
  689. os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
  690. }
  691. if !dir.IsDir() {
  692. continue
  693. }
  694. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
  695. if os.IsNotExist(err) {
  696. continue
  697. }
  698. err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
  699. fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
  700. require.NoError(t, err)
  701. }
  702. }