--[[ Moonshark Lua Sandbox Environment This file contains all the Lua code needed for the sandbox environment, including core modules and utilities. It's designed to be embedded in the Go binary at build time. ]]-- -- Global tables for execution context __http_responses = {} __module_paths = {} __module_bytecode = {} __ready_modules = {} __session_data = {} __session_id = nil __session_modified = false -- ====================================================================== -- CORE SANDBOX FUNCTIONALITY -- ====================================================================== -- Create environment inheriting from _G function __create_env(ctx) -- Create environment with metatable inheriting from _G local env = setmetatable({}, {__index = _G}) -- Add context if provided if ctx then env.ctx = ctx end -- Add proper require function to this environment if __setup_require then __setup_require(env) end return env end -- Execute script with clean environment function __execute_script(fn, ctx) -- Clear previous responses __http_responses[1] = nil -- Create environment with metatable inheriting from _G local env = setmetatable({}, {__index = _G}) -- Add context if provided if ctx then env.ctx = ctx end print("INIT SESSION DATA:", util.json_encode(ctx.session_data or {})) -- Initialize local session variables in the environment env.__session_data = ctx.session_data or {} env.__session_id = ctx.session_id env.__session_modified = false -- Add proper require function to this environment if __setup_require then __setup_require(env) end -- Set environment for function setfenv(fn, env) -- Execute with protected call local ok, result = pcall(fn) if not ok then error(result, 0) end -- If session was modified, add to response if env.__session_modified then __http_responses[1] = __http_responses[1] or {} __http_responses[1].session_data = env.__session_data __http_responses[1].session_id = env.__session_id __http_responses[1].session_modified = true end print("SESSION MODIFIED:", env.__session_modified) print("FINAL DATA:", util.json_encode(env.__session_data or {})) return result end -- ====================================================================== -- HTTP MODULE -- ====================================================================== -- HTTP module implementation local http = { -- Set HTTP status code set_status = function(code) if type(code) ~= "number" then error("http.set_status: status code must be a number", 2) end local resp = __http_responses[1] or {} resp.status = code __http_responses[1] = resp end, -- Set HTTP header set_header = function(name, value) if type(name) ~= "string" or type(value) ~= "string" then error("http.set_header: name and value must be strings", 2) end local resp = __http_responses[1] or {} resp.headers = resp.headers or {} resp.headers[name] = value __http_responses[1] = resp end, -- Set content type; set_header helper set_content_type = function(content_type) http.set_header("Content-Type", content_type) end, -- Set metadata (arbitrary data to be returned with response) set_metadata = function(key, value) if type(key) ~= "string" then error("http.set_metadata: key must be a string", 2) end local resp = __http_responses[1] or {} resp.metadata = resp.metadata or {} resp.metadata[key] = value __http_responses[1] = resp end, -- HTTP client submodule client = { -- Generic request function request = function(method, url, body, options) if type(method) ~= "string" then error("http.client.request: method must be a string", 2) end if type(url) ~= "string" then error("http.client.request: url must be a string", 2) end -- Call native implementation local result = __http_request(method, url, body, options) return result end, -- Simple GET request get = function(url, options) return http.client.request("GET", url, nil, options) end, -- Simple POST request with automatic content-type post = function(url, body, options) options = options or {} return http.client.request("POST", url, body, options) end, -- Simple PUT request with automatic content-type put = function(url, body, options) options = options or {} return http.client.request("PUT", url, body, options) end, -- Simple DELETE request delete = function(url, options) return http.client.request("DELETE", url, nil, options) end, -- Simple PATCH request patch = function(url, body, options) options = options or {} return http.client.request("PATCH", url, body, options) end, -- Simple HEAD request head = function(url, options) options = options or {} return http.client.request("HEAD", url, nil, options) end, -- Simple OPTIONS request options = function(url, options) return http.client.request("OPTIONS", url, nil, options) end, -- Shorthand function to directly get JSON get_json = function(url, options) options = options or {} local response = http.client.get(url, options) if response.ok and response.json then return response.json end return nil, response end, -- Utility to build a URL with query parameters build_url = function(base_url, params) if not params or type(params) ~= "table" then return base_url end local query = {} for k, v in pairs(params) do if type(v) == "table" then for _, item in ipairs(v) do table.insert(query, k .. "=" .. tostring(item)) end else table.insert(query, k .. "=" .. tostring(v)) end end if #query > 0 then if base_url:find("?") then return base_url .. "&" .. table.concat(query, "&") else return base_url .. "?" .. table.concat(query, "&") end end return base_url end } } -- ====================================================================== -- COOKIE MODULE -- ====================================================================== -- Cookie module implementation local cookie = { -- Set a cookie set = function(name, value, options) if type(name) ~= "string" then error("cookie.set: name must be a string", 2) end -- Get or create response local resp = __http_responses[1] or {} resp.cookies = resp.cookies or {} __http_responses[1] = resp -- Handle options as table local opts = options or {} -- Create cookie table local cookie = { name = name, value = value or "", path = opts.path or "/", domain = opts.domain } -- Handle expiry if opts.expires then if type(opts.expires) == "number" then if opts.expires > 0 then cookie.max_age = opts.expires local now = os.time() cookie.expires = now + opts.expires elseif opts.expires < 0 then cookie.expires = 1 cookie.max_age = 0 end -- opts.expires == 0: Session cookie (omitting both expires and max-age) end end -- Security flags cookie.secure = (opts.secure ~= false) cookie.http_only = (opts.http_only ~= false) -- Store in cookies table local n = #resp.cookies + 1 resp.cookies[n] = cookie return true end, -- Get a cookie value get = function(name) if type(name) ~= "string" then error("cookie.get: name must be a string", 2) end -- Access values directly from current environment local env = getfenv(2) -- Check if context exists and has cookies if env.ctx and env.ctx.cookies then return env.ctx.cookies[name] end -- If context has request_cookies map if env.ctx and env.ctx._request_cookies then return env.ctx._request_cookies[name] end return nil end, -- Remove a cookie remove = function(name, path, domain) if type(name) ~= "string" then error("cookie.remove: name must be a string", 2) end -- Create an expired cookie return cookie.set(name, "", {expires = 0, path = path or "/", domain = domain}) end } -- ====================================================================== -- SESSION MODULE -- ====================================================================== local session = { -- Get session value get = function(key) if type(key) ~= "string" then error("session.get: key must be a string", 2) end local env = getfenv(2) return env.__session_data and env.__session_data[key] end, -- Set session value set = function(key, value) if type(key) ~= "string" then error("session.set: key must be a string", 2) end local env = getfenv(2) print("SET ENV:", tostring(env)) -- Debug the environment if not env.__session_data then env.__session_data = {} print("CREATED NEW SESSION TABLE") end env.__session_data[key] = value env.__session_modified = true print("SET:", key, "=", tostring(value), "MODIFIED:", env.__session_modified) return true end, -- Delete session value delete = function(key) if type(key) ~= "string" then error("session.delete: key must be a string", 2) end local env = getfenv(2) if env.__session_data and env.__session_data[key] ~= nil then env.__session_data[key] = nil env.__session_modified = true end return true end, -- Clear all session data clear = function() local env = getfenv(2) if env.__session_data and next(env.__session_data) then env.__session_data = {} env.__session_modified = true end return true end, -- Get session ID get_id = function() local env = getfenv(2) return env.__session_id or "" end, -- Get all session data get_all = function() local env = getfenv(2) return env.__session_data or {} end, -- Check if session has key has = function(key) if type(key) ~= "string" then error("session.has: key must be a string", 2) end local env = getfenv(2) return env.__session_data ~= nil and env.__session_data[key] ~= nil end } -- ====================================================================== -- CSRF MODULE -- ====================================================================== -- CSRF protection module local csrf = { -- Session key where the token is stored TOKEN_KEY = "_csrf_token", -- Default form field name DEFAULT_FIELD = "csrf", -- Generate a new CSRF token and store it in the session generate = function(length) -- Default length is 32 characters length = length or 32 if length < 16 then -- Enforce minimum security length = 16 end -- Check if we have a session module if not session then error("CSRF protection requires the session module", 2) end local token = __generate_token(length) session.set(csrf.TOKEN_KEY, token) return token end, -- Get the current token or generate a new one token = function() -- Get from session if exists local token = session.get(csrf.TOKEN_KEY) -- Generate if needed if not token then token = csrf.generate() end return token end, -- Generate a hidden form field with the CSRF token field = function(field_name) field_name = field_name or csrf.DEFAULT_FIELD local token = csrf.token() return string.format('', field_name, token) end, -- Verify a given token against the session token verify = function(token, field_name) field_name = field_name or csrf.DEFAULT_FIELD local env = getfenv(2) local form = nil if env.ctx and env.ctx._request_form then form = env.ctx._request_form elseif env.ctx and env.ctx.form then form = env.ctx.form else return false end token = token or form[field_name] if not token then return false end local session_token = session.get(csrf.TOKEN_KEY) if not session_token then return false end -- Constant-time comparison to prevent timing attacks if #token ~= #session_token then return false end local result = true for i = 1, #token do if token:sub(i, i) ~= session_token:sub(i, i) then result = false -- Don't break early - continue to prevent timing attacks end end return result end } -- ====================================================================== -- UTIL MODULE -- ====================================================================== -- Utility module implementation local util = { -- Generate a token (wrapper around __generate_token) generate_token = function(length) return __generate_token(length or 32) end, -- Simple JSON stringify (for when you just need a quick string) json_encode = function(value) if type(value) == "table" then local json = "{" local sep = "" for k, v in pairs(value) do json = json .. sep if type(k) == "number" then -- Array-like json = json .. util.json_encode(v) else -- Object-like json = json .. '"' .. k .. '":' .. util.json_encode(v) end sep = "," end return json .. "}" elseif type(value) == "string" then return '"' .. value:gsub('"', '\\"'):gsub('\n', '\\n') .. '"' elseif type(value) == "number" then return tostring(value) elseif type(value) == "boolean" then return value and "true" or "false" elseif value == nil then return "null" end return '"' .. tostring(value) .. '"' end, -- Deep copy of tables deep_copy = function(obj) if type(obj) ~= 'table' then return obj end local res = {} for k, v in pairs(obj) do res[k] = util.deep_copy(v) end return res end, -- Merge tables merge_tables = function(t1, t2) if type(t1) ~= 'table' or type(t2) ~= 'table' then error("Both arguments must be tables", 2) end local result = util.deep_copy(t1) for k, v in pairs(t2) do if type(v) == 'table' and type(result[k]) == 'table' then result[k] = util.merge_tables(result[k], v) else result[k] = v end end return result end, -- String utilities string = { -- Trim whitespace trim = function(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end, -- Split string split = function(s, delimiter) delimiter = delimiter or "," local result = {} for match in (s..delimiter):gmatch("(.-)"..delimiter) do table.insert(result, match) end return result end } } -- ====================================================================== -- REGISTER MODULES GLOBALLY -- ====================================================================== -- Install modules in global scope _G.http = http _G.cookie = cookie _G.session = session _G.csrf = csrf _G.util = util