use tldr_core::{Language, TaintInfo, TaintSinkType, TaintSourceType};
#[path = "common/integ_helpers.rs"]
mod common;
use common::{analyze_ast_only, analyze_with_ssa};
fn assert_has_source_of_type(result: &TaintInfo, expected: TaintSourceType, path: &str) {
let lines: Vec<u32> = result
.sources
.iter()
.filter(|s| s.source_type == expected)
.map(|s| s.line)
.collect();
assert!(
!lines.is_empty(),
"[{path}] expected at least one {:?} source; got sources={:?}",
expected,
result.sources
);
}
fn assert_has_sink_of_type(result: &TaintInfo, expected: TaintSinkType, path: &str) {
let lines: Vec<u32> = result
.sinks
.iter()
.filter(|s| s.sink_type == expected)
.map(|s| s.line)
.collect();
assert!(
!lines.is_empty(),
"[{path}] expected at least one {:?} sink; got sinks={:?}",
expected,
result.sinks
);
}
fn assert_has_source_or_sink(result: &TaintInfo, source: TaintSourceType, sink: TaintSinkType, path: &str) {
assert_has_source_of_type(result, source, path);
assert_has_sink_of_type(result, sink, path);
}
#[test]
fn ruby_stdin_read_to_eval_via_compute_taint() {
let src = "\
def handler
cmd = STDIN.read
eval(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_or_sink(®ular, TaintSourceType::Stdin, TaintSinkType::CodeEval, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::Stdin, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::CodeEval, "ast_only");
}
#[test]
fn ruby_stdin_gets_to_system_via_compute_taint() {
let src = "\
def handler
cmd = STDIN.gets
system(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::Stdin, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::Stdin, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ruby_stdin_readline_to_io_popen_via_compute_taint() {
let src = "\
def handler
cmd = STDIN.readline
IO.popen(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::Stdin, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::Stdin, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ruby_file_read_to_eval_via_compute_taint() {
let src = "\
def handler
cmd = File.read(\"path\")
eval(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::CodeEval, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::CodeEval, "ast_only");
}
#[test]
fn ruby_file_open_to_system_via_compute_taint() {
let src = "\
def handler
cmd = File.open(\"path\")
system(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ruby_io_popen_with_user_input_via_compute_taint() {
let src = "\
def handler
cmd = gets
IO.popen(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn elixir_io_gets_to_system_cmd_via_compute_taint() {
let src = "\
def handler do
cmd = IO.gets(\"> \")
System.cmd(cmd, [])
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn elixir_system_get_env_to_code_eval_via_compute_taint() {
let src = "\
def handler do
name = System.get_env(\"X\")
Code.eval_string(name)
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::EnvVar, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::CodeEval, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::EnvVar, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::CodeEval, "ast_only");
}
#[test]
fn elixir_file_read_to_system_cmd_via_compute_taint() {
let src = "\
def handler do
cmd = File.read(\"path\")
System.cmd(cmd, [])
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn elixir_file_read_bang_to_system_cmd_via_compute_taint() {
let src = "\
def handler do
cmd = File.read!(\"path\")
System.cmd(cmd, [])
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn elixir_user_input_to_code_eval_string_via_compute_taint() {
let src = "\
def handler do
cmd = IO.gets(\"> \")
Code.eval_string(cmd)
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::CodeEval, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::CodeEval, "ast_only");
}
#[test]
fn elixir_user_input_to_ecto_sql_query_via_compute_taint() {
let src = "\
def handler(repo) do
cmd = IO.gets(\"> \")
Ecto.Adapters.SQL.query(repo, cmd, [])
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::SqlQuery, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::SqlQuery, "ast_only");
}
#[test]
fn elixir_pipe_operator_io_gets_to_system_cmd_via_compute_taint() {
let src = "\
def handler do
IO.gets(\"> \") |> System.cmd([])
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ocaml_sys_getenv_to_sys_command_via_compute_taint() {
let src = "\
let handler () =
let cmd = Sys.getenv \"CMD\" in
ignore (Sys.command cmd)
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::EnvVar, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::EnvVar, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ocaml_in_channel_read_all_to_sys_command_via_compute_taint() {
let src = "\
let handler path =
let cmd = In_channel.read_all path in
ignore (Sys.command cmd)
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ocaml_in_channel_input_all_to_unix_execvp_via_compute_taint() {
let src = "\
let handler ic =
let cmd = In_channel.input_all ic in
Unix.execvp cmd [||]
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::FileRead, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::FileRead, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ocaml_read_line_to_sys_command_via_compute_taint() {
let src = "\
let handler () =
let cmd = read_line () in
ignore (Sys.command cmd)
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::ShellExec, "regular");
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::ShellExec, "ast_only");
}
#[test]
fn ocaml_user_input_to_sqlite3_exec_via_compute_taint() {
let src = "\
let handler db =
let cmd = read_line () in
ignore (Sqlite3.exec db cmd)
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert_has_source_of_type(®ular, TaintSourceType::UserInput, "regular");
assert_has_sink_of_type(®ular, TaintSinkType::SqlQuery, "regular");
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert_has_source_of_type(&ast_only, TaintSourceType::UserInput, "ast_only");
assert_has_sink_of_type(&ast_only, TaintSinkType::SqlQuery, "ast_only");
}
#[test]
fn ruby_string_literal_io_popen_substring_zero_findings() {
let src = "\
def handler
msg = \"do not run IO.popen here\"
puts msg
end
";
let regular = analyze_with_ssa(src, Language::Ruby, "handler", false);
assert!(
regular.sources.is_empty(),
"[regular] expected ZERO sources for string-literal IO.popen substring; got sources={:?}",
regular.sources
);
assert!(
!regular.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[regular] expected ZERO ShellExec sinks for string-literal IO.popen substring; got sinks={:?}",
regular.sinks
);
let ast_only = analyze_ast_only(src, Language::Ruby, "handler");
assert!(
ast_only.sources.is_empty(),
"[ast_only] expected ZERO sources for string-literal IO.popen substring; got sources={:?}",
ast_only.sources
);
assert!(
!ast_only.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[ast_only] expected ZERO ShellExec sinks for string-literal IO.popen substring; got sinks={:?}",
ast_only.sinks
);
}
#[test]
fn elixir_string_literal_system_cmd_substring_zero_findings() {
let src = "\
def handler do
msg = \"System.cmd is dangerous\"
IO.puts(msg)
end
";
let regular = analyze_with_ssa(src, Language::Elixir, "handler", false);
assert!(
!regular.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[regular] expected ZERO ShellExec sinks for string-literal System.cmd substring; got sinks={:?}",
regular.sinks
);
let ast_only = analyze_ast_only(src, Language::Elixir, "handler");
assert!(
!ast_only.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[ast_only] expected ZERO ShellExec sinks for string-literal System.cmd substring; got sinks={:?}",
ast_only.sinks
);
}
#[test]
fn ocaml_string_literal_sys_command_substring_zero_findings() {
let src = "\
let handler () =
let msg = \"Sys.command is dangerous\" in
print_endline msg
";
let regular = analyze_with_ssa(src, Language::Ocaml, "handler", false);
assert!(
!regular.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[regular] expected ZERO ShellExec sinks for string-literal Sys.command substring; got sinks={:?}",
regular.sinks
);
let ast_only = analyze_ast_only(src, Language::Ocaml, "handler");
assert!(
!ast_only.sinks.iter().any(|s| matches!(s.sink_type, TaintSinkType::ShellExec)),
"[ast_only] expected ZERO ShellExec sinks for string-literal Sys.command substring; got sinks={:?}",
ast_only.sinks
);
}