detect_test.go 21 KB

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