Files
MosisService/docs/SANDBOX_MILESTONE_4.md

242 lines
6.1 KiB
Markdown

# 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 <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
```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