use crate::runtime::docs::{self, LuaDoc, LuaDocTyp};
pub fn apply(lua: &mlua::Lua) -> mlua::Result<()> {
sandbox_os_module(lua)?;
let globals = lua.globals();
globals.set("io", mlua::Value::Nil)?;
globals.set("file", mlua::Value::Nil)?;
globals.set("require", mlua::Value::Nil)?;
globals.set("dofile", mlua::Value::Nil)?;
globals.set("load", mlua::Value::Nil)?;
globals.set("loadfile", mlua::Value::Nil)?;
globals.set("loadstring", mlua::Value::Nil)?;
globals.set("package", mlua::Value::Nil)?;
globals.set("debug", mlua::Value::Nil)?;
globals.set("rawget", mlua::Value::Nil)?;
globals.set("rawset", mlua::Value::Nil)?;
globals.set("rawequal", mlua::Value::Nil)?;
globals.set("rawlen", mlua::Value::Nil)?;
globals.set("getmetatable", mlua::Value::Nil)?;
globals.set("setmetatable", mlua::Value::Nil)?;
globals.set("collectgarbage", mlua::Value::Nil)?;
globals.set("coroutine", mlua::Value::Nil)?;
register_docs(lua)?;
Ok(())
}
fn register_docs(lua: &mlua::Lua) -> mlua::Result<()> {
docs::register(
lua,
&LuaDoc {
name: "os".to_string(),
typ: LuaDocTyp::Scope,
description: "Operating system functions (sandboxed)".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "os.time".to_string(),
typ: LuaDocTyp::Function,
description: "Returns current Unix timestamp".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "os.date".to_string(),
typ: LuaDocTyp::Function,
description: "Formats date/time. Usage: os.date(format, time?)".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "string".to_string(),
typ: LuaDocTyp::Scope,
description: "String manipulation functions".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "table".to_string(),
typ: LuaDocTyp::Scope,
description: "Table manipulation functions".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "math".to_string(),
typ: LuaDocTyp::Scope,
description: "Mathematical functions".to_string(),
},
)?;
docs::register(
lua,
&LuaDoc {
name: "utf8".to_string(),
typ: LuaDocTyp::Scope,
description: "UTF-8 support functions".to_string(),
},
)?;
Ok(())
}
fn sandbox_os_module(lua: &mlua::Lua) -> mlua::Result<()> {
let globals = lua.globals();
let os_table: mlua::Table = globals.get("os")?;
let os_time: mlua::Function = os_table.get("time")?;
let os_date: mlua::Function = os_table.get("date")?;
let safe_os = lua.create_table()?;
safe_os.set("time", os_time)?;
safe_os.set("date", os_date)?;
globals.set("os", safe_os)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::runtime::output::capture_output;
use super::*;
#[test]
fn blocked_io_module() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("local f = io.open('test.txt', 'r')").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_os_execute() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("os.execute('ls')").exec();
assert!(result.is_err());
}
#[test]
fn blocked_os_getenv() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("os.getenv('PATH')").exec();
assert!(result.is_err());
}
#[test]
fn allowed_os_time() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let rx = capture_output(&lua).unwrap();
lua.load("print(type(os.time()))").exec().unwrap();
let output: String = rx.try_iter().collect();
assert!(output.contains("number"));
}
#[test]
fn allowed_os_date() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let rx = capture_output(&lua).unwrap();
lua.load("print(type(os.date('%Y-%m-%d')))").exec().unwrap();
let output: String = rx.try_iter().collect();
assert!(output.contains("string"));
}
#[test]
fn blocked_require() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("require('os')").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_load() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("load('print(1)')()").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_dofile() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("dofile('/etc/passwd')").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_collectgarbage() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("collectgarbage('collect')").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_coroutine() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("coroutine.create(function() end)").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_rawset() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("rawset(_G, 'x', 1)").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
#[test]
fn blocked_getmetatable() {
let lua = mlua::Lua::new();
apply(&lua).unwrap();
let result = lua.load("getmetatable('')").exec();
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("nil"));
}
}