Security
When running lune scripts, it is important to note that any scripts you execute have full access to your device, including access to your files, programs, and more. Therefore, it is important to stay cautious when executing a script from a source you don't trust.
If you are unsure what a script does, it is recommended to run the script in a sandboxed (opens in a new tab) environment to evaluate what it does.
Lune does not include a built-in permissions sandbox, but the following luau script, which utilizes the @lune/luau library can be used.
Step 1
Copy the source below and place it in a file named sandbox.luau
:
Click to expand
local datetime = require("@lune/datetime")
local fs = require("@lune/fs")
local luau = require("@lune/luau")
local net = require("@lune/net")
local process = require("@lune/process")
local regex = require("@lune/regex")
local roblox = require("@lune/roblox")
local serde = require("@lune/serde")
local stdio = require("@lune/stdio")
local task = require("@lune/task")
local processArgs = table.clone(process.args)
local filePath: string = table.remove(processArgs, 1)
or error("usage: lune run sandbox [SCRIPT_PATH] -- [ARGS]")
local DEFAULT_PRINT = print
local SANDBOXED_ENV = {
debugName = filePath,
environment = {
require = nil,
getfenv = nil,
setfenv = nil,
print = nil,
warn = nil,
},
}
local PROMPT_MSG_TMPL = `allow {SANDBOXED_ENV.debugName} to access %s?`
local DENIED_ERR_TMPL = `{SANDBOXED_ENV.debugName} tried to access disallowed library %s!`
local function discoverAndReadScript(filePath: string): string
local scriptContents: string
if fs.isFile(filePath) then
scriptContents = fs.readFile(filePath)
return scriptContents
end
if fs.isDir(filePath) then
if fs.isFile(filePath .. "/init.luau") then
scriptContents = fs.readFile(filePath .. "/init.luau")
end
if fs.isFile(filePath .. "/init.lua") then
scriptContents = fs.readFile(filePath .. "/init.lua")
end
end
if scriptContents == nil then
for _, ext in { ".luau", ".lua" } do
local filePathExt = filePath .. ext
if fs.isFile(filePathExt) then
scriptContents = fs.readFile(filePathExt)
end
end
if scriptContents == nil then
error(`No such file or directory \`{filePath}\``)
end
end
return scriptContents
end
local function sandboxGetfenv(): {}
if table.isfrozen(SANDBOXED_ENV) then
return SANDBOXED_ENV.environment
end
return {}
end
local function sandboxSetfenv(env: {}): never
error("cannot call setfenv from sandbox")
end
local function sandboxPrint(...: any)
DEFAULT_PRINT(`---- Output from {SANDBOXED_ENV.debugName} ----`)
DEFAULT_PRINT(tostring(...))
DEFAULT_PRINT(`---------------------------------------`)
end
local function constructProtectedMt<T>(library: T)
return {
__index = library,
__metatable = "Locked",
__tostring = function()
return stdio.format(library)
end,
}
end
local SANDBOXED_LUNE_STD_LIB = {
["@lune/fs"] = setmetatable({}, constructProtectedMt(fs)),
["@lune/luau"] = setmetatable({}, constructProtectedMt(luau)),
["@lune/process"] = setmetatable({}, constructProtectedMt(process)),
["@lune/stdio"] = setmetatable({
write = sandboxPrint,
ewrite = sandboxPrint,
}, constructProtectedMt(stdio)),
["@lune/net"] = setmetatable({}, constructProtectedMt(net)),
["@lune/roblox"] = setmetatable({
getAuthCookie = function(...)
local allowAuthCookie: boolean = stdio.prompt(
"confirm",
`allow {SANDBOXED_ENV.debugName} to access your .ROBLOSECURITY token?`
)
if allowAuthCookie then
local getAuthCookie = require("@lune/roblox").getAuthCookie
return getAuthCookie(...)
end
error(
`{SANDBOXED_ENV.debugName} attempted to access .ROBLOSECURITY token even when denied`
)
end,
}, constructProtectedMt(roblox)),
["@lune/serde"] = serde,
["@lune/task"] = task,
["@lune/regex"] = regex,
["@lune/datetime"] = datetime,
}
local function sandboxedRequire(path: string)
local module = SANDBOXED_LUNE_STD_LIB[path]
if module then
local allowed: boolean = stdio.prompt("confirm", string.format(PROMPT_MSG_TMPL, path))
if allowed then
return module
end
error(string.format(DENIED_ERR_TMPL, path))
else
local contents = discoverAndReadScript(path)
local evalChunk = luau.load(contents, SANDBOXED_ENV)
return evalChunk()
end
end
SANDBOXED_ENV.environment.require = sandboxedRequire
SANDBOXED_ENV.environment.getfenv = sandboxGetfenv
SANDBOXED_ENV.environment.setfenv = sandboxSetfenv
SANDBOXED_ENV.environment.print = sandboxPrint
SANDBOXED_ENV.environment.warn = sandboxPrint
luau.load(discoverAndReadScript(filePath), table.freeze(SANDBOXED_ENV))()
Step 2
Now, place the untrusted script you want to run safely next to the sandbox.luau
script.
lune run sandbox.luau script.luau -- [ARGUMENTS_HERE]
Replace script.luau
and [ARGUMENTS_HERE]
with the path to the script and the arguments to run
it with.
Step 3
As the script runs, any requires to potentially dangerous modules will require your approval before continuing and any invocations to methods within approved modules will be logged.
Furthermore, the output of the sandbox script and the script being run will be separated.