use std::sync::Arc;
use jsdet_core::{CompiledModule, EmptyBridge, SandboxConfig};
#[test]
fn string_concat_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var a = "hello";
var b = " world";
var c = a + b;
if (c !== "hello world") throw "concat failed: " + c;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn string_slice_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s = "abcdefgh";
var sliced = s.slice(2, 5);
if (sliced !== "cde") throw "slice failed: " + sliced;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn string_replace_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s = "hello world";
var replaced = s.replace("world", "jsdet");
if (replaced !== "hello jsdet") throw "replace failed: " + replaced;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn string_split_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s = "a,b,c";
var parts = s.split(",");
if (parts.length !== 3) throw "split count failed: " + parts.length;
if (parts[1] !== "b") throw "split value failed: " + parts[1];
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn template_literal_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var name = "jsdet";
var greeting = `hello ${name}!`;
if (greeting !== "hello jsdet!") throw "template failed: " + greeting;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn json_parse_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var json = '{"name":"jsdet","version":1}';
var obj = JSON.parse(json);
if (obj.name !== "jsdet") throw "json parse failed: " + obj.name;
if (obj.version !== 1) throw "json version failed: " + obj.version;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn complex_string_chain_preserves_execution() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
// Chain: JSON parse → substring → concat → replace
// This is the exact data flow in phishing kits.
var payload = '{"url":"https://evil.com/exfil","token":"abc123"}';
var obj = JSON.parse(payload);
var path = obj.url.substring(obj.url.indexOf("/", 8));
var full = "https://legit.com" + path;
var encoded = full.replace("exfil", "data");
if (!encoded.includes("legit.com")) throw "chain failed: " + encoded;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
}
#[test]
fn taint_set_and_get_roundtrip() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s = "tainted_data";
__jsdet_set_taint(s, 7);
var label = __jsdet_get_taint(s);
if (label !== 7) throw "taint roundtrip failed: got " + label;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(
result.errors.is_empty(),
"taint roundtrip should succeed: {:?}",
result.errors
);
}
#[test]
fn taint_get_returns_zero_for_clean_string() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var clean = "no taint";
var label = __jsdet_get_taint(clean);
if (label !== 0) throw "clean string should have label 0, got " + label;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_get_returns_zero_for_non_string() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var num = 42;
var label = __jsdet_get_taint(num);
if (label !== 0) throw "non-string should have label 0, got " + label;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_propagates_through_plus_operator() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var t = "payload";
__jsdet_set_taint(t, 1);
var result = "prefix_" + t;
var label = __jsdet_get_taint(result);
if (label === 0) throw "concat result should be tainted, got label " + label;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(
result.errors.is_empty(),
"taint should propagate through +: {:?}",
result.errors
);
}
#[test]
fn taint_propagates_through_substring() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var t = "abcPAYLOADxyz";
__jsdet_set_taint(t, 1);
var sub = t.substring(3, 10);
var label = __jsdet_get_taint(sub);
if (label === 0) throw "substring should preserve taint, got " + label;
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_check_at_sink_detects_tainted_input() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var t = "evil_code";
__jsdet_set_taint(t, 1);
var detected = __jsdet_check_taint_at_sink("eval", t);
if (!detected) throw "should detect tainted input at sink";
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_check_at_sink_ignores_clean_input() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var clean = "safe_code";
var detected = __jsdet_check_taint_at_sink("eval", clean);
if (detected) throw "should NOT detect clean input at sink";
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_survives_json_parse_stringify() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var obj = {key: "value"};
var json = JSON.stringify(obj);
__jsdet_set_taint(json, 1);
var parsed = JSON.parse(json);
// After JSON.parse, the string values should carry taint
// (depends on QuickJS JSON.parse implementation)
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_multiple_labels_on_different_strings() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s1 = "source_1";
var s2 = "source_2";
__jsdet_set_taint(s1, 1);
__jsdet_set_taint(s2, 2);
if (__jsdet_get_taint(s1) !== 1) throw "s1 should have label 1";
if (__jsdet_get_taint(s2) !== 2) throw "s2 should have label 2";
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}
#[test]
fn taint_overwrite_label() {
let module = CompiledModule::new().unwrap();
let result = module
.execute(
&[r#"
var s = "data";
__jsdet_set_taint(s, 5);
if (__jsdet_get_taint(s) !== 5) throw "should be 5";
__jsdet_set_taint(s, 10);
if (__jsdet_get_taint(s) !== 10) throw "should be 10 after overwrite";
"#
.into()],
Arc::new(EmptyBridge),
&SandboxConfig::default(),
)
.unwrap();
assert!(result.errors.is_empty(), "{:?}", result.errors);
}