import { Steps } from "nextra/components"

# 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](https://en.wikipedia.org/wiki/Sandbox_(computer_security)) 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](/api-reference/luau) library can be used.

<Steps>

### Step 1

Copy the source below and place it in a file named `sandbox.luau`:

<details>
<summary>Click to expand</summary>

```lua copy
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))()
```

</details>

### Step 2

Now, place the untrusted script you want to run safely next to the `sandbox.luau` script.

```sh copy filename="Bash"
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.

</Steps>