# Milestone 4: Safe Path & Require **Status**: Complete ✓ **Goal**: Secure file access within app sandbox. --- ## Overview This milestone implements path validation to prevent directory traversal attacks and a safe `require()` function that loads Lua modules only from the app's scripts directory. ### Key Deliverables 1. **PathSandbox class** - Path validation and canonicalization 2. **SafeRequire function** - Secure module loader 3. **Module caching** - Registry-based cache for loaded modules --- ## File Structure ``` src/main/cpp/sandbox/ ├── lua_sandbox.h # (existing) ├── lua_sandbox.cpp # (existing) ├── permission_gate.h # (existing) ├── permission_gate.cpp # (existing) ├── audit_log.h # (existing) ├── audit_log.cpp # (existing) ├── rate_limiter.h # (existing) ├── rate_limiter.cpp # (existing) ├── path_sandbox.h # NEW - Path validation └── path_sandbox.cpp # NEW - Implementation ``` --- ## Implementation Details ### 1. PathSandbox Class ```cpp // path_sandbox.h #pragma once #include #include namespace mosis { class PathSandbox { public: explicit PathSandbox(const std::string& app_path); // Validate a path is within the sandbox bool ValidatePath(const std::string& path, std::string& out_canonical); // Check if path contains traversal attempts static bool ContainsTraversal(const std::string& path); // Check if path is absolute static bool IsAbsolutePath(const std::string& path); // Normalize path separators and remove redundant components static std::string NormalizePath(const std::string& path); // Get the app's base path const std::string& GetAppPath() const { return m_app_path; } // Resolve a relative path to full path within sandbox std::string ResolvePath(const std::string& relative_path); private: std::string m_app_path; }; } // namespace mosis ``` ### 2. SafeRequire Function ```cpp // path_sandbox.cpp // Safe require implementation for Lua int SafeRequire(lua_State* L); // Register safe require as global void RegisterSafeRequire(lua_State* L, PathSandbox* sandbox); ``` ### 3. Module Name Validation Valid module names contain only: - Alphanumeric characters (a-z, A-Z, 0-9) - Underscores (_) - Dots (.) for submodules Examples: - `utils` → loads `scripts/utils.lua` - `ui.button` → loads `scripts/ui/button.lua` --- ## Test Cases ### Test 1: Rejects Directory Traversal ```cpp bool Test_PathRejectsTraversal(std::string& error_msg) { mosis::PathSandbox sandbox("D:/test/app"); EXPECT_TRUE(mosis::PathSandbox::ContainsTraversal("../etc/passwd")); EXPECT_TRUE(mosis::PathSandbox::ContainsTraversal("foo/../../../bar")); EXPECT_TRUE(mosis::PathSandbox::ContainsTraversal("..\\windows\\system32")); std::string canonical; EXPECT_FALSE(sandbox.ValidatePath("../etc/passwd", canonical)); EXPECT_FALSE(sandbox.ValidatePath("data/../../../etc/passwd", canonical)); return true; } ``` ### Test 2: Rejects Absolute Paths ```cpp bool Test_PathRejectsAbsolute(std::string& error_msg) { EXPECT_TRUE(mosis::PathSandbox::IsAbsolutePath("/etc/passwd")); EXPECT_TRUE(mosis::PathSandbox::IsAbsolutePath("C:\\Windows\\System32")); EXPECT_TRUE(mosis::PathSandbox::IsAbsolutePath("D:/test/file.txt")); EXPECT_FALSE(mosis::PathSandbox::IsAbsolutePath("scripts/utils.lua")); EXPECT_FALSE(mosis::PathSandbox::IsAbsolutePath("./data/file.txt")); return true; } ``` ### Test 3: Accepts Valid Paths ```cpp bool Test_PathAcceptsValid(std::string& error_msg) { mosis::PathSandbox sandbox("D:/test/app"); std::string canonical; EXPECT_TRUE(sandbox.ValidatePath("scripts/utils.lua", canonical)); EXPECT_TRUE(sandbox.ValidatePath("data/config.json", canonical)); EXPECT_TRUE(sandbox.ValidatePath("./scripts/ui/button.lua", canonical)); return true; } ``` ### Test 4: Safe Require Loads Modules ```cpp bool Test_SafeRequireLoads(std::string& error_msg) { // Create sandbox with test scripts directory SandboxContext ctx = TestContext(); ctx.app_path = GetScriptsDir(); // Use scripts/ as app path LuaSandbox sandbox(ctx); // Should be able to require a test module std::string script = R"( local m = require("test_module") return m.value == 42 )"; EXPECT_TRUE(sandbox.LoadString(script, "require_test")); return true; } ``` ### Test 5: Safe Require Caches Modules ```cpp bool Test_SafeRequireCaches(std::string& error_msg) { SandboxContext ctx = TestContext(); ctx.app_path = GetScriptsDir(); LuaSandbox sandbox(ctx); std::string script = R"( local m1 = require("test_module") local m2 = require("test_module") return m1 == m2 -- Should be same table (cached) )"; EXPECT_TRUE(sandbox.LoadString(script, "cache_test")); return true; } ``` ### Test 6: Safe Require Rejects Invalid Names ```cpp bool Test_SafeRequireRejectsInvalid(std::string& error_msg) { SandboxContext ctx = TestContext(); ctx.app_path = GetScriptsDir(); LuaSandbox sandbox(ctx); // Should reject path traversal in module name EXPECT_FALSE(sandbox.LoadString("require('../evil')", "evil_require")); // Should reject absolute paths EXPECT_FALSE(sandbox.LoadString("require('/etc/passwd')", "abs_require")); // Should reject special characters EXPECT_FALSE(sandbox.LoadString("require('foo;bar')", "special_require")); return true; } ``` --- ## Acceptance Criteria All tests pass: - [x] `Test_PathRejectsTraversal` - Block .. traversal - [x] `Test_PathRejectsAbsolute` - Block absolute paths - [x] `Test_PathAcceptsValid` - Allow valid relative paths - [x] `Test_ModuleNameValidation` - Validate module names - [x] `Test_ModuleToPath` - Convert module names to paths - [x] `Test_SafeRequireLoads` - Load modules from scripts/ - [x] `Test_SafeRequireCaches` - Cache loaded modules - [x] `Test_SafeRequireRejectsInvalid` - Reject malicious require calls --- ## Next Steps After Milestone 4 passes: 1. Milestone 5: Timer & Callback System 2. Milestone 6: JSON & Crypto APIs