1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/// Configuration for a single sandbox execution.
///
/// Sensible defaults are provided. Zero config for consumers who just want
/// `execute(scripts)`. Full control for experts.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SandboxConfig {
/// Maximum wall-clock execution time in milliseconds.
/// Default: 200ms. Enough for most phishing kits.
/// Set to 5000ms for complex extension analysis.
pub timeout_ms: u64,
/// Maximum WASM linear memory in bytes.
/// Default: 16MB. `QuickJS` needs ~4MB for bootstrap.
pub max_memory_bytes: usize,
/// Maximum fuel (WASM instruction count).
/// Default: `100_000_000` (~100ms of execution on modern hardware).
/// 0 = unlimited (use timeout only).
pub max_fuel: u64,
/// Maximum observations before the sandbox stops recording.
/// Prevents memory exhaustion from observation floods.
/// Default: `10_000`.
pub max_observations: usize,
/// Maximum number of scripts to execute per sandbox run.
/// Default: 100.
pub max_scripts: usize,
/// Maximum size of a single script in bytes.
/// Default: 1MB.
pub max_script_bytes: usize,
/// Maximum combined size of all scripts in bytes.
/// Default: 5MB.
pub max_total_script_bytes: usize,
/// Whether to immediately drain all pending timers after script execution.
/// When true, setTimeout/setInterval callbacks fire synchronously.
/// Default: true (for detonation — you want to trigger delayed payloads).
pub drain_timers: bool,
/// Maximum timer callbacks to drain per execution.
/// Default: 50.
pub max_timer_drains: usize,
/// Whether to allow nested WASM instantiation.
/// When true, JS `new WebAssembly.Module()` creates a real nested WASM instance.
/// When false, it returns a stub that records the attempt but doesn't execute.
/// Default: true.
pub allow_nested_wasm: bool,
/// Maximum linear memory for nested WASM instances in bytes.
/// Default: 4MB.
pub nested_wasm_max_memory: usize,
/// Maximum fuel for nested WASM instances.
/// Default: `10_000_000`.
pub nested_wasm_max_fuel: u64,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
timeout_ms: 200,
max_memory_bytes: 16 * 1024 * 1024,
max_fuel: 100_000_000,
max_observations: 10_000,
max_scripts: 100,
max_script_bytes: 1024 * 1024,
max_total_script_bytes: 5 * 1024 * 1024,
drain_timers: true,
max_timer_drains: 50,
// Secure by default — nested WASM disabled. Enable explicitly for extension analysis.
allow_nested_wasm: false,
nested_wasm_max_memory: 4 * 1024 * 1024,
nested_wasm_max_fuel: 10_000_000,
}
}
}
impl SandboxConfig {
/// Config tuned for fast URL detonation (Sear).
/// Low timeouts, drain timers, nested WASM disabled.
#[must_use]
pub fn detonation() -> Self {
Self {
timeout_ms: 200,
max_fuel: 50_000_000,
drain_timers: true,
allow_nested_wasm: false,
..Self::default()
}
}
/// Config tuned for deep extension analysis (Soleno).
/// Higher timeouts, nested WASM enabled, more observations.
#[must_use]
pub fn extension_analysis() -> Self {
Self {
timeout_ms: 5000,
max_fuel: 500_000_000,
max_observations: 50_000,
max_scripts: 500,
drain_timers: true,
allow_nested_wasm: true,
..Self::default()
}
}
/// Config for interactive research.
/// No fuel limit, high timeouts, everything enabled.
#[must_use]
pub fn research() -> Self {
Self {
timeout_ms: 30_000,
max_fuel: 0, // unlimited
max_observations: 1_000_000,
max_scripts: 10_000,
max_memory_bytes: 256 * 1024 * 1024,
drain_timers: false, // researcher controls timer advancement
allow_nested_wasm: true,
nested_wasm_max_memory: 64 * 1024 * 1024,
nested_wasm_max_fuel: 100_000_000,
..Self::default()
}
}
}