Files
MosisService/SANDBOX_MILESTONE_4.md

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

  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

// 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 → loads scripts/utils.lua
  • ui.button → loads scripts/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 .. traversal
  • Test_PathRejectsAbsolute - Block absolute paths
  • Test_PathAcceptsValid - Allow valid relative paths
  • Test_ModuleNameValidation - Validate module names
  • Test_ModuleToPath - Convert module names to paths
  • Test_SafeRequireLoads - Load modules from scripts/
  • Test_SafeRequireCaches - Cache loaded modules
  • Test_SafeRequireRejectsInvalid - Reject malicious require calls

Next Steps

After Milestone 4 passes:

  1. Milestone 5: Timer & Callback System
  2. Milestone 6: JSON & Crypto APIs