use super::*;
fn path_step(
caller: &str,
callee: &str,
file: &str,
line: u32,
column: u32,
args: Vec<(usize, &str, &str)>,
) -> TaintPropagationStep {
TaintPropagationStep {
storage_transfer: None,
caller: caller.to_string(),
callee: callee.to_string(),
file: file.to_string(),
line,
column,
tainted_args: args
.into_iter()
.map(|(index, value_text, param_name)| TaintPropagationArg {
index,
value_text: value_text.to_string(),
param_name: param_name.to_string(),
})
.collect(),
}
}
fn sink_match(file: &str, line: u32, column: u32, text: &str, enclosing_fn: &str) -> RuleMatch {
RuleMatch {
origin: MatchOrigin::Rulepack,
rule_id: "typescript.test.sink".to_string(),
language: "typescript".to_string(),
file: file.to_string(),
line,
column,
span: Span::new(bonsai_common::FileId::new(0), 0, 1),
match_text: text.to_string(),
enclosing_fn: Some(enclosing_fn.to_string()),
}
}
#[test]
fn normalize_taint_path_collapses_adjacent_duplicate_report_sites() {
let normalized = normalize_taint_path(vec![
path_step(
"route",
"service",
"src/server.js",
10,
12,
vec![(0, "q", "query")],
),
path_step(
"service",
"renderResults",
"src/server.js",
10,
28,
vec![(0, "q", "")],
),
]);
assert_eq!(normalized.len(), 1);
assert_eq!(normalized[0].caller, "route");
assert_eq!(normalized[0].callee, "renderResults");
assert_eq!(normalized[0].file, "src/server.js");
assert_eq!(normalized[0].line, 10);
assert_eq!(normalized[0].tainted_args.len(), 2);
}
#[test]
fn normalize_taint_path_collapses_all_same_location_chain() {
let normalized = normalize_taint_path(vec![
path_step(
"resource",
"loader",
"SafeYaml.java",
11,
4,
vec![(0, "body", "body")],
),
path_step(
"loader",
"YAML.load",
"SafeYaml.java",
11,
14,
vec![(0, "body", "")],
),
path_step(
"YAML.load",
"YAML.load",
"SafeYaml.java",
11,
14,
vec![(0, "body", "")],
),
]);
assert_eq!(normalized.len(), 1);
assert_eq!(normalized[0].caller, "resource");
assert_eq!(normalized[0].callee, "YAML.load");
assert_eq!(normalized[0].tainted_args.len(), 2);
}
#[test]
fn normalize_taint_path_preserves_distinct_report_lines() {
let normalized = normalize_taint_path(vec![
path_step("route", "service", "src/server.js", 7, 18, vec![(0, "q", "q")]),
path_step(
"service",
"renderResults",
"src/render.js",
3,
9,
vec![(0, "q", "")],
),
]);
assert_eq!(normalized.len(), 2);
assert_eq!(normalized[0].callee, "service");
assert_eq!(normalized[1].callee, "renderResults");
}
#[test]
fn normalize_taint_path_does_not_relabel_a_call_as_a_self_call_on_same_line_return() {
let normalized = normalize_taint_path(vec![
path_step(
"factory",
"Record",
"lib/record.dart",
6,
12,
vec![(0, "fields['name']", "name"), (1, "fields['count']", "count")],
),
path_step("Record", "factory", "lib/record.dart", 6, 12, Vec::new()),
]);
assert_eq!(normalized.len(), 1);
assert_eq!(normalized[0].caller, "factory");
assert_eq!(normalized[0].callee, "Record");
assert_eq!(normalized[0].tainted_args.len(), 2);
}
#[test]
fn equivalent_taint_call_sequences_merge_only_argument_evidence() {
let mut current = vec![path_step(
"factory",
"Record",
"lib/record.dart",
6,
12,
vec![(0, "fields['name']", "name")],
)];
let incoming = vec![path_step(
"factory",
"Record",
"lib/record.dart",
6,
12,
vec![(1, "fields['count']", "count")],
)];
assert!(equivalent_taint_call_sequence(¤t, &incoming));
merge_equivalent_taint_path(&mut current, &incoming);
assert_eq!(
current[0]
.tainted_args
.iter()
.map(|arg| (arg.index, arg.param_name.as_str()))
.collect::<Vec<_>>(),
vec![(0, "name"), (1, "count")]
);
}
#[test]
fn same_named_chain_at_different_call_sites_is_not_equivalent() {
let first = vec![path_step(
"factory",
"Record",
"lib/record.dart",
6,
12,
vec![(0, "first", "name")],
)];
let second = vec![path_step(
"factory",
"Record",
"lib/record.dart",
14,
12,
vec![(0, "second", "name")],
)];
assert!(!equivalent_taint_call_sequence(&first, &second));
}
#[test]
fn terminal_taint_step_aligns_to_selected_sink_location() {
let aligned = align_terminal_taint_step_to_sink(
vec![
path_step(
"route",
"applyOverrides",
"src/server.ts",
4,
10,
vec![(1, "req.body", "source")],
),
path_step(
"applyOverrides",
"key",
"src/deep.ts",
2,
3,
vec![(0, "key", ""), (1, "source", "")],
),
],
&sink_match("src/deep.ts", 6, 7, "target.key", "applyOverrides"),
);
assert_eq!(aligned.len(), 2);
let terminal = aligned.last().unwrap();
assert_eq!(terminal.file, "src/deep.ts");
assert_eq!(terminal.line, 6);
assert_eq!(terminal.column, 7);
assert_eq!(terminal.callee, "target.key");
}
#[test]
fn terminal_taint_step_does_not_align_across_functions() {
let aligned = align_terminal_taint_step_to_sink(
vec![path_step(
"helper",
"key",
"src/deep.ts",
2,
3,
vec![(0, "key", "")],
)],
&sink_match("src/deep.ts", 6, 7, "target.key", "applyOverrides"),
);
assert_eq!(aligned[0].line, 2);
assert_eq!(aligned[0].callee, "key");
}