Skip to content

Commit e701fa6

Browse files
mosheavniMoshe Avni
andauthored
feat(diagnostics): gitleaks builtin (#315)
* fix(formatting): npm_groovy_lint fix timeout and no error https://github.com/nvuillam/npm-groovy-lint?tab=readme-ov-file#usage * chore: Auto generate docs * chore: Auto generate docs * feat: add gitleaks diagnostic builtin Add support for gitleaks SAST tool for detecting hardcoded secrets like passwords, API keys, and tokens in git repos. * add tests, convert to stdin --------- Co-authored-by: Moshe Avni <mavni@netapp.com> Co-authored-by: mosheavni <mosheavni@users.noreply.github.com>
1 parent 5abf619 commit e701fa6

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
local h = require("null-ls.helpers")
2+
local methods = require("null-ls.methods")
3+
4+
local DIAGNOSTICS = methods.internal.DIAGNOSTICS
5+
6+
local handle_gitleaks_output = function(params)
7+
local parser = h.diagnostics.from_json({
8+
attributes = {
9+
code = "code",
10+
},
11+
diagnostic = {
12+
source = "gitleaks",
13+
},
14+
})
15+
16+
local offenses = {}
17+
for _, finding in ipairs(params.output or {}) do
18+
table.insert(offenses, {
19+
message = finding.Description,
20+
ruleId = finding.RuleID,
21+
code = finding.RuleID,
22+
line = finding.StartLine,
23+
column = finding.StartColumn,
24+
endLine = finding.EndLine,
25+
endColumn = finding.EndColumn,
26+
})
27+
end
28+
29+
return parser({ output = offenses })
30+
end
31+
32+
return h.make_builtin({
33+
name = "gitleaks",
34+
meta = {
35+
url = "https://github.com/gitleaks/gitleaks",
36+
description = "Gitleaks is a SAST tool for detecting and preventing hardcoded secrets like passwords, API keys, and tokens in git repos.",
37+
},
38+
method = DIAGNOSTICS,
39+
filetypes = {},
40+
generator_opts = {
41+
command = "gitleaks",
42+
args = {
43+
"stdin",
44+
"--report-format",
45+
"json",
46+
"--report-path",
47+
"-",
48+
"--exit-code",
49+
"0",
50+
"--no-banner",
51+
},
52+
format = "json",
53+
to_stdin = true,
54+
from_stderr = true,
55+
ignore_stderr = true,
56+
check_exit_code = function(code)
57+
return code == 0
58+
end,
59+
on_output = handle_gitleaks_output,
60+
},
61+
factory = h.generator_factory,
62+
})
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
local diagnostics = require("null-ls.builtins").diagnostics
2+
3+
describe("diagnostics gitleaks", function()
4+
local parser = diagnostics.gitleaks._opts.on_output
5+
6+
it("should create a diagnostic from gitleaks output", function()
7+
local output = vim.json.decode([[
8+
[
9+
{
10+
"RuleID": "generic-api-key",
11+
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
12+
"StartLine": 192,
13+
"EndLine": 192,
14+
"StartColumn": 8,
15+
"EndColumn": 67,
16+
"Match": "ocp-apim-subscription-key: 5ccb5b137e7444d885be752eda7f767a'",
17+
"Secret": "5ccb5b137e7444d885be752eda7f767a",
18+
"File": "zsh/zsh.d/functions.zsh",
19+
"SymlinkFile": "",
20+
"Commit": "",
21+
"Entropy": 3.5695488,
22+
"Author": "",
23+
"Email": "",
24+
"Date": "",
25+
"Message": "",
26+
"Tags": [],
27+
"Fingerprint": "zsh/zsh.d/functions.zsh:generic-api-key:192"
28+
}
29+
]
30+
]])
31+
local diagnostic = parser({ output = output })
32+
assert.same({
33+
{
34+
row = 192,
35+
col = 8,
36+
end_row = 192,
37+
end_col = 67,
38+
message = "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
39+
source = "gitleaks",
40+
code = "generic-api-key",
41+
},
42+
}, diagnostic)
43+
end)
44+
45+
it("should handle multiple findings", function()
46+
local output = vim.json.decode([[
47+
[
48+
{
49+
"RuleID": "generic-api-key",
50+
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
51+
"StartLine": 10,
52+
"EndLine": 10,
53+
"StartColumn": 5,
54+
"EndColumn": 30,
55+
"Match": "api_key = 'abc123'",
56+
"Secret": "abc123",
57+
"File": "config.py",
58+
"Fingerprint": "config.py:generic-api-key:10"
59+
},
60+
{
61+
"RuleID": "aws-access-token",
62+
"Description": "Detected AWS Access Token, risking unauthorized cloud resource access and data breaches.",
63+
"StartLine": 25,
64+
"EndLine": 25,
65+
"StartColumn": 12,
66+
"EndColumn": 50,
67+
"Match": "AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI",
68+
"Secret": "wJalrXUtnFEMI",
69+
"File": "env.sh",
70+
"Fingerprint": "env.sh:aws-access-token:25"
71+
}
72+
]
73+
]])
74+
local diagnostic = parser({ output = output })
75+
assert.same({
76+
{
77+
row = 10,
78+
col = 5,
79+
end_row = 10,
80+
end_col = 30,
81+
message = "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
82+
source = "gitleaks",
83+
code = "generic-api-key",
84+
},
85+
{
86+
row = 25,
87+
col = 12,
88+
end_row = 25,
89+
end_col = 50,
90+
message = "Detected AWS Access Token, risking unauthorized cloud resource access and data breaches.",
91+
source = "gitleaks",
92+
code = "aws-access-token",
93+
},
94+
}, diagnostic)
95+
end)
96+
97+
it("should handle empty output", function()
98+
local output = vim.json.decode("[]")
99+
local diagnostic = parser({ output = output })
100+
assert.same({}, diagnostic)
101+
end)
102+
103+
it("should handle nil output", function()
104+
local diagnostic = parser({ output = nil })
105+
assert.same({}, diagnostic)
106+
end)
107+
end)

0 commit comments

Comments
 (0)