Packages
sobelow
0.13.0
0.14.1
0.14.0
0.13.0
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
Security-focused static analysis for Elixir & the Phoenix framework
Current section
Files
Jump to
Current section
Files
lib/sobelow/print.ex
defmodule Sobelow.Print do
@moduledoc false
alias Sobelow.{Finding, Parse}
def add_finding(%Finding{} = finding) do
finding = Finding.fetch_fingerprint(finding)
case Sobelow.format() do
"json" ->
log_json_finding(finding)
"txt" ->
Sobelow.log_finding(finding)
print_finding_metadata(finding)
"compact" ->
log_compact_finding(finding)
"flycheck" ->
log_flycheck_finding(finding)
_ ->
Sobelow.log_finding(finding)
end
end
def print_finding_metadata(%Finding{} = finding) do
if Sobelow.loggable?(finding.fingerprint, finding.confidence) do
do_print_finding_metadata(finding)
end
end
def do_print_finding_metadata(%Finding{} = finding) do
IO.puts(finding_header(finding.type, finding.confidence))
IO.puts(finding_file_name(finding.filename))
IO.puts(finding_line(finding.vuln_line_no))
maybe_print_finding_fun_metadata(finding.fun_name, finding.fun_line_no)
IO.puts(finding_variable(finding.vuln_variable))
maybe_print_code(finding.fun_source, finding.vuln_source)
IO.puts(finding_break())
end
def print_custom_finding_metadata(%Finding{} = finding, headers) do
if Sobelow.loggable?(finding.fingerprint, finding.confidence) do
do_print_custom_finding_metadata(finding, headers)
end
end
def do_print_custom_finding_metadata(%Finding{} = finding, headers) do
IO.puts(finding_header(finding.type, finding.confidence))
Enum.each(headers, fn header ->
IO.puts(header)
end)
maybe_print_code(finding.fun_source, finding.vuln_source)
IO.puts(finding_break())
end
def log_compact_finding(%Finding{} = finding) do
details = "#{finding.type} - #{finding.filename}:#{finding.vuln_line_no}"
Sobelow.log_finding(%{finding | type: details})
print_compact_finding(finding, details)
end
defp print_compact_finding(finding, details) do
if Sobelow.loggable?(finding.fingerprint, finding.confidence) do
do_print_compact_finding(details, finding.confidence)
end
end
defp do_print_compact_finding(details, severity) do
sev =
case severity do
:high -> IO.ANSI.red()
:medium -> IO.ANSI.yellow()
:low -> IO.ANSI.green()
end
IO.puts("#{sev}[+]#{IO.ANSI.reset()} #{details}")
end
def log_flycheck_finding(%Finding{} = finding) do
details = "#{finding.filename}:#{finding.vuln_line_no}: #{finding.type}"
Sobelow.log_finding(%{finding | type: details})
print_flycheck_finding(finding, details)
end
defp print_flycheck_finding(finding, details) do
if Sobelow.loggable?(finding.fingerprint, finding.confidence) do
do_print_flycheck_finding(details, finding.confidence)
end
end
defp do_print_flycheck_finding(details, _confidence) do
IO.puts(details)
end
def log_json_finding(%Finding{} = finding) do
json_finding = [
type: finding.type,
file: finding.filename,
line: finding.vuln_line_no,
variable: finding.vuln_variable
]
Sobelow.log_finding(json_finding, finding)
end
def finding_header(type, severity) do
{color, confidence} = finding_confidence(severity)
color <> type <> " - #{confidence} Confidence" <> IO.ANSI.reset()
end
def finding_file_name(filename) do
"File: #{filename}"
end
def finding_line(line_no) when is_integer(line_no) do
"Line: #{line_no}"
end
def finding_line(finding) do
"Line: #{Parse.get_fun_line(finding)}"
end
def maybe_print_finding_fun_metadata("", _), do: nil
def maybe_print_finding_fun_metadata(fun_name, line_no),
do: print_finding_fun_metadata(fun_name, line_no)
def print_finding_fun_metadata({:unquote, _, _} = fun_name, line_no) do
print_finding_fun_metadata(Macro.to_string(fun_name), line_no)
end
def print_finding_fun_metadata(fun_name, line_no) do
finding_fun_metadata(fun_name, line_no) |> IO.puts()
end
def finding_fun_metadata(fun_name, line_no) do
"Function: #{fun_name}:#{line_no}"
end
def finding_variable({_, _, _} = var) do
{var, [], []} |> Macro.to_string() |> finding_variable()
end
def finding_variable(var) do
"Variable: #{var}"
end
def finding_confidence(severity) do
case severity do
:high -> {IO.ANSI.red(), "High"}
:medium -> {IO.ANSI.yellow(), "Medium"}
:low -> {IO.ANSI.green(), "Low"}
end
end
def finding_break do
"\n-----------------------------------------------\n"
end
def maybe_print_code(fun, finding) do
if Sobelow.get_env(:verbose), do: print_code(fun, finding)
end
def get_sev(params, var) do
do_get_sev(params, var, :medium, :low)
end
def get_sev(params, vars, severity) when is_list(vars) do
Enum.map(vars, &get_sev(params, &1, severity))
|> get_highest_sev()
end
def get_sev(params, var, false) do
do_get_sev(params, var, :high, :medium)
end
def get_sev(params, var, nil) do
do_get_sev(params, var, :high, :medium)
end
def get_sev(_params, _var, severity) do
severity
end
defp do_get_sev(params, var, high, low) do
params = ["conn.params" | params]
case Enum.member?(params, var) do
true -> high
false -> low
end
end
defp get_highest_sev(sevs) do
cond do
Enum.member?(sevs, :high) -> :high
Enum.member?(sevs, :medium) -> :medium
true -> :low
end
end
def print_code(nil, nil), do: nil
def print_code(nil, out) when is_binary(out) do
IO.puts("\n")
IO.puts(out)
end
def print_code(fun, :highlight_all) do
IO.puts("\n")
IO.puts(IO.ANSI.light_magenta() <> Macro.to_string(fun) <> IO.ANSI.reset())
end
def print_code(fun, find) do
{ast, highlights} = highlight_variables(fun, find)
code_string = Macro.to_string(ast)
highlighted_code = add_highlights(code_string, highlights)
IO.puts("\n")
IO.puts(highlighted_code)
end
defp highlight_variables(fun, find) do
{new_ast, highlights} =
Macro.prewalk(fun, %{}, fn ast, acc ->
case ast do
^find ->
highlight_key = make_highlight_key(acc)
updated_acc = Map.put(acc, highlight_key, ast)
{String.to_atom(highlight_key), updated_acc}
# normalizes template variables
{{_, _, [{_, _, [:EEx, :Engine]}, _]}, _, [{:var!, _, [{:assigns, _, _}]}, key]} ->
{"@#{key}", acc}
_ ->
{ast, acc}
end
end)
{new_ast, highlights}
end
defp add_highlights(code_string, highlights) do
Enum.reduce(highlights, code_string, fn {highlight_key, ast}, acc ->
highlighted = IO.ANSI.light_magenta() <> Macro.to_string(ast) <> IO.ANSI.reset()
String.replace(acc, ":" <> highlight_key, highlighted)
end)
end
defp make_highlight_key(acc) do
"__sobelow_highlight_#{map_size(acc)}__"
end
end