From 44b3c67a37d49d3f37a941b2fcc8dc3e25556d8c Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Wed, 20 May 2026 19:31:31 +0200 Subject: [PATCH] +stylua Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- nix/shell.nix | 2 + plugins/inject_header.lua | 32 ++--- plugins/ip_filter.lua | 90 ++++++------ plugins/scopes.lua | 118 +++++++++------- plugins/secretscan.lua | 286 ++++++++++++++++++++++---------------- plugins/trufflehog.lua | 91 ++++++------ 6 files changed, 349 insertions(+), 270 deletions(-) diff --git a/nix/shell.nix b/nix/shell.nix index 388dcb3..1031208 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -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 ] diff --git a/plugins/inject_header.lua b/plugins/inject_header.lua index c8f70e2..3eef4eb 100644 --- a/plugins/inject_header.lua +++ b/plugins/inject_header.lua @@ -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 diff --git a/plugins/ip_filter.lua b/plugins/ip_filter.lua index b5669fc..8f07d41 100644 --- a/plugins/ip_filter.lua +++ b/plugins/ip_filter.lua @@ -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 diff --git a/plugins/scopes.lua b/plugins/scopes.lua index 86b5a74..5f985be 100644 --- a/plugins/scopes.lua +++ b/plugins/scopes.lua @@ -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 diff --git a/plugins/secretscan.lua b/plugins/secretscan.lua index afbf87c..db5509f 100644 --- a/plugins/secretscan.lua +++ b/plugins/secretscan.lua @@ -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 diff --git a/plugins/trufflehog.lua b/plugins/trufflehog.lua index d67d68c..ee25cab 100644 --- a/plugins/trufflehog.lua +++ b/plugins/trufflehog.lua @@ -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