use jsdet_core::observation::ResourceLimitKind;
use jsdet_core::{Bridge, CompiledModule, EmptyBridge, Observation, SandboxConfig};
use std::sync::Arc;
use std::thread;
struct AdversarialBridge {
return_value: Result<jsdet_core::observation::Value, String>,
}
impl Bridge for AdversarialBridge {
fn call(
&self,
_api: &str,
_args: &[jsdet_core::observation::Value],
) -> Result<jsdet_core::observation::Value, String> {
self.return_value.clone()
}
fn get_property(
&self,
object: &str,
property: &str,
) -> Result<jsdet_core::observation::Value, String> {
Err(format!("{object}.{property} is not defined"))
}
fn set_property(
&self,
_object: &str,
_property: &str,
_value: &jsdet_core::observation::Value,
) -> Result<(), String> {
Ok(())
}
fn provided_globals(&self) -> Vec<String> {
Vec::new()
}
fn bootstrap_js(&self) -> String {
String::new()
}
}
#[test]
fn test_01_empty_scripts() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let res = module.execute(&[], Arc::new(EmptyBridge), &config).unwrap();
assert_eq!(res.scripts_executed, 0);
assert!(res.errors.is_empty());
}
#[test]
fn test_02_empty_string_script() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let res = module
.execute(&["".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_03_null_byte_script() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let res = module
.execute(&["\0".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_04_mid_string_null_byte() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "console.log('A\0B');".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_05_u32_max_memory_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_memory_bytes = u32::MAX as usize;
let res = module
.execute(&["1+1".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_06_u64_max_fuel_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_fuel = u64::MAX;
let res = module
.execute(&["1+1".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_07_zero_memory_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_memory_bytes = 0;
let res = module.execute(&["1+1".to_string()], Arc::new(EmptyBridge), &config);
assert!(res.is_err(), "Expected an error but got {:?}", res);
}
#[test]
fn test_08_zero_fuel_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_fuel = 0; let res = module.execute(&["1+1".to_string()], Arc::new(EmptyBridge), &config);
assert!(
res.is_err(),
"Expected trap/error from QuickJS initialization, but got {:?}",
res
);
}
#[test]
fn test_09_1mb_plus_input_script() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_script_bytes = 2 * 1024 * 1024;
config.max_total_script_bytes = 2 * 1024 * 1024;
let mut script = "1+".repeat(500_000);
script.push('1');
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_10_concurrent_access_8_threads() {
let module = Arc::new(CompiledModule::new().unwrap());
let mut handles = vec![];
for _ in 0..8 {
let mod_clone = module.clone();
handles.push(thread::spawn(move || {
let config = SandboxConfig::default();
let res = mod_clone
.execute(&["var x = 1;".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}));
}
for h in handles {
h.join().unwrap();
}
}
#[test]
fn test_11_malformed_js_syntax() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let res = module
.execute(
&["function() {".to_string()],
Arc::new(EmptyBridge),
&config,
)
.unwrap();
assert!(
!res.errors.is_empty()
|| res
.observations
.iter()
.any(|o| matches!(o, Observation::Error { .. }))
);
}
#[test]
fn test_12_unicode_bom_start() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "\u{FEFF}var a = 1;".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_13_overlong_utf8_sequence_in_js() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "var a = '😎';".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_14_unpaired_surrogates() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "var a = '\\uD800';".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_15_duplicate_scripts() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "var a = 1;".to_string();
let res = module
.execute(&[script.clone(), script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 2);
}
#[test]
fn test_16_off_by_one_script_length() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_script_bytes = 10;
let script = "12345678901".to_string();
let res = module.execute(&[script], Arc::new(EmptyBridge), &config);
assert!(res.is_err()); }
#[test]
fn test_17_resource_exhaustion_infinite_loop() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.timeout_ms = 50;
config.max_fuel = 1_000_000;
let res = module.execute(
&["while(true){}".to_string()],
Arc::new(EmptyBridge),
&config,
);
assert!(
res.is_err(),
"Expected infinite loop trap/error from QuickJS initialization, but got {:?}",
res
);
}
#[test]
fn test_18_resource_exhaustion_deep_recursion() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "function f() { f(); } f();".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert!(
res.errors.len() > 0
|| res
.observations
.iter()
.any(|o| matches!(o, Observation::Error { .. }))
);
}
#[test]
fn test_19_resource_exhaustion_huge_array() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_memory_bytes = 16 * 1024 * 1024;
let script = "let a = new Uint8Array(32 * 1024 * 1024);".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert!(
res.errors.len() > 0
|| res
.observations
.iter()
.any(|o| matches!(o, Observation::Error { .. }))
);
}
#[test]
fn test_20_tampered_cache_bytes() {
let bytes = vec![0u8; 1024];
let res = CompiledModule::load_cached(&bytes);
assert!(res.is_err());
}
#[test]
fn test_21_max_script_count_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_scripts = 2;
let scripts = vec!["1".to_string(), "2".to_string(), "3".to_string()];
let res = module
.execute(&scripts, Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 2);
}
#[test]
fn test_22_total_script_bytes_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_total_script_bytes = 10;
let scripts = vec!["123456".to_string(), "123456".to_string()];
let res = module.execute(&scripts, Arc::new(EmptyBridge), &config);
assert!(res.is_err());
}
#[test]
fn test_23_timeout_ms_zero() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.timeout_ms = 0;
let res = module
.execute(&["1".to_string()], Arc::new(EmptyBridge), &config)
.unwrap();
assert!(
res.timed_out
|| res.observations.iter().any(|o| matches!(
o,
Observation::ResourceLimit {
kind: ResourceLimitKind::Timeout,
..
}
))
);
}
#[test]
fn test_24_huge_number_of_timers() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "for(let i=0; i<1000; i++) { setTimeout(()=>console.log(i), 1); }".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_25_timer_drain_limit() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_timer_drains = 2;
let script =
"setTimeout(()=>setTimeout(()=>setTimeout(()=>console.log(1), 1), 1), 1);".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_26_nested_wasm_alloc_bomb() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.allow_nested_wasm = true;
config.nested_wasm_max_memory = 0;
let script =
"new WebAssembly.Module(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]));"
.to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_27_huge_json_bridge_args() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "let a = 'A'.repeat(100000); __jsdet_dispatch_message(a);".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_28_adversarial_bridge_return() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let bridge = Arc::new(AdversarialBridge {
return_value: Ok(jsdet_core::observation::Value::string(
"A".repeat(1024 * 1024),
)),
});
let script = "jsdet.bridge_call('test', '[]');".to_string();
let res = module.execute(&[script], bridge, &config).unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_29_observation_flood() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_observations = 10;
let script = "for(let i=0; i<100; i++) { jsdet.observe(1, 'flood'); }".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_30_bridge_call_with_invalid_args() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "jsdet.bridge_call('test', undefined);".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_31_memory_out_of_bounds_js() {
let module = CompiledModule::new().unwrap();
let config = SandboxConfig::default();
let script = "let m = new WebAssembly.Memory({initial:10000});".to_string();
let res = module
.execute(&[script], Arc::new(EmptyBridge), &config)
.unwrap();
assert_eq!(res.scripts_executed, 1);
}
#[test]
fn test_32_timeout_ms_max() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.timeout_ms = u64::MAX;
config.max_fuel = 1000;
let res = module.execute(
&["while(true){}".to_string()],
Arc::new(EmptyBridge),
&config,
);
assert!(
res.is_err(),
"Expected initialization error, but got {:?}",
res
);
}
#[test]
fn test_33_fuel_consumption_without_trap() {
let module = CompiledModule::new().unwrap();
let mut config = SandboxConfig::default();
config.max_fuel = 10_000;
config.timeout_ms = 1000;
let script = "let a = 0; for(let i=0; i<100; i++){ a += i; }".to_string();
let res = module.execute(&[script], Arc::new(EmptyBridge), &config);
assert!(
res.is_err(),
"Expected initialization error, but got {:?}",
res
);
}