Moonshark/runner/sandbox.go

187 lines
5.1 KiB
Go

package runner
import (
"fmt"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
"github.com/valyala/fasthttp"
)
// Sandbox provides a secure execution environment for Lua scripts
type Sandbox struct {
executorBytecode []byte
}
// NewSandbox creates a new sandbox environment
func NewSandbox() *Sandbox {
return &Sandbox{}
}
// Setup initializes the sandbox in a Lua state
func (s *Sandbox) Setup(state *luajit.State, verbose bool) error {
// Load all embedded modules and sandbox
if err := loadSandboxIntoState(state, verbose); err != nil {
return fmt.Errorf("failed to load sandbox: %w", err)
}
// Pre-compile the executor function for reuse
executorCode := `return __execute`
bytecode, err := state.CompileBytecode(executorCode, "executor")
if err != nil {
return fmt.Errorf("failed to compile executor: %w", err)
}
s.executorBytecode = bytecode
// Register native functions
if err := s.registerCoreFunctions(state); err != nil {
return err
}
return nil
}
// Execute runs a Lua script in the sandbox with the given context
func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx *Context) (*Response, error) {
// Create response object in Lua
response := map[string]any{
"status": 200,
"headers": make(map[string]string),
"cookies": []any{},
"metadata": make(map[string]any),
"session": make(map[string]any),
}
// Load script bytecode (pushes function)
if err := state.LoadBytecode(bytecode, "script"); err != nil {
return nil, fmt.Errorf("failed to load bytecode: %w", err)
}
// Load executor (pushes __execute function)
if err := state.LoadBytecode(s.executorBytecode, "executor"); err != nil {
state.Pop(1) // Remove script function
return nil, fmt.Errorf("failed to load executor: %w", err)
}
// Call the loaded executor to get __execute
if err := state.Call(0, 1); err != nil {
state.Pop(1) // Remove script function
return nil, fmt.Errorf("failed to get executor: %w", err)
}
// Stack: [script_func, __execute]
state.PushCopy(-2) // Copy script function
// Stack: [script_func, __execute, script_func]
// Push context
if err := state.PushValue(ctx.Values); err != nil {
state.Pop(3)
return nil, fmt.Errorf("failed to push context: %w", err)
}
// Push response object
if err := state.PushValue(response); err != nil {
state.Pop(4)
return nil, fmt.Errorf("failed to push response: %w", err)
}
// Stack: [script_func, __execute, script_func, ctx, response]
// Call __execute(script_func, ctx, response)
if err := state.Call(3, 1); err != nil {
state.Pop(1) // Clean up
return nil, fmt.Errorf("script execution failed: %w", err)
}
// Get the result
result, _ := state.ToValue(-1)
state.Pop(2) // Remove result and original script function
// Extract response data directly from the response object we passed
return s.buildResponse(response, result), nil
}
// buildResponse converts the Lua response object to a Go Response
func (s *Sandbox) buildResponse(luaResp map[string]any, body any) *Response {
resp := NewResponse()
resp.Body = body
// Extract status
if status, ok := luaResp["status"].(float64); ok {
resp.Status = int(status)
} else if status, ok := luaResp["status"].(int); ok {
resp.Status = status
}
// Extract headers
if headers, ok := luaResp["headers"].(map[string]any); ok {
for k, v := range headers {
if str, ok := v.(string); ok {
resp.Headers[k] = str
}
}
} else if headers, ok := luaResp["headers"].(map[string]string); ok {
for k, v := range headers {
resp.Headers[k] = v
}
}
// Extract cookies
if cookies, ok := luaResp["cookies"].([]any); ok {
for _, cookieData := range cookies {
if cookieMap, ok := cookieData.(map[string]any); ok {
cookie := fasthttp.AcquireCookie()
if name, ok := cookieMap["name"].(string); ok && name != "" {
cookie.SetKey(name)
if value, ok := cookieMap["value"].(string); ok {
cookie.SetValue(value)
}
if path, ok := cookieMap["path"].(string); ok {
cookie.SetPath(path)
}
if domain, ok := cookieMap["domain"].(string); ok {
cookie.SetDomain(domain)
}
if httpOnly, ok := cookieMap["http_only"].(bool); ok {
cookie.SetHTTPOnly(httpOnly)
}
if secure, ok := cookieMap["secure"].(bool); ok {
cookie.SetSecure(secure)
}
if maxAge, ok := cookieMap["max_age"].(float64); ok {
cookie.SetMaxAge(int(maxAge))
} else if maxAge, ok := cookieMap["max_age"].(int); ok {
cookie.SetMaxAge(maxAge)
}
resp.Cookies = append(resp.Cookies, cookie)
} else {
fasthttp.ReleaseCookie(cookie)
}
}
}
}
// Extract metadata
if metadata, ok := luaResp["metadata"].(map[string]any); ok {
for k, v := range metadata {
resp.Metadata[k] = v
}
}
// Extract session data
if session, ok := luaResp["session"].(map[string]any); ok {
for k, v := range session {
resp.SessionData[k] = v
}
}
return resp
}
// registerCoreFunctions registers all built-in functions in the Lua state
func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
// Register your native functions here
// This stays the same as your current implementation
return nil
}