A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147use mlua::{Lua, Result as LuaResult};
use super::tid::generate_tid;
const INSTRUCTION_LIMIT: u32 = 1_000_000;
/// Create a fresh sandboxed Lua VM.////// - Dangerous globals (`os`, `io`, `debug`, `package`, `require`, `dofile`, `loadfile`, `load`) are removed./// - An instruction-count hook prevents infinite loops./// - Utility globals `now()` and `log()` are injected.pub fn create_sandbox() -> LuaResult<Lua> { let lua = Lua::new();
// Remove dangerous globals let globals = lua.globals(); for name in &[ "os", "io", "debug", "package", "require", "dofile", "loadfile", "load", "collectgarbage", ] { globals.raw_set(*name, mlua::Value::Nil)?; }
// Instruction limit to prevent infinite loops lua.set_hook( mlua::HookTriggers::new().every_nth_instruction(INSTRUCTION_LIMIT), |_lua, _debug| Err(mlua::Error::runtime("script exceeded execution limit")), )?;
// Utility: now() returns UTC ISO 8601 string let now_fn = lua.create_function(|_, ()| Ok(chrono::Utc::now().to_rfc3339()))?; globals.set("now", now_fn)?;
// Utility: log(message) logs via tracing::debug let log_fn = lua.create_function(|_, msg: String| { tracing::debug!(lua_log = %msg, "lua script log"); Ok(()) })?; globals.set("log", log_fn)?;
// Utility: TID() returns a fresh AT Protocol TID string let tid_fn = lua.create_function(|_, ()| Ok(generate_tid()))?; globals.set("TID", tid_fn)?;
Ok(lua)}
/// Validate that a script compiles and defines a `handle` function.pub fn validate_script(source: &str) -> Result<(), String> { let lua = create_sandbox().map_err(|e| format!("failed to create Lua VM: {e}"))?; lua.load(source) .exec() .map_err(|e| format!("script compilation failed: {e}"))?;
let globals = lua.globals(); match globals.get::<mlua::Function>("handle") { Ok(_) => Ok(()), Err(_) => Err("script must define a handle() function".into()), }}
#[cfg(test)]mod tests { use super::*;
#[test] fn sandbox_removes_dangerous_globals() { let lua = create_sandbox().unwrap(); let globals = lua.globals(); assert!(globals.get::<mlua::Value>("os").unwrap().is_nil()); assert!(globals.get::<mlua::Value>("io").unwrap().is_nil()); assert!(globals.get::<mlua::Value>("debug").unwrap().is_nil()); assert!(globals.get::<mlua::Value>("package").unwrap().is_nil()); assert!(globals.get::<mlua::Value>("require").unwrap().is_nil()); }
#[test] fn sandbox_provides_now() { let lua = create_sandbox().unwrap(); let result: String = lua.load("return now()").eval().unwrap(); assert!(result.contains("T")); // ISO 8601 format }
#[test] fn sandbox_provides_log() { let lua = create_sandbox().unwrap(); lua.load(r#"log("test message")"#).exec().unwrap(); }
#[test] fn sandbox_provides_tid() { let lua = create_sandbox().unwrap(); let result: String = lua.load("return TID()").eval().unwrap(); assert_eq!(result.len(), 13); let valid = "234567abcdefghijklmnopqrstuvwxyz"; for ch in result.chars() { assert!(valid.contains(ch), "invalid char '{ch}' in TID"); } }
#[test] fn sandbox_tid_returns_unique_values() { let lua = create_sandbox().unwrap(); let a: String = lua.load("return TID()").eval().unwrap(); let b: String = lua.load("return TID()").eval().unwrap(); assert_ne!(a, b); }
#[test] fn sandbox_kills_infinite_loop() { let lua = create_sandbox().unwrap(); let result = lua.load("while true do end").exec(); assert!(result.is_err()); let err = result.unwrap_err().to_string(); assert!( err.contains("execution limit"), "expected execution limit error, got: {err}" ); }
#[test] fn validate_script_accepts_valid() { let result = validate_script("function handle() return {} end"); assert!(result.is_ok()); }
#[test] fn validate_script_rejects_missing_handle() { let result = validate_script("function other() return {} end"); assert!(result.is_err()); assert!(result.unwrap_err().contains("handle")); }
#[test] fn validate_script_rejects_syntax_error() { let result = validate_script("function handle("); assert!(result.is_err()); }}