Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions slatron-server/src/rhai_engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,39 @@ fn register_global_functions(engine: &mut Engine) {
});
}

const ALLOWED_COMMANDS: &[&str] = &["yt-dlp", "ffmpeg", "ffprobe"];

fn run_shell_execute(cmd: String, args: Vec<rhai::Dynamic>) -> rhai::Map {
// Security Check: Allowlist
if !ALLOWED_COMMANDS.contains(&cmd.as_str()) {
let err_msg = format!("Security Alert: Command '{}' is not in the allowlist.", cmd);
tracing::error!("{}", err_msg);
let mut map = rhai::Map::new();
map.insert("code".into(), (-1 as i64).into());
map.insert("stdout".into(), "".into());
map.insert("stderr".into(), err_msg.into());
return map;
}

// Security Check: Block dangerous args for specific commands
if cmd == "yt-dlp" {
for arg in &args {
let s = arg.to_string();
if s.starts_with("--exec") {
let err_msg = format!(
"Security Alert: Argument '{}' is blocked for command '{}'.",
s, cmd
);
tracing::error!("{}", err_msg);
let mut map = rhai::Map::new();
map.insert("code".into(), (-1 as i64).into());
map.insert("stdout".into(), "".into());
map.insert("stderr".into(), err_msg.into());
return map;
}
}
}

let mut command = std::process::Command::new(&cmd);

let mut args_str = String::new();
Expand Down
46 changes: 46 additions & 0 deletions slatron-server/src/rhai_engine/security_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,49 @@ fn test_download_file_safe_path_rejection() {
assert_eq!(val, false, "Should reject absolute path");
}
}

#[test]
fn test_shell_execute_blocked_command() {
let engine = create_engine("content_loader");

// Attempt to run 'ls' which should be blocked
let script = r#"
let result = shell_execute("ls", ["-la"]);
result
"#;

let result = engine.eval::<rhai::Map>(script);
if let Ok(map) = result {
let stderr = map.get("stderr").expect("Should have stderr").clone().into_string().unwrap_or_default();
let code = map.get("code").expect("Should have code").as_int().unwrap_or(0);

// This test is expected to fail initially (showing the vulnerability)
// because ls will run successfully (code 0) and stderr will be empty.
assert!(code != 0, "Blocked command should not return success code 0. Got code: {}", code);
assert!(stderr.contains("Security Alert"), "Stderr should contain 'Security Alert'. Got: {}", stderr);
} else {
panic!("Script execution failed");
}
}

#[test]
fn test_shell_execute_yt_dlp_exec_blocked() {
let engine = create_engine("content_loader");

// Attempt to use --exec
let script = r#"
let result = shell_execute("yt-dlp", ["--exec", "touch /tmp/pwned"]);
result
"#;

let result = engine.eval::<rhai::Map>(script);
if let Ok(map) = result {
let stderr = map.get("stderr").expect("Should have stderr").clone().into_string().unwrap_or_default();
let code = map.get("code").expect("Should have code").as_int().unwrap_or(0);

assert!(code != 0, "Blocked arg should not return success code 0. Got code: {}", code);
assert!(stderr.contains("Security Alert"), "Stderr should contain 'Security Alert'. Got: {}", stderr);
} else {
panic!("Script execution failed");
}
}