242 lines
6.1 KiB
Markdown
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
|