mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-05-21 18:12:33 +02:00
+stylua
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
hooks = {
|
||||
gofmt.enable = true;
|
||||
govet.enable = true;
|
||||
stylua.enable = true;
|
||||
|
||||
gomod2nix = {
|
||||
enable = true;
|
||||
@@ -53,6 +54,7 @@ in
|
||||
go
|
||||
python3
|
||||
doctoc
|
||||
stylua
|
||||
trufflehog
|
||||
gomod2nixPkgs.gomod2nix
|
||||
]
|
||||
|
||||
+16
-16
@@ -1,6 +1,6 @@
|
||||
Plugin = {
|
||||
name = "Inject Header",
|
||||
description = [[
|
||||
name = "Inject Header",
|
||||
description = [[
|
||||
Inject custom headers into every intercepted request.
|
||||
|
||||
**Config** (YAML):
|
||||
@@ -9,26 +9,26 @@ headers:
|
||||
- "X-My-Header: myvalue"
|
||||
```
|
||||
]],
|
||||
on_request = { sync = true },
|
||||
on_request = { sync = true },
|
||||
}
|
||||
|
||||
local headers = {}
|
||||
|
||||
function on_config()
|
||||
headers = {}
|
||||
local cfg = get_config()
|
||||
if cfg and cfg.headers then
|
||||
for _, line in ipairs(cfg.headers) do
|
||||
local name, value = line:match("^([^:]+):%s*(.+)$")
|
||||
if name and value then
|
||||
table.insert(headers, { name = name, value = value })
|
||||
end
|
||||
end
|
||||
end
|
||||
headers = {}
|
||||
local cfg = get_config()
|
||||
if cfg and cfg.headers then
|
||||
for _, line in ipairs(cfg.headers) do
|
||||
local name, value = line:match("^([^:]+):%s*(.+)$")
|
||||
if name and value then
|
||||
table.insert(headers, { name = name, value = value })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_request(req)
|
||||
for _, h in ipairs(headers) do
|
||||
req:set_header(h.name, h.value)
|
||||
end
|
||||
for _, h in ipairs(headers) do
|
||||
req:set_header(h.name, h.value)
|
||||
end
|
||||
end
|
||||
|
||||
+46
-44
@@ -1,6 +1,6 @@
|
||||
Plugin = {
|
||||
name = "IP Filter (Whitelist/Blacklist)",
|
||||
description = [[
|
||||
name = "IP Filter (Whitelist/Blacklist)",
|
||||
description = [[
|
||||
Checks that the proxy's outbound IP is in an allowed list on startup.
|
||||
|
||||
**Config** (YAML):
|
||||
@@ -11,61 +11,63 @@ ips:
|
||||
```
|
||||
- If no IPs are configured, the check is skipped.
|
||||
]],
|
||||
on_start = { sync = false },
|
||||
disable_by_default = true,
|
||||
on_start = { sync = false },
|
||||
disable_by_default = true,
|
||||
}
|
||||
|
||||
local whitelist = {}
|
||||
local blacklist = {}
|
||||
|
||||
function on_config()
|
||||
whitelist, blacklist = {}, {}
|
||||
local cfg = get_config()
|
||||
if cfg and cfg.ips then
|
||||
for _, entry in ipairs(cfg.ips) do
|
||||
local trimmed = entry:match("^%s*(.-)%s*$")
|
||||
if trimmed ~= "" then
|
||||
if trimmed:sub(1, 1) == "!" then
|
||||
local ip = trimmed:sub(2):match("^%s*(.-)%s*$")
|
||||
if ip ~= "" then table.insert(blacklist, ip) end
|
||||
else
|
||||
table.insert(whitelist, trimmed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
whitelist, blacklist = {}, {}
|
||||
local cfg = get_config()
|
||||
if cfg and cfg.ips then
|
||||
for _, entry in ipairs(cfg.ips) do
|
||||
local trimmed = entry:match("^%s*(.-)%s*$")
|
||||
if trimmed ~= "" then
|
||||
if trimmed:sub(1, 1) == "!" then
|
||||
local ip = trimmed:sub(2):match("^%s*(.-)%s*$")
|
||||
if ip ~= "" then
|
||||
table.insert(blacklist, ip)
|
||||
end
|
||||
else
|
||||
table.insert(whitelist, trimmed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_start()
|
||||
if #whitelist == 0 and #blacklist == 0 then
|
||||
return
|
||||
end
|
||||
if #whitelist == 0 and #blacklist == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local result, err = shell_pipe("curl -sf https://api.ipify.org 2>/dev/null")
|
||||
result = result and result:match("^%s*(.-)%s*$") or nil
|
||||
local result, err = shell_pipe("curl -sf https://api.ipify.org 2>/dev/null")
|
||||
result = result and result:match("^%s*(.-)%s*$") or nil
|
||||
|
||||
if err or not result or result == "" then
|
||||
log("could not determine outbound IP, skipping check")
|
||||
notif("IP Filter", "Could not determine outbound IP, skipping check", "warning")
|
||||
return
|
||||
end
|
||||
if err or not result or result == "" then
|
||||
log("could not determine outbound IP, skipping check")
|
||||
notif("IP Filter", "Could not determine outbound IP, skipping check", "warning")
|
||||
return
|
||||
end
|
||||
|
||||
for _, ip in ipairs(blacklist) do
|
||||
if result == ip then
|
||||
notif("IP Filter", "Outbound IP " .. result .. " is blacklisted!", "error")
|
||||
return
|
||||
end
|
||||
end
|
||||
for _, ip in ipairs(blacklist) do
|
||||
if result == ip then
|
||||
notif("IP Filter", "Outbound IP " .. result .. " is blacklisted!", "error")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if #whitelist == 0 then
|
||||
return
|
||||
end
|
||||
if #whitelist == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
for _, ip in ipairs(whitelist) do
|
||||
if result == ip then
|
||||
return
|
||||
end
|
||||
end
|
||||
for _, ip in ipairs(whitelist) do
|
||||
if result == ip then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
notif("IP Filter", "Outbound IP " .. result .. " is not in the whitelist!", "error")
|
||||
notif("IP Filter", "Outbound IP " .. result .. " is not in the whitelist!", "error")
|
||||
end
|
||||
|
||||
+68
-50
@@ -1,6 +1,6 @@
|
||||
Plugin = {
|
||||
name = "Scopes",
|
||||
description = [[
|
||||
name = "Scopes",
|
||||
description = [[
|
||||
Auto-forward requests and exclude them from history based on patterns.
|
||||
|
||||
**Config** (YAML):
|
||||
@@ -34,70 +34,88 @@ patterns:
|
||||
- "h:^$"
|
||||
```
|
||||
]],
|
||||
priority = 100,
|
||||
on_request = { sync = true },
|
||||
on_response = { sync = true },
|
||||
on_history_entry = { sync = true },
|
||||
priority = 100,
|
||||
on_request = { sync = true },
|
||||
on_response = { sync = true },
|
||||
on_history_entry = { sync = true },
|
||||
}
|
||||
|
||||
local blacklist = {}
|
||||
local whitelist = {}
|
||||
local blacklist_req = {}
|
||||
local whitelist_req = {}
|
||||
local blacklist = {}
|
||||
local whitelist = {}
|
||||
local blacklist_req = {}
|
||||
local whitelist_req = {}
|
||||
local blacklist_hist = {}
|
||||
local whitelist_hist = {}
|
||||
|
||||
function on_config()
|
||||
blacklist, whitelist = {}, {}
|
||||
blacklist_req, whitelist_req = {}, {}
|
||||
blacklist_hist, whitelist_hist = {}, {}
|
||||
local cfg = get_config()
|
||||
if not cfg or not cfg.patterns then return end
|
||||
for _, line in ipairs(cfg.patterns) do
|
||||
local trimmed = line:match("^%s*(.-)%s*$")
|
||||
if trimmed ~= "" and trimmed:sub(1, 1) ~= "#" then
|
||||
local scope = trimmed:match("^([rh]):")
|
||||
local rest = scope and trimmed:sub(3) or trimmed
|
||||
local is_bl = rest:sub(1, 1) == "!"
|
||||
local pattern = is_bl and rest:sub(2) or rest
|
||||
if scope == "r" then
|
||||
table.insert(is_bl and blacklist_req or whitelist_req, pattern)
|
||||
elseif scope == "h" then
|
||||
table.insert(is_bl and blacklist_hist or whitelist_hist, pattern)
|
||||
else
|
||||
table.insert(is_bl and blacklist or whitelist, pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
blacklist, whitelist = {}, {}
|
||||
blacklist_req, whitelist_req = {}, {}
|
||||
blacklist_hist, whitelist_hist = {}, {}
|
||||
local cfg = get_config()
|
||||
if not cfg or not cfg.patterns then
|
||||
return
|
||||
end
|
||||
for _, line in ipairs(cfg.patterns) do
|
||||
local trimmed = line:match("^%s*(.-)%s*$")
|
||||
if trimmed ~= "" and trimmed:sub(1, 1) ~= "#" then
|
||||
local scope = trimmed:match("^([rh]):")
|
||||
local rest = scope and trimmed:sub(3) or trimmed
|
||||
local is_bl = rest:sub(1, 1) == "!"
|
||||
local pattern = is_bl and rest:sub(2) or rest
|
||||
if scope == "r" then
|
||||
table.insert(is_bl and blacklist_req or whitelist_req, pattern)
|
||||
elseif scope == "h" then
|
||||
table.insert(is_bl and blacklist_hist or whitelist_hist, pattern)
|
||||
else
|
||||
table.insert(is_bl and blacklist or whitelist, pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function check_skip(url, bl_extra, wl_extra)
|
||||
for _, p in ipairs(blacklist) do
|
||||
if url:match(p) then return true end
|
||||
end
|
||||
for _, p in ipairs(bl_extra) do
|
||||
if url:match(p) then return true end
|
||||
end
|
||||
local wl = {}
|
||||
for _, p in ipairs(whitelist) do wl[#wl + 1] = p end
|
||||
for _, p in ipairs(wl_extra) do wl[#wl + 1] = p end
|
||||
if #wl > 0 then
|
||||
for _, p in ipairs(wl) do
|
||||
if url:match(p) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
for _, p in ipairs(blacklist) do
|
||||
if url:match(p) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
for _, p in ipairs(bl_extra) do
|
||||
if url:match(p) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
local wl = {}
|
||||
for _, p in ipairs(whitelist) do
|
||||
wl[#wl + 1] = p
|
||||
end
|
||||
for _, p in ipairs(wl_extra) do
|
||||
wl[#wl + 1] = p
|
||||
end
|
||||
if #wl > 0 then
|
||||
for _, p in ipairs(wl) do
|
||||
if url:match(p) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function on_request(req)
|
||||
if check_skip(req.url, blacklist_req, whitelist_req) then return "forward" end
|
||||
if check_skip(req.url, blacklist_req, whitelist_req) then
|
||||
return "forward"
|
||||
end
|
||||
end
|
||||
|
||||
function on_response(req)
|
||||
if check_skip(req.url, blacklist_req, whitelist_req) then return "forward" end
|
||||
if check_skip(req.url, blacklist_req, whitelist_req) then
|
||||
return "forward"
|
||||
end
|
||||
end
|
||||
|
||||
function on_history_entry(entry)
|
||||
if check_skip(entry.host .. entry.path, blacklist_hist, whitelist_hist) then return "skip" end
|
||||
if check_skip(entry.host .. entry.path, blacklist_hist, whitelist_hist) then
|
||||
return "skip"
|
||||
end
|
||||
end
|
||||
|
||||
+166
-120
@@ -1,70 +1,91 @@
|
||||
Plugin = {
|
||||
name = "Secret Scan",
|
||||
description = [[
|
||||
name = "Secret Scan",
|
||||
description = [[
|
||||
Scans HTML, JavaScript and JSON content (requests and responses) for hardcoded
|
||||
secrets by matching common secret key names followed by a non-trivial value.
|
||||
|
||||
Uses `grep -E` (available on all Unix systems, no extra dependencies).
|
||||
]],
|
||||
on_request = { sync = false },
|
||||
on_response = { sync = false },
|
||||
disable_by_default = true,
|
||||
on_request = { sync = false },
|
||||
on_response = { sync = false },
|
||||
disable_by_default = true,
|
||||
}
|
||||
|
||||
local CONTENT_TYPES = {
|
||||
"text/html",
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/json",
|
||||
"text/html",
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/json",
|
||||
}
|
||||
|
||||
-- Key name alternation (case-insensitive via grep -i)
|
||||
-- Suffixes are required (no bare generic keyword alone).
|
||||
local KEYS = {
|
||||
"access(_key|_token)", "accessid_secret", "account(_key|_sid)",
|
||||
"admin_pass(word)?", "admin_user",
|
||||
"(algolia|aws|gcp|azure|heroku|firebase|github|gitlab|slack|datadog|stripe|twilio|vercel|supabase|sendgrid|cloudinary|cloudflare|bitbucket|npm|netlify|auth0|okta|sentry)(_?(api|secret|access)(_?(key|token|id|sid|secret))?|_?(key|token|id|sid|secret))",
|
||||
"ansible_vault_password", "aos_key",
|
||||
"api(_key|_secret|_token)",
|
||||
"app_(id|key|secret)", "application(_key|_id|_secret)",
|
||||
"auth(_token|_secret|orization)", "authkey", "authsecret",
|
||||
"bearer_?token",
|
||||
"bucket(_password|_key)",
|
||||
"cert_?pass(word)?", "certificate_password",
|
||||
"client(_id|_secret)",
|
||||
"codecov_token", "consumer_(key|secret)",
|
||||
"connection_?string", "credentials?", "crypt(_key|_secret)",
|
||||
"db_(password|passwd|user(name)?)",
|
||||
"deploy(_key|_password|_token)",
|
||||
"docker_?pass(word)?", "dockerhub_?password",
|
||||
"encryption_(key|password)",
|
||||
"jwt_secret", "json_web_token",
|
||||
"keycloak_secret", "kubernetes_token",
|
||||
"ldap_(password|bindpw)", "login(_password|_token)",
|
||||
"mail_?password", "mail_smtp_pass",
|
||||
"mysql_password", "mongo_password",
|
||||
"netlify_token", "npm(_token|_auth_token)",
|
||||
"oauth(_token|_secret)",
|
||||
"openai_(api_key|secret)",
|
||||
"pass(word)?", "passwd",
|
||||
"private(_key|_token)",
|
||||
"rds_password",
|
||||
"s3(_key|_secret|_access_key_id)",
|
||||
"secret(_key|_token|_id)", "security_token",
|
||||
"sendgrid_api_key",
|
||||
"ses_(smtp|access|secret)",
|
||||
"service(_account|_key|_token)",
|
||||
"smtp_pass(word)?", "smtp_secret",
|
||||
"sonar_token",
|
||||
"ssh(_key|_private_key|_rsa)",
|
||||
"supabase(_anon|_service)?_key",
|
||||
"symfony_secret",
|
||||
"telegram_bot_token",
|
||||
"token",
|
||||
"travis_token",
|
||||
"vault(_token|_secret)",
|
||||
"webhook(_secret|_token)",
|
||||
"zapier_webhook_token",
|
||||
"access(_key|_token)",
|
||||
"accessid_secret",
|
||||
"account(_key|_sid)",
|
||||
"admin_pass(word)?",
|
||||
"admin_user",
|
||||
"(algolia|aws|gcp|azure|heroku|firebase|github|gitlab|slack|datadog|stripe|twilio|vercel|supabase|sendgrid|cloudinary|cloudflare|bitbucket|npm|netlify|auth0|okta|sentry)(_?(api|secret|access)(_?(key|token|id|sid|secret))?|_?(key|token|id|sid|secret))",
|
||||
"ansible_vault_password",
|
||||
"aos_key",
|
||||
"api(_key|_secret|_token)",
|
||||
"app_(id|key|secret)",
|
||||
"application(_key|_id|_secret)",
|
||||
"auth(_token|_secret|orization)",
|
||||
"authkey",
|
||||
"authsecret",
|
||||
"bearer_?token",
|
||||
"bucket(_password|_key)",
|
||||
"cert_?pass(word)?",
|
||||
"certificate_password",
|
||||
"client(_id|_secret)",
|
||||
"codecov_token",
|
||||
"consumer_(key|secret)",
|
||||
"connection_?string",
|
||||
"credentials?",
|
||||
"crypt(_key|_secret)",
|
||||
"db_(password|passwd|user(name)?)",
|
||||
"deploy(_key|_password|_token)",
|
||||
"docker_?pass(word)?",
|
||||
"dockerhub_?password",
|
||||
"encryption_(key|password)",
|
||||
"jwt_secret",
|
||||
"json_web_token",
|
||||
"keycloak_secret",
|
||||
"kubernetes_token",
|
||||
"ldap_(password|bindpw)",
|
||||
"login(_password|_token)",
|
||||
"mail_?password",
|
||||
"mail_smtp_pass",
|
||||
"mysql_password",
|
||||
"mongo_password",
|
||||
"netlify_token",
|
||||
"npm(_token|_auth_token)",
|
||||
"oauth(_token|_secret)",
|
||||
"openai_(api_key|secret)",
|
||||
"pass(word)?",
|
||||
"passwd",
|
||||
"private(_key|_token)",
|
||||
"rds_password",
|
||||
"s3(_key|_secret|_access_key_id)",
|
||||
"secret(_key|_token|_id)",
|
||||
"security_token",
|
||||
"sendgrid_api_key",
|
||||
"ses_(smtp|access|secret)",
|
||||
"service(_account|_key|_token)",
|
||||
"smtp_pass(word)?",
|
||||
"smtp_secret",
|
||||
"sonar_token",
|
||||
"ssh(_key|_private_key|_rsa)",
|
||||
"supabase(_anon|_service)?_key",
|
||||
"symfony_secret",
|
||||
"telegram_bot_token",
|
||||
"token",
|
||||
"travis_token",
|
||||
"vault(_token|_secret)",
|
||||
"webhook(_secret|_token)",
|
||||
"zapier_webhook_token",
|
||||
}
|
||||
|
||||
-- Built once at load time.
|
||||
@@ -74,93 +95,118 @@ local KEYS = {
|
||||
-- [[:space:]]*[:=] REQUIRED: actual = or : assignment operator
|
||||
-- [[:space:]]*"? optional whitespace + opening quote
|
||||
-- [a-zA-Z0-9+/=_.-]{8,} the secret value, at least 8 chars
|
||||
local KEY_PAT = "(" .. table.concat(KEYS, "|") .. ")"
|
||||
local KEY_PAT = "(" .. table.concat(KEYS, "|") .. ")"
|
||||
local FULL_PAT = KEY_PAT .. '[a-z0-9._-]{0,20}[^=:a-zA-Z0-9_]{0,3}[[:space:]]*[:=][[:space:]]*"?[a-zA-Z0-9+/=_.-]{8,}'
|
||||
local GREP_CMD = "grep -Eoni '" .. FULL_PAT .. "'"
|
||||
|
||||
local function is_relevant(ct)
|
||||
if not ct or ct == "" then return false end
|
||||
ct = ct:lower()
|
||||
for _, t in ipairs(CONTENT_TYPES) do
|
||||
if ct:find(t, 1, true) then return true end
|
||||
end
|
||||
return false
|
||||
if not ct or ct == "" then
|
||||
return false
|
||||
end
|
||||
ct = ct:lower()
|
||||
for _, t in ipairs(CONTENT_TYPES) do
|
||||
if ct:find(t, 1, true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function build_context(lines, linenum)
|
||||
local lo = math.max(1, linenum - 6)
|
||||
local hi = math.min(#lines, linenum + 6)
|
||||
local lo = math.max(1, linenum - 6)
|
||||
local hi = math.min(#lines, linenum + 6)
|
||||
|
||||
local before, after = {}, {}
|
||||
for i = lo, linenum - 1 do
|
||||
local l = lines[i] or ""
|
||||
if #l > 120 then l = l:sub(1, 120) .. "..." end
|
||||
table.insert(before, l)
|
||||
end
|
||||
for i = linenum + 1, hi do
|
||||
local l = lines[i] or ""
|
||||
if #l > 120 then l = l:sub(1, 120) .. "..." end
|
||||
table.insert(after, l)
|
||||
end
|
||||
local before, after = {}, {}
|
||||
for i = lo, linenum - 1 do
|
||||
local l = lines[i] or ""
|
||||
if #l > 120 then
|
||||
l = l:sub(1, 120) .. "..."
|
||||
end
|
||||
table.insert(before, l)
|
||||
end
|
||||
for i = linenum + 1, hi do
|
||||
local l = lines[i] or ""
|
||||
if #l > 120 then
|
||||
l = l:sub(1, 120) .. "..."
|
||||
end
|
||||
table.insert(after, l)
|
||||
end
|
||||
|
||||
local matched_line = lines[linenum] or ""
|
||||
if #matched_line > 200 then matched_line = matched_line:sub(1, 200) .. "..." end
|
||||
local matched_line = lines[linenum] or ""
|
||||
if #matched_line > 200 then
|
||||
matched_line = matched_line:sub(1, 200) .. "..."
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
if #before > 0 then
|
||||
table.insert(parts, "```\n" .. table.concat(before, "\n") .. "\n```")
|
||||
end
|
||||
table.insert(parts, "> **`" .. matched_line .. "`**")
|
||||
if #after > 0 then
|
||||
table.insert(parts, "```\n" .. table.concat(after, "\n") .. "\n```")
|
||||
end
|
||||
return table.concat(parts, "\n\n")
|
||||
local parts = {}
|
||||
if #before > 0 then
|
||||
table.insert(parts, "```\n" .. table.concat(before, "\n") .. "\n```")
|
||||
end
|
||||
table.insert(parts, "> **`" .. matched_line .. "`**")
|
||||
if #after > 0 then
|
||||
table.insert(parts, "```\n" .. table.concat(after, "\n") .. "\n```")
|
||||
end
|
||||
return table.concat(parts, "\n\n")
|
||||
end
|
||||
|
||||
local function scan(label, ct, body, host, path)
|
||||
if not is_relevant(ct) then return end
|
||||
if not body or body == "" then return end
|
||||
if not is_relevant(ct) then
|
||||
return
|
||||
end
|
||||
if not body or body == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local out, err = shell_pipe(GREP_CMD, body)
|
||||
if err and err ~= "" then
|
||||
log("grep error on " .. label .. " for " .. host .. path .. ": " .. err)
|
||||
return
|
||||
end
|
||||
if not out or out == "" then return end
|
||||
local out, err = shell_pipe(GREP_CMD, body)
|
||||
if err and err ~= "" then
|
||||
log("grep error on " .. label .. " for " .. host .. path .. ": " .. err)
|
||||
return
|
||||
end
|
||||
if not out or out == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for line in (body .. "\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
local lines = {}
|
||||
for line in (body .. "\n"):gmatch("([^\n]*)\n") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
for entry in out:gmatch("[^\n]+") do
|
||||
local linenum_str, matched = entry:match("^(%d+):(.+)$")
|
||||
if linenum_str then
|
||||
local linenum = tonumber(linenum_str)
|
||||
matched = matched:match("^%s*(.-)%s*$")
|
||||
if matched ~= "" then
|
||||
local display = matched
|
||||
if #display > 200 then display = display:sub(1, 200) .. "..." end
|
||||
local ctx = build_context(lines, linenum)
|
||||
create_finding({
|
||||
title = "Potential secret in " .. label .. " (" .. host .. ")",
|
||||
description = "**Host:** `" .. host .. "` \n**Path:** `" .. path .. "`\n\n**Match:** `" .. display .. "`\n\n" .. ctx,
|
||||
key = host .. "|" .. path .. "|" .. label .. "|" .. matched,
|
||||
severity = "high",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
for entry in out:gmatch("[^\n]+") do
|
||||
local linenum_str, matched = entry:match("^(%d+):(.+)$")
|
||||
if linenum_str then
|
||||
local linenum = tonumber(linenum_str)
|
||||
matched = matched:match("^%s*(.-)%s*$")
|
||||
if matched ~= "" then
|
||||
local display = matched
|
||||
if #display > 200 then
|
||||
display = display:sub(1, 200) .. "..."
|
||||
end
|
||||
local ctx = build_context(lines, linenum)
|
||||
create_finding({
|
||||
title = "Potential secret in " .. label .. " (" .. host .. ")",
|
||||
description = "**Host:** `"
|
||||
.. host
|
||||
.. "` \n**Path:** `"
|
||||
.. path
|
||||
.. "`\n\n**Match:** `"
|
||||
.. display
|
||||
.. "`\n\n"
|
||||
.. ctx,
|
||||
key = host .. "|" .. path .. "|" .. label .. "|" .. matched,
|
||||
severity = "high",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_request(req)
|
||||
scan("request", req.headers["Content-Type"] or "", req:get_body(), req.host, req.path)
|
||||
scan("request", req.headers["Content-Type"] or "", req:get_body(), req.host, req.path)
|
||||
end
|
||||
|
||||
function on_response(req, res)
|
||||
local ct = ""
|
||||
if res.headers then
|
||||
ct = res.headers["Content-Type"] or ""
|
||||
end
|
||||
scan("response", ct, res:get_body(), req.host, req.path)
|
||||
local ct = ""
|
||||
if res.headers then
|
||||
ct = res.headers["Content-Type"] or ""
|
||||
end
|
||||
scan("response", ct, res:get_body(), req.host, req.path)
|
||||
end
|
||||
|
||||
+51
-40
@@ -1,6 +1,6 @@
|
||||
Plugin = {
|
||||
name = "TruffleHog",
|
||||
description = [[
|
||||
name = "TruffleHog",
|
||||
description = [[
|
||||
Scans request and response bodies for secrets using [TruffleHog](https://github.com/trufflesecurity/trufflehog).
|
||||
|
||||
Requires `trufflehog` v3+ to be installed and available in PATH.
|
||||
@@ -8,54 +8,65 @@ Requires `trufflehog` v3+ to be installed and available in PATH.
|
||||
Each finding is stored on the **Findings** page with the matched detector output.
|
||||
Findings are deduplicated per host+path+body content so repeated requests do not create duplicates.
|
||||
]],
|
||||
on_start = { sync = false },
|
||||
on_request = { sync = false },
|
||||
on_response = { sync = false },
|
||||
disable_by_default = true,
|
||||
on_start = { sync = false },
|
||||
on_request = { sync = false },
|
||||
on_response = { sync = false },
|
||||
disable_by_default = true,
|
||||
}
|
||||
|
||||
function on_start()
|
||||
local result, _ = shell_pipe("command -v trufflehog 2>/dev/null")
|
||||
if not result or result:match("^%s*$") then
|
||||
log("trufflehog is not installed or not in PATH")
|
||||
notif("TruffleHog", "trufflehog is not installed or not in PATH, plugin disabled", "error")
|
||||
return false
|
||||
end
|
||||
local result, _ = shell_pipe("command -v trufflehog 2>/dev/null")
|
||||
if not result or result:match("^%s*$") then
|
||||
log("trufflehog is not installed or not in PATH")
|
||||
notif("TruffleHog", "trufflehog is not installed or not in PATH, plugin disabled", "error")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function scan(label, content, host, path)
|
||||
if not content or content == "" then return end
|
||||
local out, err = shell_pipe("f=$(mktemp) && cat > \"$f\" && trufflehog filesystem --no-color \"$f\"; rc=$?; rm -f \"$f\"; exit $rc", content)
|
||||
if err and err ~= "" then
|
||||
log("trufflehog error on " .. label .. ": " .. err)
|
||||
return
|
||||
end
|
||||
if not out or out == "" then return end
|
||||
local blocks = {}
|
||||
local current = nil
|
||||
for line in out:gmatch("[^\n]+") do
|
||||
if line:match("^Found ") then
|
||||
if current then table.insert(blocks, current) end
|
||||
current = line
|
||||
elseif current then
|
||||
current = current .. "\n" .. line
|
||||
end
|
||||
end
|
||||
if current then table.insert(blocks, current) end
|
||||
for _, block in ipairs(blocks) do
|
||||
create_finding({
|
||||
title = "Secret detected in " .. label .. " (" .. host .. ")",
|
||||
description = "**Host:** `" .. host .. "` \n**Path:** `" .. path .. "`\n\n```\n" .. block .. "\n```",
|
||||
key = host .. "|" .. path .. "|" .. label .. "|" .. block,
|
||||
severity = "high",
|
||||
})
|
||||
end
|
||||
if not content or content == "" then
|
||||
return
|
||||
end
|
||||
local out, err = shell_pipe(
|
||||
'f=$(mktemp) && cat > "$f" && trufflehog filesystem --no-color "$f"; rc=$?; rm -f "$f"; exit $rc',
|
||||
content
|
||||
)
|
||||
if err and err ~= "" then
|
||||
log("trufflehog error on " .. label .. ": " .. err)
|
||||
return
|
||||
end
|
||||
if not out or out == "" then
|
||||
return
|
||||
end
|
||||
local blocks = {}
|
||||
local current = nil
|
||||
for line in out:gmatch("[^\n]+") do
|
||||
if line:match("^Found ") then
|
||||
if current then
|
||||
table.insert(blocks, current)
|
||||
end
|
||||
current = line
|
||||
elseif current then
|
||||
current = current .. "\n" .. line
|
||||
end
|
||||
end
|
||||
if current then
|
||||
table.insert(blocks, current)
|
||||
end
|
||||
for _, block in ipairs(blocks) do
|
||||
create_finding({
|
||||
title = "Secret detected in " .. label .. " (" .. host .. ")",
|
||||
description = "**Host:** `" .. host .. "` \n**Path:** `" .. path .. "`\n\n```\n" .. block .. "\n```",
|
||||
key = host .. "|" .. path .. "|" .. label .. "|" .. block,
|
||||
severity = "high",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function on_request(req)
|
||||
scan("request", req:get_body(), req.host, req.path)
|
||||
scan("request", req:get_body(), req.host, req.path)
|
||||
end
|
||||
|
||||
function on_response(req, res)
|
||||
scan("response", res:get_body(), req.host, req.path)
|
||||
scan("response", res:get_body(), req.host, req.path)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user