Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lua/null-ls/builtins/diagnostics/gitleaks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
local h = require("null-ls.helpers")
local methods = require("null-ls.methods")

local DIAGNOSTICS = methods.internal.DIAGNOSTICS

local handle_gitleaks_output = function(params)
local parser = h.diagnostics.from_json({
attributes = {
code = "code",
},
diagnostic = {
source = "gitleaks",
},
})

local offenses = {}
for _, finding in ipairs(params.output or {}) do
table.insert(offenses, {
message = finding.Description,
ruleId = finding.RuleID,
code = finding.RuleID,
line = finding.StartLine,
column = finding.StartColumn,
endLine = finding.EndLine,
endColumn = finding.EndColumn,
})
end

return parser({ output = offenses })
end

return h.make_builtin({
name = "gitleaks",
meta = {
url = "https://github.com/gitleaks/gitleaks",
description = "Gitleaks is a SAST tool for detecting and preventing hardcoded secrets like passwords, API keys, and tokens in git repos.",
},
method = DIAGNOSTICS,
filetypes = {},
generator_opts = {
command = "gitleaks",
args = {
"stdin",
"--report-format",
"json",
"--report-path",
"-",
"--exit-code",
"0",
"--no-banner",
},
format = "json",
to_stdin = true,
from_stderr = true,
ignore_stderr = true,
Comment on lines +42 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Test where gitleaks outputs JSON report with stdin command

# First check if gitleaks is available
if ! command -v gitleaks &> /dev/null; then
    echo "gitleaks not found in PATH"
    exit 1
fi

# Get gitleaks version
echo "=== Gitleaks version ==="
gitleaks version

# Create a test file with a fake secret
echo 'api_key = "abc123def456"' > /tmp/test_secret.txt

echo -e "\n=== Testing stdout output ==="
cat /tmp/test_secret.txt | gitleaks stdin --report-format json --report-path - --exit-code 0 --no-banner 2>/dev/null | head -c 200

echo -e "\n\n=== Testing stderr output ==="
cat /tmp/test_secret.txt | gitleaks stdin --report-format json --report-path - --exit-code 0 --no-banner 2>&1 >/dev/null | head -c 200

echo -e "\n\n=== Testing both streams combined ==="
cat /tmp/test_secret.txt | gitleaks stdin --report-format json --report-path - --exit-code 0 --no-banner 2>&1 | head -c 200

# Cleanup
rm /tmp/test_secret.txt

Repository: nvimtools/none-ls.nvim

Length of output: 678


Fix output stream configuration: from_stderr should be false.

The JSON report is output to stdout, not stderr. Gitleaks outputs only info/warning logs to stderr (e.g., "12:27PM INF scanned ~25 bytes"). With from_stderr = true, null-ls will attempt to parse stderr log messages as JSON, which will fail and prevent diagnostics from being generated.

Change line 54 to from_stderr = false to read the JSON report from stdout.

🤖 Prompt for AI Agents
In lua/null-ls/builtins/diagnostics/gitleaks.lua around lines 42 to 55, the
configuration incorrectly sets from_stderr = true so null-ls tries to parse
gitleaks' stderr logs as JSON; change from_stderr to false so the JSON report
(emitted on stdout) is read correctly (i.e., set from_stderr = false and keep
to_stdin/format as-is).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do ignore_stderr here.

check_exit_code = function(code)
return code == 0
end,
on_output = handle_gitleaks_output,
},
factory = h.generator_factory,
})
107 changes: 107 additions & 0 deletions test/spec/builtins/diagnostics/gitleaks_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
local diagnostics = require("null-ls.builtins").diagnostics

describe("diagnostics gitleaks", function()
local parser = diagnostics.gitleaks._opts.on_output

it("should create a diagnostic from gitleaks output", function()
local output = vim.json.decode([[
[
{
"RuleID": "generic-api-key",
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
"StartLine": 192,
"EndLine": 192,
"StartColumn": 8,
"EndColumn": 67,
"Match": "ocp-apim-subscription-key: 5ccb5b137e7444d885be752eda7f767a'",
"Secret": "5ccb5b137e7444d885be752eda7f767a",
"File": "zsh/zsh.d/functions.zsh",
"SymlinkFile": "",
"Commit": "",
"Entropy": 3.5695488,
"Author": "",
"Email": "",
"Date": "",
"Message": "",
"Tags": [],
"Fingerprint": "zsh/zsh.d/functions.zsh:generic-api-key:192"
}
]
]])
local diagnostic = parser({ output = output })
assert.same({
{
row = 192,
col = 8,
end_row = 192,
end_col = 67,
message = "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
source = "gitleaks",
code = "generic-api-key",
},
}, diagnostic)
end)

it("should handle multiple findings", function()
local output = vim.json.decode([[
[
{
"RuleID": "generic-api-key",
"Description": "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
"StartLine": 10,
"EndLine": 10,
"StartColumn": 5,
"EndColumn": 30,
"Match": "api_key = 'abc123'",
"Secret": "abc123",
"File": "config.py",
"Fingerprint": "config.py:generic-api-key:10"
},
{
"RuleID": "aws-access-token",
"Description": "Detected AWS Access Token, risking unauthorized cloud resource access and data breaches.",
"StartLine": 25,
"EndLine": 25,
"StartColumn": 12,
"EndColumn": 50,
"Match": "AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI",
"Secret": "wJalrXUtnFEMI",
"File": "env.sh",
"Fingerprint": "env.sh:aws-access-token:25"
}
]
]])
local diagnostic = parser({ output = output })
assert.same({
{
row = 10,
col = 5,
end_row = 10,
end_col = 30,
message = "Detected a Generic API Key, potentially exposing access to various services and sensitive operations.",
source = "gitleaks",
code = "generic-api-key",
},
{
row = 25,
col = 12,
end_row = 25,
end_col = 50,
message = "Detected AWS Access Token, risking unauthorized cloud resource access and data breaches.",
source = "gitleaks",
code = "aws-access-token",
},
}, diagnostic)
end)

it("should handle empty output", function()
local output = vim.json.decode("[]")
local diagnostic = parser({ output = output })
assert.same({}, diagnostic)
end)

it("should handle nil output", function()
local diagnostic = parser({ output = nil })
assert.same({}, diagnostic)
end)
end)