6.1 KiB
6.1 KiB
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
- PathSandbox class - Path validation and canonicalization
- SafeRequire function - Secure module loader
- 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
// path_sandbox.h
#pragma once
#include <string>
#include <filesystem>
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
// 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→ loadsscripts/utils.luaui.button→ loadsscripts/ui/button.lua
Test Cases
Test 1: Rejects Directory Traversal
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
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
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
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
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
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:
Test_PathRejectsTraversal- Block .. traversalTest_PathRejectsAbsolute- Block absolute pathsTest_PathAcceptsValid- Allow valid relative pathsTest_ModuleNameValidation- Validate module namesTest_ModuleToPath- Convert module names to pathsTest_SafeRequireLoads- Load modules from scripts/Test_SafeRequireCaches- Cache loaded modulesTest_SafeRequireRejectsInvalid- Reject malicious require calls
Next Steps
After Milestone 4 passes:
- Milestone 5: Timer & Callback System
- Milestone 6: JSON & Crypto APIs