use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn make_vm() -> mlua::Lua {
let _g = ENV_LOCK.lock().unwrap();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.unwrap();
assay::lua::create_vm(client).unwrap()
}
fn make_vm_with_env(blocks: &str) -> mlua::Lua {
let _g = ENV_LOCK.lock().unwrap();
unsafe {
std::env::set_var(assay::lua::BLOCK_GLOBALS_ENV, blocks);
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.unwrap();
let vm = assay::lua::create_vm(client).unwrap();
unsafe {
std::env::remove_var(assay::lua::BLOCK_GLOBALS_ENV);
}
vm
}
async fn run(script: &str, vm: mlua::Lua) {
let bridged = assay::lua::async_bridge::strip_shebang(script);
vm.load(bridged).exec_async().await.unwrap();
}
#[tokio::test]
async fn load_loadfile_dofile_are_callable_by_default() {
let vm = make_vm();
let script = r#"
assert.eq(type(load), "function")
assert.eq(type(loadfile), "function")
assert.eq(type(dofile), "function")
-- load() with a chunk string returns a function, not nil.
local fn = load("return 1 + 2")
assert.eq(type(fn), "function")
assert.eq(fn(), 3)
"#;
run(script, vm).await;
}
#[tokio::test]
async fn string_dump_stays_blocked_for_safety() {
let vm = make_vm();
let script = r#"
-- string.dump produces Lua bytecode and is the documented
-- bytecode-escape hatch — must be unavailable.
assert.eq(string.dump, nil)
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_nils_top_level_names() {
let vm = make_vm_with_env("dofile,loadfile");
let script = r#"
assert.eq(dofile, nil)
assert.eq(loadfile, nil)
assert.eq(type(load), "function")
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_nils_dotted_paths() {
let vm = make_vm_with_env("string.rep,os.execute,os.exit");
let script = r#"
assert.eq(string.rep, nil)
assert.eq(type(string.sub), "function")
local os_lib = require("os")
assert.eq(os_lib.execute, nil)
assert.eq(os_lib.exit, nil)
assert.eq(type(os_lib.time), "function")
assert.eq(type(os.time), "function")
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_silently_skips_typos() {
let vm = make_vm_with_env("bogus.path, , dofile");
let script = r#"
assert.eq(dofile, nil)
assert.eq(bogus, nil)
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_reaches_assay_s_own_globals() {
let vm = make_vm_with_env("fs,db,dns,ws");
let script = r#"
assert.eq(fs, nil)
assert.eq(db, nil)
assert.eq(dns, nil)
assert.eq(ws, nil)
assert.eq(type(http), "table")
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_mixes_stdlib_and_builtin_names() {
let vm = make_vm_with_env("os.execute,fs");
let script = r#"
local os_lib = require("os")
assert.eq(os_lib.execute, nil)
assert.eq(type(os_lib.time), "function")
assert.eq(fs, nil)
"#;
run(script, vm).await;
}
#[tokio::test]
async fn assay_block_globals_clears_the_require_cache_for_a_bare_name() {
let vm = make_vm_with_env("io");
let script = r#"
assert.eq(io, nil)
assert.eq(package.loaded["io"], nil)
assert.eq(pcall(require, "io"), false)
"#;
run(script, vm).await;
}
#[tokio::test]
async fn blocking_a_table_never_leaves_an_ungated_one_behind() {
let vm = {
let _g = ENV_LOCK.lock().unwrap();
unsafe {
std::env::set_var(assay::lua::BLOCK_GLOBALS_ENV, "io,os.execute,fs");
std::env::set_var(assay::lua::READONLY_ENV, "1");
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.unwrap();
let vm = assay::lua::create_vm(client).unwrap();
unsafe {
std::env::remove_var(assay::lua::BLOCK_GLOBALS_ENV);
std::env::remove_var(assay::lua::READONLY_ENV);
}
vm
};
let script = r#"
assert.eq(io, nil)
assert.eq(fs, nil)
assert.eq(package.loaded["io"], nil)
assert.eq(pcall(require, "io"), false)
-- and nothing ungated survives behind require for the rest of it
local os_lib = require("os")
assert.eq(os_lib.execute, nil)
local ok, err = pcall(os_lib.remove, "/tmp/assay-block-probe")
assert.eq(ok, false)
assert.eq(tostring(err):find("readonly:") ~= nil, true)
"#;
run(script, vm).await;
}