detect_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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. fragment Fragment
  18. expectedFindings []report.Finding
  19. wantError error
  20. }{
  21. {
  22. cfgName: "simple",
  23. fragment: Fragment{
  24. Raw: `awsToken := \"AKIALALEMEL33243OKIA\ // gitleaks:allow"`,
  25. FilePath: "tmp.go",
  26. },
  27. expectedFindings: []report.Finding{},
  28. },
  29. {
  30. cfgName: "simple",
  31. fragment: Fragment{
  32. Raw: `awsToken := \
  33. \"AKIALALEMEL33243OKIA\ // gitleaks:allow"
  34. `,
  35. FilePath: "tmp.go",
  36. },
  37. expectedFindings: []report.Finding{},
  38. },
  39. {
  40. cfgName: "simple",
  41. fragment: Fragment{
  42. Raw: `awsToken := \"AKIALALEMEL33243OKIA\"
  43. // gitleaks:allow"
  44. `,
  45. FilePath: "tmp.go",
  46. },
  47. expectedFindings: []report.Finding{
  48. {
  49. Description: "AWS Access Key",
  50. Secret: "AKIALALEMEL33243OKIA",
  51. Match: "AKIALALEMEL33243OKIA",
  52. File: "tmp.go",
  53. RuleID: "aws-access-key",
  54. Tags: []string{"key", "AWS"},
  55. StartLine: 0,
  56. EndLine: 0,
  57. StartColumn: 15,
  58. EndColumn: 34,
  59. Entropy: 3.1464393,
  60. },
  61. },
  62. },
  63. {
  64. cfgName: "escaped_character_group",
  65. fragment: Fragment{
  66. Raw: `pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB`,
  67. FilePath: "tmp.go",
  68. },
  69. expectedFindings: []report.Finding{
  70. {
  71. Description: "PyPI upload token",
  72. Secret: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  73. Match: "pypi-AgEIcHlwaS5vcmcAAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAA-AAAAAAAAAAB",
  74. File: "tmp.go",
  75. RuleID: "pypi-upload-token",
  76. Tags: []string{"key", "pypi"},
  77. StartLine: 1,
  78. EndLine: 1,
  79. StartColumn: 1,
  80. EndColumn: 86,
  81. Entropy: 1.9606875,
  82. },
  83. },
  84. },
  85. {
  86. cfgName: "simple",
  87. fragment: Fragment{
  88. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  89. FilePath: "tmp.go",
  90. },
  91. expectedFindings: []report.Finding{
  92. {
  93. Description: "AWS Access Key",
  94. Secret: "AKIALALEMEL33243OLIA",
  95. Match: "AKIALALEMEL33243OLIA",
  96. File: "tmp.go",
  97. RuleID: "aws-access-key",
  98. Tags: []string{"key", "AWS"},
  99. StartLine: 1,
  100. EndLine: 1,
  101. StartColumn: 15,
  102. EndColumn: 34,
  103. Entropy: 3.0841837,
  104. },
  105. },
  106. },
  107. {
  108. cfgName: "allow_aws_re",
  109. fragment: Fragment{
  110. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  111. FilePath: "tmp.go",
  112. },
  113. expectedFindings: []report.Finding{},
  114. },
  115. {
  116. cfgName: "allow_path",
  117. fragment: Fragment{
  118. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  119. FilePath: "tmp.go",
  120. },
  121. expectedFindings: []report.Finding{},
  122. },
  123. {
  124. cfgName: "allow_commit",
  125. fragment: Fragment{
  126. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  127. FilePath: "tmp.go",
  128. CommitSHA: "allowthiscommit",
  129. },
  130. expectedFindings: []report.Finding{},
  131. },
  132. {
  133. cfgName: "entropy_group",
  134. fragment: Fragment{
  135. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  136. FilePath: "tmp.go",
  137. },
  138. expectedFindings: []report.Finding{
  139. {
  140. Description: "Discord API key",
  141. Match: "Discord_Public_Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  142. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  143. File: "tmp.go",
  144. RuleID: "discord-api-key",
  145. Tags: []string{},
  146. Entropy: 3.7906237,
  147. StartLine: 1,
  148. EndLine: 1,
  149. StartColumn: 7,
  150. EndColumn: 93,
  151. },
  152. },
  153. },
  154. {
  155. cfgName: "generic_with_py_path",
  156. fragment: Fragment{
  157. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  158. FilePath: "tmp.go",
  159. },
  160. expectedFindings: []report.Finding{},
  161. },
  162. {
  163. cfgName: "generic_with_py_path",
  164. fragment: Fragment{
  165. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  166. FilePath: "tmp.py",
  167. },
  168. expectedFindings: []report.Finding{
  169. {
  170. Description: "Generic API Key",
  171. Match: "Key = \"e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5\"",
  172. Secret: "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5",
  173. File: "tmp.py",
  174. RuleID: "generic-api-key",
  175. Tags: []string{},
  176. Entropy: 3.7906237,
  177. StartLine: 1,
  178. EndLine: 1,
  179. StartColumn: 22,
  180. EndColumn: 93,
  181. },
  182. },
  183. },
  184. {
  185. cfgName: "path_only",
  186. fragment: Fragment{
  187. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  188. FilePath: "tmp.py",
  189. },
  190. expectedFindings: []report.Finding{
  191. {
  192. Description: "Python Files",
  193. Match: "file detected: tmp.py",
  194. File: "tmp.py",
  195. RuleID: "python-files-only",
  196. Tags: []string{},
  197. },
  198. },
  199. },
  200. {
  201. cfgName: "bad_entropy_group",
  202. fragment: Fragment{
  203. Raw: `const Discord_Public_Key = "e7322523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  204. FilePath: "tmp.go",
  205. },
  206. expectedFindings: []report.Finding{},
  207. wantError: fmt.Errorf("Discord API key invalid regex secret group 5, max regex secret group 3"),
  208. },
  209. {
  210. cfgName: "simple",
  211. fragment: Fragment{
  212. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  213. FilePath: filepath.Join(configPath, "simple.toml"),
  214. },
  215. expectedFindings: []report.Finding{},
  216. },
  217. {
  218. cfgName: "allow_global_aws_re",
  219. fragment: Fragment{
  220. Raw: `awsToken := \"AKIALALEMEL33243OLIA\"`,
  221. FilePath: "tmp.go",
  222. },
  223. expectedFindings: []report.Finding{},
  224. },
  225. {
  226. cfgName: "generic_with_py_path",
  227. fragment: Fragment{
  228. Raw: `const Discord_Public_Key = "load2523fb86ed64c836a979cf8465fbd436378c653c1db38f9ae87bc62a6fd5"`,
  229. FilePath: "tmp.py",
  230. },
  231. expectedFindings: []report.Finding{},
  232. },
  233. }
  234. for _, tt := range tests {
  235. viper.Reset()
  236. viper.AddConfigPath(configPath)
  237. viper.SetConfigName(tt.cfgName)
  238. viper.SetConfigType("toml")
  239. err := viper.ReadInConfig()
  240. if err != nil {
  241. t.Error(err)
  242. }
  243. var vc config.ViperConfig
  244. err = viper.Unmarshal(&vc)
  245. if err != nil {
  246. t.Error(err)
  247. }
  248. cfg, err := vc.Translate()
  249. cfg.Path = filepath.Join(configPath, tt.cfgName+".toml")
  250. if tt.wantError != nil {
  251. if err == nil {
  252. t.Errorf("expected error")
  253. }
  254. assert.Equal(t, tt.wantError, err)
  255. }
  256. d := NewDetector(cfg)
  257. findings := d.Detect(tt.fragment)
  258. assert.ElementsMatch(t, tt.expectedFindings, findings)
  259. }
  260. }
  261. // TestFromGit tests the FromGit function
  262. func TestFromGit(t *testing.T) {
  263. tests := []struct {
  264. cfgName string
  265. source string
  266. logOpts string
  267. expectedFindings []report.Finding
  268. }{
  269. {
  270. source: filepath.Join(repoBasePath, "small"),
  271. cfgName: "simple",
  272. expectedFindings: []report.Finding{
  273. {
  274. Description: "AWS Access Key",
  275. StartLine: 20,
  276. EndLine: 20,
  277. StartColumn: 19,
  278. EndColumn: 38,
  279. Secret: "AKIALALEMEL33243OLIA",
  280. Match: "AKIALALEMEL33243OLIA",
  281. File: "main.go",
  282. Date: "2021-11-02T23:37:53Z",
  283. Commit: "1b6da43b82b22e4eaa10bcf8ee591e91abbfc587",
  284. Author: "Zachary Rice",
  285. Email: "zricer@protonmail.com",
  286. Message: "Accidentally add a secret",
  287. RuleID: "aws-access-key",
  288. Tags: []string{"key", "AWS"},
  289. Entropy: 3.0841837,
  290. },
  291. {
  292. Description: "AWS Access Key",
  293. StartLine: 9,
  294. EndLine: 9,
  295. StartColumn: 17,
  296. EndColumn: 36,
  297. Secret: "AKIALALEMEL33243OLIA",
  298. Match: "AKIALALEMEL33243OLIA",
  299. File: "foo/foo.go",
  300. Date: "2021-11-02T23:48:06Z",
  301. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  302. Author: "Zach Rice",
  303. Email: "zricer@protonmail.com",
  304. Message: "adding foo package with secret",
  305. RuleID: "aws-access-key",
  306. Tags: []string{"key", "AWS"},
  307. Entropy: 3.0841837,
  308. },
  309. },
  310. },
  311. {
  312. source: filepath.Join(repoBasePath, "small"),
  313. logOpts: "--all foo...",
  314. cfgName: "simple",
  315. expectedFindings: []report.Finding{
  316. {
  317. Description: "AWS Access Key",
  318. StartLine: 9,
  319. EndLine: 9,
  320. StartColumn: 17,
  321. EndColumn: 36,
  322. Secret: "AKIALALEMEL33243OLIA",
  323. Match: "AKIALALEMEL33243OLIA",
  324. Date: "2021-11-02T23:48:06Z",
  325. File: "foo/foo.go",
  326. Commit: "491504d5a31946ce75e22554cc34203d8e5ff3ca",
  327. Author: "Zach Rice",
  328. Email: "zricer@protonmail.com",
  329. Message: "adding foo package with secret",
  330. RuleID: "aws-access-key",
  331. Tags: []string{"key", "AWS"},
  332. Entropy: 3.0841837,
  333. },
  334. },
  335. },
  336. }
  337. err := moveDotGit("dotGit", ".git")
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. defer func() {
  342. if err := moveDotGit(".git", "dotGit"); err != nil {
  343. t.Error(err)
  344. }
  345. }()
  346. for _, tt := range tests {
  347. viper.AddConfigPath(configPath)
  348. viper.SetConfigName("simple")
  349. viper.SetConfigType("toml")
  350. err = viper.ReadInConfig()
  351. if err != nil {
  352. t.Error(err)
  353. }
  354. var vc config.ViperConfig
  355. err = viper.Unmarshal(&vc)
  356. if err != nil {
  357. t.Error(err)
  358. }
  359. cfg, err := vc.Translate()
  360. if err != nil {
  361. t.Error(err)
  362. }
  363. detector := NewDetector(cfg)
  364. findings, err := detector.DetectGit(tt.source, tt.logOpts, DetectType)
  365. if err != nil {
  366. t.Error(err)
  367. }
  368. for _, f := range findings {
  369. f.Match = "" // remove lines cause copying and pasting them has some wack formatting
  370. }
  371. assert.ElementsMatch(t, tt.expectedFindings, findings)
  372. }
  373. }
  374. // TestFromGit tests the FromGit function
  375. func TestFromFiles(t *testing.T) {
  376. tests := []struct {
  377. cfgName string
  378. source string
  379. expectedFindings []report.Finding
  380. }{
  381. {
  382. source: filepath.Join(repoBasePath, "nogit"),
  383. cfgName: "simple",
  384. expectedFindings: []report.Finding{
  385. {
  386. Description: "AWS Access Key",
  387. StartLine: 20,
  388. EndLine: 20,
  389. StartColumn: 16,
  390. EndColumn: 35,
  391. Match: "AKIALALEMEL33243OLIA",
  392. Secret: "AKIALALEMEL33243OLIA",
  393. File: "../testdata/repos/nogit/main.go",
  394. RuleID: "aws-access-key",
  395. Tags: []string{"key", "AWS"},
  396. Entropy: 3.0841837,
  397. },
  398. },
  399. },
  400. {
  401. source: filepath.Join(repoBasePath, "nogit", "main.go"),
  402. cfgName: "simple",
  403. expectedFindings: []report.Finding{
  404. {
  405. Description: "AWS Access Key",
  406. StartLine: 20,
  407. EndLine: 20,
  408. StartColumn: 16,
  409. EndColumn: 35,
  410. Match: "AKIALALEMEL33243OLIA",
  411. Secret: "AKIALALEMEL33243OLIA",
  412. File: "../testdata/repos/nogit/main.go",
  413. RuleID: "aws-access-key",
  414. Tags: []string{"key", "AWS"},
  415. Entropy: 3.0841837,
  416. },
  417. },
  418. },
  419. }
  420. for _, tt := range tests {
  421. viper.AddConfigPath(configPath)
  422. viper.SetConfigName("simple")
  423. viper.SetConfigType("toml")
  424. err := viper.ReadInConfig()
  425. if err != nil {
  426. t.Error(err)
  427. }
  428. var vc config.ViperConfig
  429. err = viper.Unmarshal(&vc)
  430. if err != nil {
  431. t.Error(err)
  432. }
  433. cfg, _ := vc.Translate()
  434. detector := NewDetector(cfg)
  435. findings, err := detector.DetectFiles(tt.source)
  436. if err != nil {
  437. t.Error(err)
  438. }
  439. assert.ElementsMatch(t, tt.expectedFindings, findings)
  440. }
  441. }
  442. func moveDotGit(from, to string) error {
  443. repoDirs, err := os.ReadDir("../testdata/repos")
  444. if err != nil {
  445. return err
  446. }
  447. for _, dir := range repoDirs {
  448. if to == ".git" {
  449. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), "dotGit"))
  450. if os.IsNotExist(err) {
  451. // dont want to delete the only copy of .git accidentally
  452. continue
  453. }
  454. os.RemoveAll(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), ".git"))
  455. }
  456. if !dir.IsDir() {
  457. continue
  458. }
  459. _, err := os.Stat(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from))
  460. if os.IsNotExist(err) {
  461. continue
  462. }
  463. err = os.Rename(fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), from),
  464. fmt.Sprintf("%s/%s/%s", repoBasePath, dir.Name(), to))
  465. if err != nil {
  466. return err
  467. }
  468. }
  469. return nil
  470. }