detect_test.go 21 KB

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