Files
MosisService/SANDBOX.md

106 KiB

Mosis Lua Sandbox Security

Status: Design Phase Goal: Secure app isolation with defense-in-depth approach.


Overview

Third-party apps run in isolated Lua environments with restricted access to system resources. Each app gets its own lua_State with carefully controlled APIs.

┌─────────────────────────────────────────────────────────────────────┐
│                         Mosis Kernel                                 │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │                    LuaSandboxManager                           │  │
│  │  - Creates per-app lua_State with custom allocator             │  │
│  │  - Enforces memory/CPU limits                                  │  │
│  │  - Routes permission-gated API calls                           │  │
│  └───────────────────────────────────────────────────────────────┘  │
│          │                    │                    │                 │
│          ▼                    ▼                    ▼                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │
│  │ App A State  │    │ App B State  │    │ App C State  │          │
│  │ (isolated)   │    │ (isolated)   │    │ (isolated)   │          │
│  └──────────────┘    └──────────────┘    └──────────────┘          │
│         │                   │                   │                   │
│         └───────────────────┴───────────────────┘                   │
│                             │                                        │
│                    ┌────────▼────────┐                              │
│                    │ Permission Gate │                              │
│                    └────────┬────────┘                              │
│                             │                                        │
│  ┌──────────────────────────▼──────────────────────────────────┐   │
│  │                    System Services                           │   │
│  │  Camera │ Network │ Storage │ Contacts │ Messages │ ...     │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Threat Model

Threat Categories

Category Threat Severity Mitigation
Code Execution os.execute(), io.popen() Critical Remove globals
File Access io.open(), loadfile() Critical Remove globals
Bytecode Injection load() with binary chunks Critical Text-only loading
Memory Exhaustion Infinite tables/strings High Custom allocator with limits
CPU Exhaustion Infinite loops High Instruction count hook
Sandbox Escape debug.getregistry() Critical Remove debug library
Global Pollution Modifying _G, string Medium Frozen globals
Path Traversal require("../../etc/passwd") Critical Path validation
Data Exfiltration Unauthorized network access High Permission-gated network
Privilege Escalation Access other app's data High Per-app storage isolation
Timing Attacks High-resolution timers Low Limit timer precision
Side Channels Memory/CPU usage patterns Low Rate limiting

Attacker Capabilities

We assume a malicious app developer can:

  • Write arbitrary Lua code within the sandbox
  • Attempt to exploit any exposed API
  • Try to escape the sandbox via Lua language features
  • Attempt DoS via resource exhaustion
  • Try to access other apps' data
  • Attempt to exfiltrate user data

Security Layers

Layer 1: Dangerous Globals Removal

Status: Designed

Remove all dangerous functions before any app code runs.

void RemoveDangerousGlobals(lua_State* L) {
    const char* dangerous[] = {
        // Execution
        "dofile", "loadfile", "load", "loadstring",

        // Raw access (bypass metatables)
        "rawget", "rawset", "rawequal", "rawlen",

        // Metatable manipulation
        "getmetatable", "setmetatable",

        // GC manipulation
        "collectgarbage",

        // Must not exist
        "os", "io", "debug", "package", "require",
        "ffi", "jit", "newproxy",

        nullptr
    };

    for (const char** p = dangerous; *p; ++p) {
        lua_pushnil(L);
        lua_setglobal(L, *p);
    }

    // Remove string.dump (creates bytecode)
    lua_getglobal(L, "string");
    lua_pushnil(L);
    lua_setfield(L, -2, "dump");
    lua_pop(L, 1);
}

Safe globals retained:

  • print (redirected to logger)
  • type, tonumber, tostring
  • pairs, ipairs, next
  • pcall, xpcall
  • assert, error
  • select, unpack
  • string.* (except dump)
  • table.*
  • math.*
  • utf8.*

Layer 2: Bytecode Prevention

Status: Designed

Prevent loading of binary Lua chunks which can bypass sandbox checks.

// Only allow text chunks, reject bytecode
int result = luaL_loadbufferx(L, code, len, name, "t");
//                                              mode: "t" = text only

Bytecode starts with \27Lua signature - the "t" mode rejects this.

Layer 3: Memory Limits

Status: Designed

Custom allocator tracks and limits memory usage per app.

void* SandboxAlloc(void* ud, void* ptr, size_t osize, size_t nsize) {
    auto* sandbox = static_cast<LuaSandbox*>(ud);

    size_t new_usage = sandbox->m_memory_used - osize + nsize;

    if (nsize > 0 && new_usage > sandbox->m_limits.memory_bytes) {
        return nullptr;  // Allocation fails
    }

    sandbox->m_memory_used = new_usage;

    if (nsize == 0) {
        free(ptr);
        return nullptr;
    }
    return realloc(ptr, nsize);
}

lua_State* L = lua_newstate(SandboxAlloc, sandbox);

Default limits:

Resource Limit Rationale
Memory 16 MB Enough for UI, prevents DoS
String size 1 MB Prevent single huge allocation
Table entries 100,000 Prevent hash DoS

Layer 4: CPU Limits

Status: Designed

Instruction count hook prevents infinite loops and excessive computation.

void InstructionHook(lua_State* L, lua_Debug* ar) {
    auto* sandbox = GetSandbox(L);
    sandbox->m_instructions += 1000;

    if (sandbox->m_instructions > sandbox->m_limits.instructions) {
        luaL_error(L, "instruction limit exceeded");
    }
}

lua_sethook(L, InstructionHook, LUA_MASKCOUNT, 1000);

Default limits:

Context Limit Approx Time
Event handler 1,000,000 ~10ms
Frame update 100,000 ~1ms
App initialization 10,000,000 ~100ms

Layer 5: Path Sandboxing

Status: Designed

All file access restricted to app's own directory.

bool ValidatePath(const std::string& app_path, const std::string& requested) {
    // Reject obvious traversal attempts
    if (requested.find("..") != std::string::npos) return false;
    if (requested.find(':') != std::string::npos) return false;  // Windows drive
    if (requested[0] == '/' || requested[0] == '\\') return false;  // Absolute

    // Canonicalize and verify within app directory
    std::filesystem::path base = std::filesystem::canonical(app_path);
    std::filesystem::path full = std::filesystem::weakly_canonical(
        base / requested
    );

    // Must be within app directory
    auto [base_end, full_it] = std::mismatch(
        base.begin(), base.end(), full.begin()
    );
    return base_end == base.end();
}

Layer 6: Safe require Replacement

Status: Designed

Custom module loader only loads from app's scripts directory.

int SafeRequire(lua_State* L) {
    const char* module = luaL_checkstring(L, 1);
    auto* sandbox = GetSandbox(L);

    // Validate module name
    std::string name(module);
    if (!IsValidModuleName(name)) {
        return luaL_error(L, "invalid module name");
    }

    // Check cache
    lua_getfield(L, LUA_REGISTRYINDEX, "__loaded");
    lua_getfield(L, -1, module);
    if (!lua_isnil(L, -1)) return 1;
    lua_pop(L, 2);

    // Load from app/scripts/name.lua
    std::string path = sandbox->app_path + "/scripts/" + name + ".lua";
    if (!ValidatePath(sandbox->app_path, "scripts/" + name + ".lua")) {
        return luaL_error(L, "path validation failed");
    }

    // Load as text only
    if (luaL_loadfilex(L, path.c_str(), "t") != LUA_OK) {
        return lua_error(L);
    }

    // Execute and cache
    lua_call(L, 0, 1);
    lua_getfield(L, LUA_REGISTRYINDEX, "__loaded");
    lua_pushvalue(L, -2);
    lua_setfield(L, -2, module);
    lua_pop(L, 1);

    return 1;
}

Layer 7: Metatable Protection

Status: Designed

Prevent modification of built-in metatables and sandbox escape via metatables.

void ProtectBuiltinTables(lua_State* L) {
    // Protect string metatable
    lua_pushstring(L, "");
    lua_getmetatable(L, -1);
    lua_pushstring(L, "string");
    lua_setfield(L, -2, "__metatable");  // Prevents getmetatable
    lua_pop(L, 2);

    // Freeze _G
    lua_pushglobaltable(L);
    lua_newtable(L);
    lua_pushstring(L, "globals");
    lua_setfield(L, -2, "__metatable");
    lua_pushcfunction(L, [](lua_State* L) -> int {
        return luaL_error(L, "cannot modify global environment");
    });
    lua_setfield(L, -2, "__newindex");
    lua_setmetatable(L, -2);
    lua_pop(L, 1);
}

Safe metatable access:

// getmetatable returns __metatable field (not real metatable)
// setmetatable blocked on protected tables

Layer 8: Permission-Gated APIs

Status: Designed

Sensitive APIs check permissions before execution.

class PermissionGate {
public:
    static bool Check(lua_State* L, const std::string& permission) {
        auto* sandbox = GetSandbox(L);
        auto& perms = sandbox->m_context.permissions;
        return std::find(perms.begin(), perms.end(), permission) != perms.end();
    }

    static int RequirePermission(lua_State* L, const char* perm) {
        if (!Check(L, perm)) {
            return luaL_error(L, "permission denied: %s", perm);
        }
        return 0;
    }
};

// Usage in API
int CameraCapture(lua_State* L) {
    PermissionGate::RequirePermission(L, "camera");
    // ... actual implementation
}

Permission categories:

Permission Required For Risk Level
storage App-private file access Normal (auto-granted)
camera Camera access Dangerous
microphone Audio recording Dangerous
location GPS/location Dangerous
contacts.read Read contacts Dangerous
contacts.write Modify contacts Dangerous
network HTTP requests Dangerous
network.websocket WebSocket connections Dangerous
bluetooth Bluetooth access Dangerous
system.notifications Show notifications Normal

Additional Security Measures

Network Request Sandboxing

Status: Planned

Network requests are proxied through the kernel with domain filtering.

struct NetworkPolicy {
    std::vector<std::string> allowed_domains;  // Whitelist
    std::vector<std::string> blocked_domains;  // Blacklist
    bool allow_localhost = false;
    bool allow_private_ips = false;
    size_t max_request_size = 10 * 1024 * 1024;   // 10 MB
    size_t max_response_size = 50 * 1024 * 1024;  // 50 MB
    int timeout_ms = 30000;
};

class NetworkSandbox {
public:
    bool ValidateRequest(const HttpRequest& req) {
        // Parse URL
        auto url = ParseUrl(req.url);

        // Block private IPs
        if (!m_policy.allow_private_ips && IsPrivateIP(url.host)) {
            return false;
        }

        // Block localhost
        if (!m_policy.allow_localhost && IsLocalhost(url.host)) {
            return false;
        }

        // Check domain whitelist/blacklist
        if (!m_policy.allowed_domains.empty()) {
            if (!MatchesDomain(url.host, m_policy.allowed_domains)) {
                return false;
            }
        }
        if (MatchesDomain(url.host, m_policy.blocked_domains)) {
            return false;
        }

        // Size limits
        if (req.body.size() > m_policy.max_request_size) {
            return false;
        }

        return true;
    }

private:
    bool IsPrivateIP(const std::string& host) {
        // 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x
        // Also check for IPv6 private ranges
    }
};

Manifest declaration:

{
  "permissions": ["network"],
  "network": {
    "allowed_domains": [
      "api.example.com",
      "*.cdn.example.com"
    ]
  }
}

Timer and Callback Security

Status: Planned

Timers and callbacks are managed by the kernel, not exposed directly.

class TimerManager {
public:
    uint32_t SetTimeout(LuaSandbox* sandbox, int callback_ref, int ms) {
        // Validate delay
        if (ms < 0) return 0;
        if (ms < 10) ms = 10;  // Minimum 10ms granularity

        // Limit active timers per app
        if (m_app_timers[sandbox->app_id()].size() >= MAX_TIMERS_PER_APP) {
            return 0;
        }

        Timer timer{
            .id = ++m_next_id,
            .sandbox = sandbox,
            .callback_ref = callback_ref,
            .fire_time = Now() + ms,
            .repeat = false
        };

        m_timers.push(timer);
        m_app_timers[sandbox->app_id()].insert(timer.id);
        return timer.id;
    }

    void ClearAppTimers(const std::string& app_id) {
        // Called when app is stopped
        for (uint32_t id : m_app_timers[app_id]) {
            // Mark timer as cancelled
        }
        m_app_timers.erase(app_id);
    }

private:
    static constexpr size_t MAX_TIMERS_PER_APP = 100;
};

Lua API:

-- Safe timer APIs (kernel-managed)
local id = setTimeout(function()
    print("fired!")
end, 1000)

clearTimeout(id)

local id = setInterval(function()
    print("tick")
end, 1000)

clearInterval(id)

Inter-App Communication

Status: Planned

Apps communicate through kernel-mediated message passing.

struct AppMessage {
    std::string from_app;
    std::string to_app;
    std::string action;
    std::string data;  // JSON
    std::vector<std::string> required_permissions;
};

class MessageBus {
public:
    bool Send(const AppMessage& msg) {
        // Validate sender
        if (!IsAppRunning(msg.from_app)) return false;

        // Check if target accepts this action
        if (!CanReceive(msg.to_app, msg.action)) return false;

        // Check permissions
        for (const auto& perm : msg.required_permissions) {
            if (!HasPermission(msg.from_app, perm)) return false;
        }

        // Deliver (async)
        QueueMessage(msg);
        return true;
    }
};

Manifest declaration:

{
  "intents": {
    "receive": [
      {
        "action": "share",
        "types": ["image/*", "text/plain"]
      }
    ],
    "send": [
      "share", "view"
    ]
  }
}

Audit Logging

Status: Planned

All security-relevant operations are logged for debugging and forensics.

enum class AuditEvent {
    AppStart,
    AppStop,
    PermissionCheck,
    PermissionDenied,
    NetworkRequest,
    FileAccess,
    StorageAccess,
    TimerCreated,
    MessageSent,
    SandboxViolation,
    ResourceLimitHit
};

struct AuditEntry {
    std::chrono::system_clock::time_point timestamp;
    std::string app_id;
    AuditEvent event;
    std::string detail;
    bool success;
};

class AuditLog {
public:
    void Log(const AuditEntry& entry) {
        // Thread-safe append
        std::lock_guard lock(m_mutex);
        m_entries.push_back(entry);

        // Trim old entries
        if (m_entries.size() > MAX_ENTRIES) {
            m_entries.erase(m_entries.begin(),
                           m_entries.begin() + TRIM_COUNT);
        }

        // Log security events to system log
        if (entry.event == AuditEvent::SandboxViolation ||
            entry.event == AuditEvent::PermissionDenied) {
            Logger::Warn("[SECURITY] {} - {} - {}",
                        entry.app_id, EventName(entry.event), entry.detail);
        }
    }

    std::vector<AuditEntry> GetAppLogs(const std::string& app_id, size_t limit);
};

Coroutine Safety

Status: Planned

Coroutines are allowed but resource-tracked.

void SetupSafeCoroutines(lua_State* L) {
    // Allow coroutine.* but track
    luaL_requiref(L, "coroutine", luaopen_coroutine, 1);
    lua_pop(L, 1);

    // Wrap coroutine.create to track
    lua_getglobal(L, "coroutine");
    lua_getfield(L, -1, "create");
    lua_pushcclosure(L, [](lua_State* L) -> int {
        auto* sandbox = GetSandbox(L);

        // Limit active coroutines
        if (sandbox->m_coroutine_count >= MAX_COROUTINES) {
            return luaL_error(L, "too many coroutines");
        }

        // Call original create
        lua_pushvalue(L, lua_upvalueindex(1));
        lua_pushvalue(L, 1);
        lua_call(L, 1, 1);

        sandbox->m_coroutine_count++;
        return 1;
    }, 1);
    lua_setfield(L, -2, "create");
    lua_pop(L, 1);
}

String Pattern DoS Prevention

Status: Planned

Complex regex patterns can cause catastrophic backtracking.

int SafeStringMatch(lua_State* L) {
    size_t slen, plen;
    const char* s = luaL_checklstring(L, 1, &slen);
    const char* p = luaL_checklstring(L, 2, &plen);

    // Limit pattern complexity
    if (plen > 256) {
        return luaL_error(L, "pattern too long");
    }

    // Count pattern operators
    int complexity = 0;
    for (size_t i = 0; i < plen; i++) {
        if (p[i] == '*' || p[i] == '+' || p[i] == '-' || p[i] == '?') {
            complexity++;
        }
    }

    // Reject overly complex patterns
    if (complexity > 10) {
        return luaL_error(L, "pattern too complex");
    }

    // Also limit based on string length * complexity
    if (slen * complexity > 1000000) {
        return luaL_error(L, "operation too expensive");
    }

    // Call original
    return original_match(L);
}

Runtime Security Audit

Audit Function

std::vector<SecurityIssue> AuditSandbox(lua_State* L) {
    std::vector<SecurityIssue> issues;

    // Check dangerous globals
    const char* dangerous[] = {
        "os", "io", "debug", "package", "ffi", "jit",
        "dofile", "loadfile", "load", "loadstring",
        "rawget", "rawset", "rawequal", "rawlen",
        "collectgarbage", "newproxy",
        nullptr
    };

    for (const char** p = dangerous; *p; ++p) {
        lua_getglobal(L, *p);
        if (!lua_isnil(L, -1)) {
            issues.push_back({
                .severity = Severity::Critical,
                .type = "dangerous_global",
                .detail = *p
            });
        }
        lua_pop(L, 1);
    }

    // Check string.dump
    lua_getglobal(L, "string");
    if (lua_istable(L, -1)) {
        lua_getfield(L, -1, "dump");
        if (!lua_isnil(L, -1)) {
            issues.push_back({
                .severity = Severity::Critical,
                .type = "bytecode_dump",
                .detail = "string.dump exists"
            });
        }
        lua_pop(L, 1);
    }
    lua_pop(L, 1);

    // Check instruction hook
    if (lua_gethook(L) == nullptr) {
        issues.push_back({
            .severity = Severity::High,
            .type = "no_cpu_limit",
            .detail = "instruction hook not set"
        });
    }

    // Check allocator (verify it's our sandbox allocator)
    // ...

    return issues;
}

Startup Audit

bool LuaSandbox::Initialize() {
    SetupSandbox();

    // Audit before running any app code
    auto issues = AuditSandbox(L);
    for (const auto& issue : issues) {
        if (issue.severity == Severity::Critical) {
            Logger::Error("Sandbox audit failed: {} - {}",
                         issue.type, issue.detail);
            return false;
        }
    }

    return true;
}

Testing Strategy

Unit Tests

// test_sandbox_security.cpp

TEST(LuaSandbox, DangerousGlobalsRemoved) {
    LuaSandbox sandbox(TestContext());

    EXPECT_FALSE(sandbox.LoadString("return os"));
    EXPECT_FALSE(sandbox.LoadString("return io"));
    EXPECT_FALSE(sandbox.LoadString("return debug"));
    EXPECT_FALSE(sandbox.LoadString("os.execute('ls')"));
    EXPECT_FALSE(sandbox.LoadString("io.open('/etc/passwd')"));
}

TEST(LuaSandbox, BytecodeRejected) {
    LuaSandbox sandbox(TestContext());

    // Try to load bytecode
    EXPECT_FALSE(sandbox.LoadString("\x1bLuaS\x00..."));
    EXPECT_TRUE(sandbox.GetLastError().find("binary") != std::string::npos);
}

TEST(LuaSandbox, MemoryLimitEnforced) {
    SandboxLimits limits;
    limits.memory_limit_bytes = 1024 * 1024;  // 1 MB

    LuaSandbox sandbox(TestContext(limits));

    EXPECT_FALSE(sandbox.LoadString(R"(
        local t = {}
        while true do
            t[#t+1] = string.rep('x', 100000)
        end
    )"));
}

TEST(LuaSandbox, CPULimitEnforced) {
    SandboxLimits limits;
    limits.instruction_limit = 10000;

    LuaSandbox sandbox(TestContext(limits));

    EXPECT_FALSE(sandbox.LoadString("while true do end"));
    EXPECT_TRUE(sandbox.GetLastError().find("instruction") != std::string::npos);
}

TEST(LuaSandbox, PathTraversalBlocked) {
    LuaSandbox sandbox(TestContext());

    EXPECT_FALSE(sandbox.LoadString("require('../../../etc/passwd')"));
    EXPECT_FALSE(sandbox.LoadString("require('..\\\\..\\\\..\\\\windows\\\\system32\\\\config')"));
    EXPECT_FALSE(sandbox.LoadString("require('/etc/passwd')"));
    EXPECT_FALSE(sandbox.LoadString("require('C:\\\\Windows\\\\System32\\\\config')"));
}

TEST(LuaSandbox, MetatableProtected) {
    LuaSandbox sandbox(TestContext());

    // Cannot access string metatable internals
    EXPECT_TRUE(sandbox.LoadString(R"(
        local mt = getmetatable("")
        assert(mt == "string", "metatable should be protected")
    )"));

    // Cannot modify globals
    EXPECT_FALSE(sandbox.LoadString("_G.print = nil"));
}

TEST(LuaSandbox, SafeOperationsWork) {
    LuaSandbox sandbox(TestContext());

    EXPECT_TRUE(sandbox.LoadString(R"(
        -- Math
        local x = math.sin(1.5) + math.floor(3.7)

        -- String
        local s = string.format("hello %d", 42)
        local upper = string.upper("test")

        -- Table
        local t = {1, 2, 3}
        table.insert(t, 4)
        table.sort(t)

        -- Iteration
        for i, v in ipairs(t) do end
        for k, v in pairs({a=1, b=2}) do end

        -- Error handling
        local ok, err = pcall(function() error("test") end)
        assert(not ok)

        -- Type checks
        assert(type({}) == "table")
        assert(type("") == "string")
    )"));
}

Fuzzing

// Fuzz the sandbox with random Lua code
void FuzzSandbox() {
    std::random_device rd;
    std::mt19937 gen(rd());

    for (int i = 0; i < 10000; i++) {
        std::string code = GenerateRandomLuaCode(gen);

        LuaSandbox sandbox(TestContext());
        sandbox.LoadString(code);  // Should never crash

        // Verify sandbox integrity after each run
        auto issues = AuditSandbox(sandbox.GetState());
        ASSERT(issues.empty()) << "Sandbox compromised by: " << code;
    }
}

Implementation Status

Core Sandbox (Milestone 1)

Component Status File
LuaSandbox class Implemented src/main/cpp/sandbox/lua_sandbox.h/cpp
Custom allocator Implemented src/main/cpp/sandbox/lua_sandbox.cpp
Instruction hook Implemented src/main/cpp/sandbox/lua_sandbox.cpp
Globals removal Implemented src/main/cpp/sandbox/lua_sandbox.cpp
Bytecode prevention Implemented src/main/cpp/sandbox/lua_sandbox.cpp
Safe require Planned Milestone 4
Metatable protection Implemented src/main/cpp/sandbox/lua_sandbox.cpp
Path validation Planned Milestone 4

APIs

Component Status File
Storage API Planned Milestone 7
Timer API Planned Milestone 5
Network API Planned Milestone 9-10
RmlUi bridge Planned Milestone 20

Security

Component Status File
Permission gate Planned Milestone 2
Audit logging Planned Milestone 3
Network sandbox Planned Milestone 9
Message bus Planned Milestone 18

Testing

Component Status File
Unit tests Implemented sandbox-test/ (11 tests)
Integration tests Planned Milestone 19
Fuzzing Planned Milestone 19

Additional Security Measures (Continued)

JSON Parsing Security

Status: Planned

Untrusted JSON from network/storage must be safely parsed.

struct JsonLimits {
    size_t max_depth = 32;
    size_t max_string_length = 1024 * 1024;  // 1 MB
    size_t max_array_elements = 100000;
    size_t max_object_keys = 10000;
};

int SafeJsonDecode(lua_State* L) {
    size_t len;
    const char* json = luaL_checklstring(L, 1, &len);

    // Size check
    if (len > 10 * 1024 * 1024) {  // 10 MB max
        return luaL_error(L, "JSON too large");
    }

    auto* sandbox = GetSandbox(L);

    try {
        auto parsed = ParseJsonWithLimits(json, len, sandbox->json_limits);
        PushJsonValue(L, parsed);
        return 1;
    } catch (const JsonDepthError&) {
        return luaL_error(L, "JSON nesting too deep");
    } catch (const JsonSizeError&) {
        return luaL_error(L, "JSON element too large");
    }
}

Lua API:

local data = json.decode(json_string)  -- Safe parsing
local str = json.encode(table)          -- Safe encoding

Cryptographic APIs

Status: Planned

Provide safe crypto primitives (apps shouldn't implement their own).

void SetupCryptoAPI(lua_State* L) {
    lua_newtable(L);

    // crypto.randomBytes(n) - cryptographically secure random
    lua_pushcfunction(L, [](lua_State* L) -> int {
        int n = luaL_checkinteger(L, 1);
        if (n < 1 || n > 1024) {
            return luaL_error(L, "invalid byte count (1-1024)");
        }

        std::vector<uint8_t> bytes(n);
        if (!CSPRNG(bytes.data(), n)) {
            return luaL_error(L, "random generation failed");
        }

        lua_pushlstring(L, reinterpret_cast<char*>(bytes.data()), n);
        return 1;
    });
    lua_setfield(L, -2, "randomBytes");

    // crypto.hash(algorithm, data) - SHA-256, SHA-512
    lua_pushcfunction(L, [](lua_State* L) -> int {
        const char* algo = luaL_checkstring(L, 1);
        size_t len;
        const char* data = luaL_checklstring(L, 2, &len);

        if (strcmp(algo, "sha256") == 0) {
            auto hash = SHA256(data, len);
            lua_pushlstring(L, hash.data(), hash.size());
            return 1;
        } else if (strcmp(algo, "sha512") == 0) {
            auto hash = SHA512(data, len);
            lua_pushlstring(L, hash.data(), hash.size());
            return 1;
        }

        return luaL_error(L, "unknown algorithm: %s", algo);
    });
    lua_setfield(L, -2, "hash");

    // crypto.hmac(algorithm, key, data)
    // crypto.encrypt(algorithm, key, iv, plaintext) - AES-256-GCM only
    // crypto.decrypt(algorithm, key, iv, ciphertext)

    lua_setglobal(L, "crypto");
}

Random Number Security

Status: Planned

math.random() uses a per-app seed to prevent cross-app prediction.

void SetupSafeRandom(lua_State* L) {
    auto* sandbox = GetSandbox(L);

    // Seed with cryptographic random + app ID hash
    uint64_t seed;
    CSPRNG(&seed, sizeof(seed));
    seed ^= std::hash<std::string>{}(sandbox->app_id);

    // Store RNG state in registry
    auto* rng = new std::mt19937_64(seed);
    lua_pushlightuserdata(L, rng);
    lua_setfield(L, LUA_REGISTRYINDEX, "__rng");

    // Replace math.random
    lua_getglobal(L, "math");
    lua_pushcfunction(L, [](lua_State* L) -> int {
        lua_getfield(L, LUA_REGISTRYINDEX, "__rng");
        auto* rng = static_cast<std::mt19937_64*>(lua_touserdata(L, -1));
        lua_pop(L, 1);

        int nargs = lua_gettop(L);
        if (nargs == 0) {
            // [0, 1)
            std::uniform_real_distribution<double> dist(0.0, 1.0);
            lua_pushnumber(L, dist(*rng));
        } else if (nargs == 1) {
            // [1, n]
            int n = luaL_checkinteger(L, 1);
            std::uniform_int_distribution<int> dist(1, n);
            lua_pushinteger(L, dist(*rng));
        } else {
            // [m, n]
            int m = luaL_checkinteger(L, 1);
            int n = luaL_checkinteger(L, 2);
            std::uniform_int_distribution<int> dist(m, n);
            lua_pushinteger(L, dist(*rng));
        }
        return 1;
    });
    lua_setfield(L, -2, "random");

    // Remove math.randomseed (prevent manipulation)
    lua_pushnil(L);
    lua_setfield(L, -2, "randomseed");

    lua_pop(L, 1);
}

Environment Fingerprinting Prevention

Status: Planned

Limit information leakage about device/user.

void SetupEnvironmentAPI(lua_State* L) {
    lua_newtable(L);

    // Only expose safe, non-identifying info
    lua_pushstring(L, "1.0.0");
    lua_setfield(L, -2, "mosis_version");

    lua_pushinteger(L, 540);
    lua_setfield(L, -2, "screen_width");

    lua_pushinteger(L, 960);
    lua_setfield(L, -2, "screen_height");

    // NOT exposed:
    // - Device model
    // - OS version
    // - Unique identifiers
    // - Installed apps list
    // - Precise time (only second precision)
    // - Memory/CPU info
    // - Network interfaces

    lua_setglobal(L, "env");
}

// Time with reduced precision
int SafeTime(lua_State* L) {
    // Round to seconds (no milliseconds)
    time_t now = time(nullptr);
    lua_pushinteger(L, static_cast<lua_Integer>(now));
    return 1;
}

Rate Limiting

Status: Planned

Prevent abuse of expensive operations.

class RateLimiter {
public:
    struct Limit {
        int max_calls;
        std::chrono::milliseconds window;
    };

    bool Check(const std::string& operation) {
        auto now = std::chrono::steady_clock::now();
        auto& bucket = m_buckets[operation];

        // Remove old entries
        while (!bucket.empty() &&
               now - bucket.front() > m_limits[operation].window) {
            bucket.pop_front();
        }

        // Check limit
        if (bucket.size() >= m_limits[operation].max_calls) {
            return false;
        }

        bucket.push_back(now);
        return true;
    }

private:
    std::unordered_map<std::string, Limit> m_limits = {
        {"network.request", {100, std::chrono::minutes(1)}},
        {"storage.write", {1000, std::chrono::minutes(1)}},
        {"crypto.hash", {1000, std::chrono::seconds(1)}},
        {"timer.create", {100, std::chrono::seconds(1)}},
    };

    std::unordered_map<std::string, std::deque<TimePoint>> m_buckets;
};

// Usage
int NetworkRequest(lua_State* L) {
    auto* sandbox = GetSandbox(L);
    if (!sandbox->rate_limiter.Check("network.request")) {
        return luaL_error(L, "rate limit exceeded");
    }
    // ... proceed with request
}

Resource Cleanup on Termination

Status: Planned

Ensure all resources are released when app stops.

class LuaSandbox {
public:
    ~LuaSandbox() {
        Cleanup();
    }

    void Cleanup() {
        // Cancel all timers
        m_timer_manager->CancelAll(m_context.app_id);

        // Close all network connections
        m_network_manager->CloseAll(m_context.app_id);

        // Release all file handles
        m_file_manager->CloseAll(m_context.app_id);

        // Clear message queue
        m_message_bus->ClearPending(m_context.app_id);

        // Release Lua callbacks
        for (int ref : m_callback_refs) {
            luaL_unref(L, LUA_REGISTRYINDEX, ref);
        }

        // Close Lua state (releases memory)
        if (L) {
            lua_close(L);
            L = nullptr;
        }

        // Log cleanup
        AuditLog::Log({
            .app_id = m_context.app_id,
            .event = AuditEvent::AppStop,
            .detail = "cleanup completed"
        });
    }

private:
    std::vector<int> m_callback_refs;
};

App Signature Verification

Status: Planned

Verify app packages haven't been tampered with.

struct AppSignature {
    std::string algorithm;  // "ed25519"
    std::string public_key; // Base64
    std::string signature;  // Base64
};

class PackageVerifier {
public:
    bool Verify(const std::string& mpkg_path) {
        // Read manifest
        auto manifest = ReadManifest(mpkg_path);
        if (!manifest) return false;

        // Check signature exists
        if (!manifest->signature) {
            // Unsigned packages allowed in dev mode only
            return IsDevMode();
        }

        // Get developer's public key from store/registry
        auto pub_key = GetDeveloperKey(manifest->author_id);
        if (!pub_key) return false;

        // Compute package hash (all files except signature)
        auto hash = ComputePackageHash(mpkg_path);

        // Verify signature
        return VerifyEd25519(
            pub_key->data(),
            hash.data(), hash.size(),
            Base64Decode(manifest->signature->signature)
        );
    }

private:
    std::vector<uint8_t> ComputePackageHash(const std::string& path) {
        // Hash all files in deterministic order
        SHA256_CTX ctx;
        SHA256_Init(&ctx);

        for (const auto& file : ListFilesRecursive(path)) {
            if (file.name == "signature.json") continue;

            // Include filename in hash
            SHA256_Update(&ctx, file.name.data(), file.name.size());

            // Include content
            auto content = ReadFile(file.path);
            SHA256_Update(&ctx, content.data(), content.size());
        }

        std::vector<uint8_t> hash(32);
        SHA256_Final(hash.data(), &ctx);
        return hash;
    }
};

Debug Mode Restrictions

Status: Planned

Development features disabled in production.

class DebugMode {
public:
    static bool IsEnabled() {
        #ifdef NDEBUG
            return false;
        #else
            return s_debug_enabled;
        #endif
    }

    // Only available in debug mode
    static void EnableHotReload(LuaSandbox* sandbox) {
        if (!IsEnabled()) return;
        // ...
    }

    static void EnableInspector(LuaSandbox* sandbox) {
        if (!IsEnabled()) return;
        // ...
    }

    // Log debug state for security audit
    static void LogDebugState() {
        if (IsEnabled()) {
            Logger::Warn("[SECURITY] Debug mode enabled - "
                        "sandbox restrictions relaxed");
        }
    }

private:
    static bool s_debug_enabled;
};

// In production builds, these are completely compiled out
#ifdef NDEBUG
    #define DEBUG_ONLY(x) ((void)0)
#else
    #define DEBUG_ONLY(x) x
#endif

Clipboard Isolation

Status: Planned

Apps can only access clipboard with permission and user gesture.

class ClipboardManager {
public:
    // Requires permission + recent user gesture
    std::optional<std::string> Read(LuaSandbox* sandbox) {
        if (!sandbox->HasPermission("clipboard.read")) {
            return std::nullopt;
        }

        // Must be within 1 second of user gesture (click/tap)
        if (!sandbox->HasRecentUserGesture(1000)) {
            Logger::Warn("Clipboard read blocked - no recent user gesture");
            return std::nullopt;
        }

        return GetSystemClipboard();
    }

    bool Write(LuaSandbox* sandbox, const std::string& text) {
        if (!sandbox->HasPermission("clipboard.write")) {
            return false;
        }

        // Size limit
        if (text.size() > 1024 * 1024) {
            return false;
        }

        SetSystemClipboard(text);
        return true;
    }
};

Stack Depth Limiting

Status: Planned

Prevent stack overflow via deep recursion.

void SetupStackLimit(lua_State* L, int max_depth) {
    // Set C stack limit
    // Note: Lua 5.4 has LUAI_MAXCSTACK

    // Also use debug hook to check Lua call depth
    lua_sethook(L, [](lua_State* L, lua_Debug* ar) {
        lua_Debug info;
        int depth = 0;

        while (lua_getstack(L, depth, &info)) {
            depth++;
        }

        auto* sandbox = GetSandbox(L);
        if (depth > sandbox->m_limits.stack_depth) {
            luaL_error(L, "stack overflow (depth %d)", depth);
        }
    }, LUA_MASKCALL, 0);
}

Security Testing Checklist

Before Release

  • All dangerous globals removed
  • Bytecode loading blocked
  • Memory limits enforced
  • CPU limits enforced
  • Path traversal blocked
  • Metatable protection working
  • Permission checks on all APIs
  • Network domain filtering working
  • Rate limiting active
  • Audit logging enabled
  • Debug mode disabled in release
  • Fuzzing completed with no crashes
  • Security audit by external party

Per-App Review (Store Submission)

  • Manifest permissions justified
  • No obfuscated code
  • Network domains declared
  • Package signature valid
  • No known vulnerability patterns
  • Resource usage reasonable

Virtual Hardware Interfaces

Mosis provides virtualized hardware interfaces that apps can access through permission-gated APIs. Each interface has specific security requirements based on the sensitivity of the data it exposes.

Hardware Permission Model

┌─────────────────────────────────────────────────────────────────────┐
│                      Permission Categories                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  NORMAL (auto-granted)         DANGEROUS (user prompt required)     │
│  ├── storage.app               ├── camera                           │
│  ├── network.metadata          ├── microphone                       │
│  ├── system.vibrate            ├── location.fine                    │
│  └── audio.playback            ├── location.coarse                  │
│                                ├── contacts.read                    │
│  SIGNATURE (system apps only)  ├── contacts.write                   │
│  ├── system.settings           ├── storage.shared                   │
│  ├── app.install               ├── network.internet                 │
│  └── hardware.control          ├── bluetooth                        │
│                                ├── phone.call                       │
│                                ├── sms.send                         │
│                                └── sensors.body                     │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Hardware Security Threat Model

Interface Threat Impact Mitigation
Camera Silent recording Critical privacy Indicator, gesture gate
Microphone Eavesdropping Critical privacy Indicator, gesture gate
Location Tracking High privacy Precision reduction, rate limit
Network Data exfiltration High privacy Domain filtering, inspection
Filesystem Data theft/corruption High integrity Path sandboxing
Database SQL injection High integrity Prepared statements only
Bluetooth Device tracking Medium privacy Pairing consent
Sensors Fingerprinting Medium privacy Reduced precision
Speaker Audio spam Low annoyance Volume limits, rate limit

Network Interface Security

Network Sandbox Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         App Lua Code                                 │
│                              │                                       │
│                     network.request(url, opts)                       │
│                              │                                       │
├──────────────────────────────▼──────────────────────────────────────┤
│                      NetworkSandbox                                  │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────────────┐ │
│  │ Permission  │  │   Domain     │  │    Content                 │ │
│  │   Check     │→ │  Validation  │→ │    Inspection              │ │
│  └─────────────┘  └──────────────┘  └────────────────────────────┘ │
│                              │                                       │
│                    ┌─────────▼──────────┐                           │
│                    │    Rate Limiter    │                           │
│                    └─────────┬──────────┘                           │
│                              │                                       │
├──────────────────────────────▼──────────────────────────────────────┤
│                      System HTTP Client                              │
│                  (libcurl / Android HttpClient)                      │
└─────────────────────────────────────────────────────────────────────┘

HTTP Request Security

Status: Designed

struct HttpRequestPolicy {
    // Domain restrictions
    std::vector<std::string> allowed_domains;   // Whitelist from manifest
    std::vector<std::string> blocked_domains;   // System blacklist
    bool allow_localhost = false;               // Block 127.0.0.1, ::1
    bool allow_private_ips = false;             // Block 10.x, 192.168.x, etc.
    bool allow_metadata_ips = false;            // Block 169.254.x (cloud metadata)

    // Protocol restrictions
    bool require_https = true;                  // Block plain HTTP
    std::vector<std::string> allowed_schemes = {"https"};

    // Size limits
    size_t max_request_body = 10 * 1024 * 1024;   // 10 MB
    size_t max_response_body = 50 * 1024 * 1024;  // 50 MB
    size_t max_header_size = 64 * 1024;           // 64 KB

    // Timeout
    int connect_timeout_ms = 10000;
    int read_timeout_ms = 30000;
    int total_timeout_ms = 60000;

    // Rate limits
    int max_concurrent_requests = 6;
    int max_requests_per_minute = 100;
};

class HttpRequestValidator {
public:
    enum class Result {
        Allowed,
        PermissionDenied,
        DomainBlocked,
        PrivateIPBlocked,
        MetadataIPBlocked,
        LocalhostBlocked,
        InsecureProtocol,
        RequestTooLarge,
        RateLimitExceeded
    };

    Result Validate(const HttpRequest& req, const AppContext& ctx) {
        // 1. Permission check
        if (!ctx.HasPermission("network.internet")) {
            return Result::PermissionDenied;
        }

        // 2. Parse URL
        auto url = ParseUrl(req.url);
        if (!url) return Result::DomainBlocked;

        // 3. Protocol check
        if (m_policy.require_https && url->scheme != "https") {
            return Result::InsecureProtocol;
        }

        // 4. Resolve hostname to IP (detect IP-based bypass)
        auto ips = ResolveHostname(url->host);

        // 5. Block private IPs
        for (const auto& ip : ips) {
            if (IsPrivateIP(ip) && !m_policy.allow_private_ips) {
                return Result::PrivateIPBlocked;
            }
            if (IsMetadataIP(ip) && !m_policy.allow_metadata_ips) {
                return Result::MetadataIPBlocked;
            }
            if (IsLocalhost(ip) && !m_policy.allow_localhost) {
                return Result::LocalhostBlocked;
            }
        }

        // 6. Domain whitelist/blacklist
        if (!m_policy.allowed_domains.empty()) {
            if (!MatchesDomainList(url->host, m_policy.allowed_domains)) {
                return Result::DomainBlocked;
            }
        }
        if (MatchesDomainList(url->host, m_policy.blocked_domains)) {
            return Result::DomainBlocked;
        }

        // 7. Size check
        if (req.body.size() > m_policy.max_request_body) {
            return Result::RequestTooLarge;
        }

        // 8. Rate limit
        if (!m_rate_limiter.Check(ctx.app_id, "http_request")) {
            return Result::RateLimitExceeded;
        }

        return Result::Allowed;
    }

private:
    bool IsPrivateIP(const std::string& ip) {
        // IPv4 private ranges
        if (ip.starts_with("10.")) return true;
        if (ip.starts_with("172.")) {
            int second_octet = std::stoi(ip.substr(4, ip.find('.', 4) - 4));
            if (second_octet >= 16 && second_octet <= 31) return true;
        }
        if (ip.starts_with("192.168.")) return true;

        // Link-local
        if (ip.starts_with("169.254.")) return true;

        // IPv6 private ranges
        if (ip.starts_with("fc") || ip.starts_with("fd")) return true;  // ULA
        if (ip.starts_with("fe80:")) return true;  // Link-local

        return false;
    }

    bool IsMetadataIP(const std::string& ip) {
        // AWS/GCP/Azure metadata endpoints
        return ip == "169.254.169.254" ||
               ip == "metadata.google.internal" ||
               ip.starts_with("fd00:ec2::");
    }
};

WebSocket Security

Status: Designed

struct WebSocketPolicy {
    bool enabled = true;
    int max_connections_per_app = 5;
    size_t max_message_size = 1 * 1024 * 1024;  // 1 MB
    int ping_interval_ms = 30000;
    int idle_timeout_ms = 300000;  // 5 minutes

    // Same domain validation as HTTP
    std::vector<std::string> allowed_origins;
};

class WebSocketManager {
public:
    struct Connection {
        uint32_t id;
        std::string app_id;
        std::string url;
        ConnectionState state;
        std::chrono::steady_clock::time_point created_at;
        std::chrono::steady_clock::time_point last_activity;
        size_t bytes_sent = 0;
        size_t bytes_received = 0;
    };

    std::optional<uint32_t> Connect(LuaSandbox* sandbox, const std::string& url) {
        // Permission check
        if (!sandbox->HasPermission("network.websocket")) {
            return std::nullopt;
        }

        // Validate URL (same as HTTP)
        auto result = m_validator.Validate({.url = url}, sandbox->context());
        if (result != HttpRequestValidator::Result::Allowed) {
            return std::nullopt;
        }

        // Connection limit
        if (CountAppConnections(sandbox->app_id()) >= m_policy.max_connections_per_app) {
            return std::nullopt;
        }

        // Create connection
        auto conn = CreateConnection(url);
        conn.app_id = sandbox->app_id();

        m_connections[conn.id] = conn;
        return conn.id;
    }

    bool Send(LuaSandbox* sandbox, uint32_t conn_id, const std::string& data) {
        auto it = m_connections.find(conn_id);
        if (it == m_connections.end()) return false;

        // Verify ownership
        if (it->second.app_id != sandbox->app_id()) return false;

        // Size limit
        if (data.size() > m_policy.max_message_size) return false;

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "ws_send")) return false;

        // Send
        it->second.bytes_sent += data.size();
        it->second.last_activity = std::chrono::steady_clock::now();
        return DoSend(conn_id, data);
    }

    void CloseAll(const std::string& app_id) {
        // Called when app stops
        for (auto it = m_connections.begin(); it != m_connections.end(); ) {
            if (it->second.app_id == app_id) {
                DoClose(it->first);
                it = m_connections.erase(it);
            } else {
                ++it;
            }
        }
    }

private:
    std::unordered_map<uint32_t, Connection> m_connections;
    WebSocketPolicy m_policy;
    HttpRequestValidator m_validator;
    RateLimiter m_rate_limiter;
};

Network Lua API

-- HTTP Requests (requires "network.internet" permission)
local response = network.request({
    url = "https://api.example.com/data",
    method = "POST",  -- GET, POST, PUT, DELETE, PATCH
    headers = {
        ["Content-Type"] = "application/json",
        ["Authorization"] = "Bearer " .. token
    },
    body = json.encode({key = "value"}),
    timeout = 30000  -- milliseconds
})

-- Response structure
-- response.status: number (200, 404, etc.)
-- response.headers: table
-- response.body: string
-- response.error: string (nil if success)

-- WebSocket (requires "network.websocket" permission)
local ws = network.websocket("wss://api.example.com/ws")

ws:on("open", function()
    ws:send(json.encode({type = "hello"}))
end)

ws:on("message", function(data)
    local msg = json.decode(data)
    print("Received:", msg.type)
end)

ws:on("close", function(code, reason)
    print("Closed:", code, reason)
end)

ws:on("error", function(err)
    print("Error:", err)
end)

ws:close()

Manifest Declaration

{
  "permissions": ["network.internet", "network.websocket"],
  "network": {
    "allowed_domains": [
      "api.example.com",
      "*.cdn.example.com",
      "wss://realtime.example.com"
    ],
    "allow_http": false,
    "max_connections": 10
  }
}

Filesystem Interface Security

Virtual Filesystem Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         Physical Storage                             │
│  /data/data/com.omixlab.mosis/                                      │
│  ├── apps/                                                           │
│  │   ├── com.example.app1/                                          │
│  │   │   ├── data/           ← App-private storage                  │
│  │   │   ├── cache/          ← Clearable cache                      │
│  │   │   └── temp/           ← Session-only                         │
│  │   └── com.example.app2/                                          │
│  ├── shared/                                                         │
│  │   ├── photos/             ← Shared media (permission required)   │
│  │   ├── downloads/                                                  │
│  │   └── documents/                                                  │
│  └── system/                 ← System apps only                      │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                            ┌───────▼───────┐
                            │ VirtualFS API │
                            └───────┬───────┘
                                    │
┌───────────────────────────────────▼─────────────────────────────────┐
│                        App's Virtual View                            │
│  /                                                                   │
│  ├── data/        → /apps/<app_id>/data/     (auto-granted)         │
│  ├── cache/       → /apps/<app_id>/cache/    (auto-granted)         │
│  ├── temp/        → /apps/<app_id>/temp/     (auto-granted)         │
│  ├── shared/      → /shared/                 (permission required)  │
│  └── (nothing else visible)                                          │
└─────────────────────────────────────────────────────────────────────┘

Filesystem Security Implementation

Status: Designed

struct FilesystemPolicy {
    // Storage quotas
    size_t max_app_storage = 100 * 1024 * 1024;    // 100 MB per app
    size_t max_cache_storage = 50 * 1024 * 1024;   // 50 MB cache
    size_t max_file_size = 50 * 1024 * 1024;       // 50 MB single file

    // File limits
    size_t max_files_per_directory = 10000;
    size_t max_path_depth = 32;
    size_t max_filename_length = 255;

    // Allowed extensions (whitelist approach)
    std::vector<std::string> allowed_extensions = {
        ".txt", ".json", ".xml", ".html", ".css", ".js", ".lua",
        ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg",
        ".mp3", ".ogg", ".wav",
        ".mp4", ".webm",
        ".db", ".sqlite"
    };

    // Blocked patterns
    std::vector<std::string> blocked_patterns = {
        "*.exe", "*.dll", "*.so", "*.dylib",  // Executables
        "*.sh", "*.bat", "*.cmd", "*.ps1",     // Scripts
        ".*",                                   // Hidden files
    };
};

class VirtualFilesystem {
public:
    VirtualFilesystem(const std::string& app_id, const std::string& base_path)
        : m_app_id(app_id)
        , m_base_path(base_path)
        , m_app_root(base_path + "/apps/" + app_id) {}

    // Resolve virtual path to physical path with security checks
    std::optional<std::string> ResolvePath(const std::string& virtual_path,
                                           AccessMode mode) {
        // 1. Validate path format
        if (!IsValidPathFormat(virtual_path)) {
            AuditLog::Log({m_app_id, AuditEvent::FileAccess,
                          "invalid path format: " + virtual_path, false});
            return std::nullopt;
        }

        // 2. Parse virtual path
        auto parts = SplitPath(virtual_path);
        if (parts.empty()) return std::nullopt;

        // 3. Determine physical root based on virtual root
        std::string physical_root;
        bool requires_permission = false;

        if (parts[0] == "data") {
            physical_root = m_app_root + "/data";
        } else if (parts[0] == "cache") {
            physical_root = m_app_root + "/cache";
        } else if (parts[0] == "temp") {
            physical_root = m_app_root + "/temp";
        } else if (parts[0] == "shared") {
            physical_root = m_base_path + "/shared";
            requires_permission = true;
        } else {
            // Unknown root - access denied
            return std::nullopt;
        }

        // 4. Permission check for shared storage
        if (requires_permission) {
            std::string perm = (mode == AccessMode::Read)
                ? "storage.shared.read"
                : "storage.shared.write";
            if (!HasPermission(perm)) {
                AuditLog::Log({m_app_id, AuditEvent::PermissionDenied, perm, false});
                return std::nullopt;
            }
        }

        // 5. Build full path
        std::filesystem::path full_path = physical_root;
        for (size_t i = 1; i < parts.size(); ++i) {
            full_path /= parts[i];
        }

        // 6. Canonicalize and verify containment
        std::filesystem::path canonical;
        try {
            if (mode == AccessMode::Read) {
                // Must exist for read
                canonical = std::filesystem::canonical(full_path);
            } else {
                // May not exist for write - canonicalize parent
                canonical = std::filesystem::weakly_canonical(full_path);
            }
        } catch (const std::filesystem::filesystem_error&) {
            return std::nullopt;
        }

        // 7. Verify path is within allowed root (prevent traversal)
        std::string canonical_str = canonical.string();
        if (!canonical_str.starts_with(physical_root)) {
            AuditLog::Log({m_app_id, AuditEvent::SandboxViolation,
                          "path traversal attempt: " + virtual_path, false});
            return std::nullopt;
        }

        // 8. Check file extension
        if (!IsAllowedExtension(canonical.extension().string())) {
            return std::nullopt;
        }

        return canonical_str;
    }

    // Quota enforcement
    bool HasStorageQuota(size_t bytes_needed) {
        size_t current = CalculateDirectorySize(m_app_root + "/data");
        return (current + bytes_needed) <= m_policy.max_app_storage;
    }

    std::optional<size_t> GetUsedStorage() {
        return CalculateDirectorySize(m_app_root + "/data");
    }

    std::optional<size_t> GetAvailableStorage() {
        auto used = GetUsedStorage();
        if (!used) return std::nullopt;
        return m_policy.max_app_storage - *used;
    }

private:
    bool IsValidPathFormat(const std::string& path) {
        // Must start with /
        if (path.empty() || path[0] != '/') return false;

        // No .. components
        if (path.find("..") != std::string::npos) return false;

        // No double slashes
        if (path.find("//") != std::string::npos) return false;

        // No null bytes
        if (path.find('\0') != std::string::npos) return false;

        // Length check
        if (path.length() > 4096) return false;

        // Check each component
        for (const auto& part : SplitPath(path)) {
            if (part.length() > m_policy.max_filename_length) return false;
            if (part.empty()) return false;
            if (MatchesBlockedPattern(part)) return false;
        }

        return true;
    }

    bool IsAllowedExtension(const std::string& ext) {
        if (ext.empty()) return true;  // Directories
        std::string lower_ext = ToLower(ext);
        return std::find(m_policy.allowed_extensions.begin(),
                        m_policy.allowed_extensions.end(),
                        lower_ext) != m_policy.allowed_extensions.end();
    }

    std::string m_app_id;
    std::string m_base_path;
    std::string m_app_root;
    FilesystemPolicy m_policy;
};

File Operations with Security Gates

class SecureFileAPI {
public:
    // Read file
    std::optional<std::string> ReadFile(LuaSandbox* sandbox,
                                        const std::string& path) {
        auto physical = m_vfs.ResolvePath(path, AccessMode::Read);
        if (!physical) return std::nullopt;

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "file_read")) {
            return std::nullopt;
        }

        // Read with size limit
        std::ifstream file(*physical, std::ios::binary);
        if (!file) return std::nullopt;

        file.seekg(0, std::ios::end);
        size_t size = file.tellg();
        if (size > m_policy.max_file_size) return std::nullopt;

        file.seekg(0, std::ios::beg);
        std::string content(size, '\0');
        file.read(content.data(), size);

        AuditLog::Log({sandbox->app_id(), AuditEvent::FileAccess,
                      "read: " + path + " (" + std::to_string(size) + " bytes)", true});

        return content;
    }

    // Write file
    bool WriteFile(LuaSandbox* sandbox, const std::string& path,
                   const std::string& content) {
        auto physical = m_vfs.ResolvePath(path, AccessMode::Write);
        if (!physical) return false;

        // Size check
        if (content.size() > m_policy.max_file_size) return false;

        // Quota check
        if (!m_vfs.HasStorageQuota(content.size())) {
            AuditLog::Log({sandbox->app_id(), AuditEvent::ResourceLimitHit,
                          "storage quota exceeded", false});
            return false;
        }

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "file_write")) {
            return false;
        }

        // Create parent directories
        std::filesystem::create_directories(
            std::filesystem::path(*physical).parent_path());

        // Write atomically (temp file + rename)
        std::string temp_path = *physical + ".tmp." + GenerateRandomId();
        {
            std::ofstream file(temp_path, std::ios::binary);
            if (!file) return false;
            file.write(content.data(), content.size());
        }

        std::error_code ec;
        std::filesystem::rename(temp_path, *physical, ec);
        if (ec) {
            std::filesystem::remove(temp_path);
            return false;
        }

        AuditLog::Log({sandbox->app_id(), AuditEvent::FileAccess,
                      "write: " + path + " (" + std::to_string(content.size()) + " bytes)",
                      true});

        return true;
    }

    // List directory
    std::optional<std::vector<FileInfo>> ListDirectory(LuaSandbox* sandbox,
                                                       const std::string& path) {
        auto physical = m_vfs.ResolvePath(path, AccessMode::Read);
        if (!physical) return std::nullopt;

        std::vector<FileInfo> entries;
        for (const auto& entry : std::filesystem::directory_iterator(*physical)) {
            if (entries.size() >= m_policy.max_files_per_directory) break;

            FileInfo info;
            info.name = entry.path().filename().string();
            info.is_directory = entry.is_directory();
            info.size = entry.is_regular_file() ? entry.file_size() : 0;
            info.modified = GetModificationTime(entry.path());
            entries.push_back(info);
        }

        return entries;
    }

    // Delete file
    bool Delete(LuaSandbox* sandbox, const std::string& path) {
        auto physical = m_vfs.ResolvePath(path, AccessMode::Write);
        if (!physical) return false;

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "file_delete")) {
            return false;
        }

        std::error_code ec;
        bool removed = std::filesystem::remove(*physical, ec);

        AuditLog::Log({sandbox->app_id(), AuditEvent::FileAccess,
                      "delete: " + path, removed});

        return removed;
    }

private:
    VirtualFilesystem m_vfs;
    FilesystemPolicy m_policy;
    RateLimiter m_rate_limiter;
};

Filesystem Lua API

-- File operations (app-private storage is auto-granted)
local content = storage.read("/data/config.json")
local success = storage.write("/data/config.json", json.encode(config))

-- Directory operations
local files = storage.list("/data/saves/")
for _, file in ipairs(files) do
    print(file.name, file.size, file.is_directory)
end

-- File info
local info = storage.info("/data/save.json")
-- info.exists, info.size, info.modified, info.is_directory

-- Delete
storage.delete("/data/old_file.json")

-- Create directory
storage.mkdir("/data/saves/slot1/")

-- Check storage quota
local quota = storage.quota()
-- quota.used, quota.available, quota.total

-- Shared storage (requires "storage.shared.read" / "storage.shared.write")
local photos = storage.list("/shared/photos/")
storage.write("/shared/documents/report.txt", "Hello")

Database Interface Security

SQLite Sandbox

Status: Designed

Apps can use SQLite databases with security restrictions.

struct DatabasePolicy {
    size_t max_database_size = 50 * 1024 * 1024;  // 50 MB
    size_t max_databases_per_app = 5;
    size_t max_query_time_ms = 5000;
    size_t max_result_rows = 10000;
    bool allow_attach = false;           // Prevent ATTACH to other DBs
    bool allow_load_extension = false;   // No extensions
};

class SecureDatabase {
public:
    SecureDatabase(const std::string& app_id, const std::string& db_name)
        : m_app_id(app_id)
        , m_db_name(db_name) {

        // Validate database name
        if (!IsValidDatabaseName(db_name)) {
            throw std::invalid_argument("invalid database name");
        }

        // Open database in app's data directory
        std::string path = GetAppDataPath(app_id) + "/databases/" + db_name + ".db";

        int rc = sqlite3_open_v2(path.c_str(), &m_db,
            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
            SQLITE_OPEN_NOMUTEX,  // Single-threaded access per app
            nullptr);

        if (rc != SQLITE_OK) {
            throw std::runtime_error("failed to open database");
        }

        // Security configuration
        ConfigureSecurity();
    }

    ~SecureDatabase() {
        if (m_db) sqlite3_close(m_db);
    }

    // Execute query with prepared statement (prevents SQL injection)
    QueryResult Execute(const std::string& sql,
                       const std::vector<SqlValue>& params) {
        // 1. Validate SQL (basic sanity checks)
        if (!ValidateSql(sql)) {
            return {.error = "invalid SQL"};
        }

        // 2. Prepare statement
        sqlite3_stmt* stmt;
        int rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr);
        if (rc != SQLITE_OK) {
            return {.error = sqlite3_errmsg(m_db)};
        }

        // 3. Bind parameters
        for (size_t i = 0; i < params.size(); ++i) {
            BindParameter(stmt, i + 1, params[i]);
        }

        // 4. Execute with timeout
        QueryResult result;
        auto start = std::chrono::steady_clock::now();

        while (true) {
            rc = sqlite3_step(stmt);

            // Check timeout
            auto elapsed = std::chrono::steady_clock::now() - start;
            if (elapsed > std::chrono::milliseconds(m_policy.max_query_time_ms)) {
                sqlite3_finalize(stmt);
                return {.error = "query timeout"};
            }

            if (rc == SQLITE_ROW) {
                // Check row limit
                if (result.rows.size() >= m_policy.max_result_rows) {
                    result.truncated = true;
                    break;
                }
                result.rows.push_back(ExtractRow(stmt));
            } else if (rc == SQLITE_DONE) {
                break;
            } else {
                result.error = sqlite3_errmsg(m_db);
                break;
            }
        }

        result.changes = sqlite3_changes(m_db);
        result.last_insert_id = sqlite3_last_insert_rowid(m_db);

        sqlite3_finalize(stmt);
        return result;
    }

private:
    void ConfigureSecurity() {
        // Disable dangerous features
        sqlite3_db_config(m_db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 0, nullptr);

        // Set authorizer to block dangerous operations
        sqlite3_set_authorizer(m_db, Authorizer, this);

        // Set database size limit
        sqlite3_soft_heap_limit64(m_policy.max_database_size);

        // Set busy timeout
        sqlite3_busy_timeout(m_db, 1000);
    }

    static int Authorizer(void* user_data, int action, const char* arg1,
                         const char* arg2, const char* arg3, const char* arg4) {
        auto* self = static_cast<SecureDatabase*>(user_data);

        switch (action) {
            case SQLITE_ATTACH:
            case SQLITE_DETACH:
                // Block ATTACH/DETACH to prevent accessing other databases
                return SQLITE_DENY;

            case SQLITE_PRAGMA:
                // Allow only safe pragmas
                if (arg1 && !IsSafePragma(arg1)) {
                    return SQLITE_DENY;
                }
                break;

            case SQLITE_FUNCTION:
                // Block dangerous functions
                if (arg2 && IsDangerousFunction(arg2)) {
                    return SQLITE_DENY;
                }
                break;
        }

        return SQLITE_OK;
    }

    static bool IsSafePragma(const char* pragma) {
        // Whitelist of safe pragmas
        static const std::unordered_set<std::string> safe = {
            "table_info", "index_list", "foreign_keys",
            "journal_mode", "synchronous", "cache_size"
        };
        return safe.count(pragma) > 0;
    }

    static bool IsDangerousFunction(const char* func) {
        // Block functions that can access filesystem or execute code
        static const std::unordered_set<std::string> dangerous = {
            "load_extension", "readfile", "writefile", "edit",
            "fts3_tokenizer", "sqlite_compileoption_get"
        };
        return dangerous.count(func) > 0;
    }

    bool ValidateSql(const std::string& sql) {
        std::string upper = ToUpper(sql);

        // Block dangerous statements
        if (upper.find("ATTACH") != std::string::npos) return false;
        if (upper.find("DETACH") != std::string::npos) return false;
        if (upper.find("LOAD_EXTENSION") != std::string::npos) return false;

        return true;
    }

    sqlite3* m_db = nullptr;
    std::string m_app_id;
    std::string m_db_name;
    DatabasePolicy m_policy;
};

Database Lua API

-- Open database (stored in /data/databases/)
local db = database.open("myapp")

-- Create table
db:execute([[
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT UNIQUE,
        created_at INTEGER DEFAULT (strftime('%s', 'now'))
    )
]])

-- Insert with parameters (SQL injection safe)
db:execute("INSERT INTO users (name, email) VALUES (?, ?)", {"John", "john@example.com"})

-- Query with parameters
local result = db:query("SELECT * FROM users WHERE name LIKE ?", {"%John%"})
for _, row in ipairs(result.rows) do
    print(row.id, row.name, row.email)
end

-- Transaction
db:transaction(function()
    db:execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", {100, 1})
    db:execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", {100, 2})
end)

-- Close
db:close()

Camera Interface Security

Camera Security Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         App Lua Code                                 │
│                    camera.capture(callback)                          │
│                              │                                       │
├──────────────────────────────▼──────────────────────────────────────┤
│                       CameraManager                                  │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────────────┐ │
│  │ Permission  │  │    Active    │  │     Frame Rate             │ │
│  │   Check     │→ │  Indicator   │→ │     Limiter                │ │
│  └─────────────┘  └──────────────┘  └────────────────────────────┘ │
│                              │                                       │
├──────────────────────────────▼──────────────────────────────────────┤
│                      Game Engine Camera                              │
│              (Unity RenderTexture / Unreal SceneCapture)             │
└─────────────────────────────────────────────────────────────────────┘

Camera Security Implementation

Status: Designed

struct CameraPolicy {
    // Access control
    bool require_user_gesture = true;      // Must be triggered by tap/click
    int max_active_sessions = 1;            // Only one app can use camera

    // Frame limits
    int max_fps = 30;
    int max_resolution_width = 1920;
    int max_resolution_height = 1080;

    // Privacy
    bool show_indicator = true;             // Always show recording indicator
    int indicator_min_duration_ms = 500;    // Indicator shows at least 500ms
    bool allow_silent_capture = false;      // Require shutter sound

    // Storage
    bool auto_save_to_gallery = false;      // App must explicitly save
    std::string watermark_text = "";        // Optional watermark
};

class CameraManager {
public:
    struct CameraSession {
        uint32_t id;
        std::string app_id;
        CameraFacing facing;
        Resolution resolution;
        bool active;
        std::chrono::steady_clock::time_point started_at;
    };

    // Start camera session
    std::optional<uint32_t> StartSession(LuaSandbox* sandbox,
                                         const CameraOptions& opts) {
        // 1. Permission check
        if (!sandbox->HasPermission("camera")) {
            AuditLog::Log({sandbox->app_id(), AuditEvent::PermissionDenied,
                          "camera", false});
            return std::nullopt;
        }

        // 2. User gesture check
        if (m_policy.require_user_gesture && !sandbox->HasRecentUserGesture(1000)) {
            AuditLog::Log({sandbox->app_id(), AuditEvent::CameraAccess,
                          "blocked - no user gesture", false});
            return std::nullopt;
        }

        // 3. Check exclusive access
        if (HasActiveSession() && m_active_session->app_id != sandbox->app_id()) {
            return std::nullopt;  // Another app is using camera
        }

        // 4. Validate resolution
        Resolution res = opts.resolution;
        res.width = std::min(res.width, m_policy.max_resolution_width);
        res.height = std::min(res.height, m_policy.max_resolution_height);

        // 5. Start session
        CameraSession session{
            .id = ++m_next_session_id,
            .app_id = sandbox->app_id(),
            .facing = opts.facing,
            .resolution = res,
            .active = true,
            .started_at = std::chrono::steady_clock::now()
        };

        m_active_session = session;

        // 6. Show indicator
        if (m_policy.show_indicator) {
            ShowCameraIndicator(true);
        }

        // 7. Request frames from game engine
        m_hardware_camera->Start(res, [this, session_id = session.id](Frame frame) {
            OnFrameReceived(session_id, frame);
        });

        AuditLog::Log({sandbox->app_id(), AuditEvent::CameraAccess,
                      "session started", true});

        return session.id;
    }

    // Capture single frame
    std::optional<ImageData> CaptureFrame(LuaSandbox* sandbox, uint32_t session_id) {
        // Verify session ownership
        if (!m_active_session || m_active_session->id != session_id ||
            m_active_session->app_id != sandbox->app_id()) {
            return std::nullopt;
        }

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "camera_capture")) {
            return std::nullopt;
        }

        // Get latest frame
        auto frame = m_latest_frame;
        if (!frame) return std::nullopt;

        // Play shutter sound (if configured)
        if (!m_policy.allow_silent_capture) {
            PlayShutterSound();
        }

        // Apply watermark if configured
        if (!m_policy.watermark_text.empty()) {
            ApplyWatermark(frame, m_policy.watermark_text);
        }

        return frame;
    }

    // Stop session
    void StopSession(LuaSandbox* sandbox, uint32_t session_id) {
        if (!m_active_session || m_active_session->id != session_id ||
            m_active_session->app_id != sandbox->app_id()) {
            return;
        }

        m_hardware_camera->Stop();

        // Keep indicator visible for minimum duration
        auto elapsed = std::chrono::steady_clock::now() - m_active_session->started_at;
        if (elapsed < std::chrono::milliseconds(m_policy.indicator_min_duration_ms)) {
            std::this_thread::sleep_for(
                std::chrono::milliseconds(m_policy.indicator_min_duration_ms) - elapsed);
        }

        ShowCameraIndicator(false);
        m_active_session.reset();

        AuditLog::Log({sandbox->app_id(), AuditEvent::CameraAccess,
                      "session stopped", true});
    }

    // Stop all sessions for app (called on app termination)
    void StopAllSessions(const std::string& app_id) {
        if (m_active_session && m_active_session->app_id == app_id) {
            m_hardware_camera->Stop();
            ShowCameraIndicator(false);
            m_active_session.reset();
        }
    }

private:
    void OnFrameReceived(uint32_t session_id, Frame frame) {
        if (!m_active_session || m_active_session->id != session_id) {
            return;
        }

        // Apply frame rate limit
        auto now = std::chrono::steady_clock::now();
        auto elapsed = now - m_last_frame_time;
        if (elapsed < std::chrono::milliseconds(1000 / m_policy.max_fps)) {
            return;
        }
        m_last_frame_time = now;

        m_latest_frame = frame;

        // Notify app (if callback registered)
        if (m_frame_callback) {
            m_frame_callback(frame);
        }
    }

    std::optional<CameraSession> m_active_session;
    std::optional<Frame> m_latest_frame;
    std::chrono::steady_clock::time_point m_last_frame_time;
    CameraPolicy m_policy;
    IHardwareCamera* m_hardware_camera;
    RateLimiter m_rate_limiter;
    uint32_t m_next_session_id = 0;
};

Camera Lua API

-- Request camera permission (shows system dialog)
local granted = permissions.request("camera")
if not granted then return end

-- Start camera session
local session = camera.start({
    facing = "back",  -- "back" or "front"
    resolution = {width = 1280, height = 720}
})

-- Preview frames
session:onFrame(function(frame)
    -- frame.width, frame.height, frame.data (base64 JPEG)
    ui.setImage("preview", frame.data)
end)

-- Capture photo
local photo = session:capture()
if photo then
    -- Save to app storage
    storage.write("/data/photos/photo_" .. os.time() .. ".jpg",
                  base64.decode(photo.data))

    -- Or save to shared gallery (requires storage.shared.write)
    storage.write("/shared/photos/MyApp/photo.jpg", base64.decode(photo.data))
end

-- Stop session
session:stop()

Microphone Interface Security

Microphone Security Implementation

Status: Designed

struct MicrophonePolicy {
    // Access control
    bool require_user_gesture = true;
    int max_active_sessions = 1;

    // Audio limits
    int max_sample_rate = 48000;
    int max_duration_ms = 300000;  // 5 minutes max recording
    int max_buffer_size = 4096;

    // Privacy
    bool show_indicator = true;
    int indicator_min_duration_ms = 500;
    bool allow_background_recording = false;
};

class MicrophoneManager {
public:
    struct AudioSession {
        uint32_t id;
        std::string app_id;
        int sample_rate;
        int channels;
        bool active;
        std::chrono::steady_clock::time_point started_at;
        size_t samples_recorded = 0;
    };

    std::optional<uint32_t> StartRecording(LuaSandbox* sandbox,
                                           const AudioOptions& opts) {
        // 1. Permission check
        if (!sandbox->HasPermission("microphone")) {
            AuditLog::Log({sandbox->app_id(), AuditEvent::PermissionDenied,
                          "microphone", false});
            return std::nullopt;
        }

        // 2. User gesture check
        if (m_policy.require_user_gesture && !sandbox->HasRecentUserGesture(1000)) {
            return std::nullopt;
        }

        // 3. Exclusive access
        if (HasActiveSession() && m_active_session->app_id != sandbox->app_id()) {
            return std::nullopt;
        }

        // 4. Background recording check
        if (!m_policy.allow_background_recording && !sandbox->IsAppForeground()) {
            return std::nullopt;
        }

        // 5. Validate options
        int sample_rate = std::min(opts.sample_rate, m_policy.max_sample_rate);

        AudioSession session{
            .id = ++m_next_session_id,
            .app_id = sandbox->app_id(),
            .sample_rate = sample_rate,
            .channels = opts.channels,
            .active = true,
            .started_at = std::chrono::steady_clock::now()
        };

        m_active_session = session;

        // 6. Show indicator
        if (m_policy.show_indicator) {
            ShowMicrophoneIndicator(true);
        }

        // 7. Start hardware recording
        m_hardware_mic->Start(sample_rate, opts.channels,
            [this, session_id = session.id](AudioBuffer buffer) {
                OnAudioReceived(session_id, buffer);
            });

        AuditLog::Log({sandbox->app_id(), AuditEvent::MicrophoneAccess,
                      "recording started", true});

        return session.id;
    }

    void StopRecording(LuaSandbox* sandbox, uint32_t session_id) {
        if (!m_active_session || m_active_session->id != session_id ||
            m_active_session->app_id != sandbox->app_id()) {
            return;
        }

        m_hardware_mic->Stop();

        // Minimum indicator duration
        auto elapsed = std::chrono::steady_clock::now() - m_active_session->started_at;
        if (elapsed < std::chrono::milliseconds(m_policy.indicator_min_duration_ms)) {
            std::this_thread::sleep_for(
                std::chrono::milliseconds(m_policy.indicator_min_duration_ms) - elapsed);
        }

        ShowMicrophoneIndicator(false);
        m_active_session.reset();

        AuditLog::Log({sandbox->app_id(), AuditEvent::MicrophoneAccess,
                      "recording stopped", true});
    }

private:
    void OnAudioReceived(uint32_t session_id, AudioBuffer buffer) {
        if (!m_active_session || m_active_session->id != session_id) {
            return;
        }

        // Check duration limit
        auto elapsed = std::chrono::steady_clock::now() - m_active_session->started_at;
        if (elapsed > std::chrono::milliseconds(m_policy.max_duration_ms)) {
            // Auto-stop after max duration
            StopRecordingInternal(session_id);
            return;
        }

        m_active_session->samples_recorded += buffer.samples;

        // Deliver to app callback
        if (m_audio_callback) {
            m_audio_callback(buffer);
        }
    }

    std::optional<AudioSession> m_active_session;
    MicrophonePolicy m_policy;
    IHardwareMicrophone* m_hardware_mic;
    uint32_t m_next_session_id = 0;
};

Microphone Lua API

-- Request permission
local granted = permissions.request("microphone")
if not granted then return end

-- Start recording
local session = microphone.start({
    sample_rate = 44100,
    channels = 1
})

-- Receive audio data
session:onAudio(function(buffer)
    -- buffer.samples: number of samples
    -- buffer.data: base64 encoded PCM data
end)

-- Stop recording
session:stop()

-- Get recorded audio
local audio = session:getRecording()
storage.write("/data/recordings/voice.wav", audio)

Speaker/Audio Output Security

Audio Output Policy

Status: Designed

struct AudioOutputPolicy {
    float max_volume = 1.0f;
    int max_concurrent_sounds = 8;
    int max_sound_duration_ms = 300000;  // 5 minutes
    size_t max_audio_data_size = 50 * 1024 * 1024;  // 50 MB
    bool require_audio_focus = true;  // Respect other apps
};

class AudioOutputManager {
public:
    std::optional<uint32_t> PlaySound(LuaSandbox* sandbox,
                                      const AudioData& audio,
                                      const PlaybackOptions& opts) {
        // No dangerous permission needed for audio output,
        // but still track and limit

        // 1. Validate audio data
        if (audio.size() > m_policy.max_audio_data_size) {
            return std::nullopt;
        }

        // 2. Check concurrent sounds limit
        int app_sounds = CountAppSounds(sandbox->app_id());
        if (app_sounds >= m_policy.max_concurrent_sounds) {
            return std::nullopt;
        }

        // 3. Clamp volume
        float volume = std::clamp(opts.volume, 0.0f, m_policy.max_volume);

        // 4. Request audio focus
        if (m_policy.require_audio_focus) {
            if (!RequestAudioFocus(sandbox->app_id())) {
                return std::nullopt;
            }
        }

        // 5. Create sound instance
        Sound sound{
            .id = ++m_next_id,
            .app_id = sandbox->app_id(),
            .volume = volume,
            .looping = opts.loop,
            .started_at = std::chrono::steady_clock::now()
        };

        m_sounds[sound.id] = sound;
        m_hardware_audio->Play(audio, volume, opts.loop);

        return sound.id;
    }

    void StopSound(LuaSandbox* sandbox, uint32_t sound_id) {
        auto it = m_sounds.find(sound_id);
        if (it == m_sounds.end() || it->second.app_id != sandbox->app_id()) {
            return;
        }

        m_hardware_audio->Stop(sound_id);
        m_sounds.erase(it);
    }

    void StopAllSounds(const std::string& app_id) {
        for (auto it = m_sounds.begin(); it != m_sounds.end(); ) {
            if (it->second.app_id == app_id) {
                m_hardware_audio->Stop(it->first);
                it = m_sounds.erase(it);
            } else {
                ++it;
            }
        }
    }

private:
    std::unordered_map<uint32_t, Sound> m_sounds;
    AudioOutputPolicy m_policy;
    IHardwareAudio* m_hardware_audio;
    uint32_t m_next_id = 0;
};

Audio Lua API

-- Play sound (no permission required)
local sound = audio.play("/data/sounds/notification.wav", {
    volume = 0.8,
    loop = false
})

-- Control playback
sound:pause()
sound:resume()
sound:stop()

-- System sounds
audio.playSystem("notification")
audio.playSystem("click")

-- Vibration (requires nothing or system.vibrate on some platforms)
audio.vibrate(100)  -- 100ms
audio.vibrate({100, 50, 100})  -- pattern: vibrate, pause, vibrate

Location Interface Security

Location Security Implementation

Status: Designed

struct LocationPolicy {
    // Precision control
    int coarse_accuracy_meters = 1000;    // City-level
    int fine_accuracy_meters = 10;         // GPS precision

    // Rate limiting
    int min_update_interval_ms = 1000;     // Max 1 update/sec
    int background_update_interval_ms = 60000;  // 1/min in background

    // Privacy
    bool require_user_gesture_for_first = true;
    bool allow_background_updates = false;
    int max_cached_locations = 100;
};

class LocationManager {
public:
    std::optional<Location> GetLocation(LuaSandbox* sandbox, LocationAccuracy accuracy) {
        // 1. Permission check
        std::string perm = (accuracy == LocationAccuracy::Fine)
            ? "location.fine" : "location.coarse";

        if (!sandbox->HasPermission(perm)) {
            AuditLog::Log({sandbox->app_id(), AuditEvent::PermissionDenied,
                          perm, false});
            return std::nullopt;
        }

        // 2. Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "location")) {
            return std::nullopt;
        }

        // 3. Get location from hardware
        auto location = m_hardware_location->GetCurrent();
        if (!location) return std::nullopt;

        // 4. Apply precision reduction for coarse
        if (accuracy == LocationAccuracy::Coarse) {
            location = ReducePrecision(*location, m_policy.coarse_accuracy_meters);
        }

        AuditLog::Log({sandbox->app_id(), AuditEvent::LocationAccess,
                      "single location request", true});

        return location;
    }

    std::optional<uint32_t> WatchLocation(LuaSandbox* sandbox,
                                          LocationAccuracy accuracy,
                                          LocationCallback callback) {
        std::string perm = (accuracy == LocationAccuracy::Fine)
            ? "location.fine" : "location.coarse";

        if (!sandbox->HasPermission(perm)) {
            return std::nullopt;
        }

        // Background check
        if (!sandbox->IsAppForeground() && !m_policy.allow_background_updates) {
            return std::nullopt;
        }

        uint32_t watch_id = ++m_next_watch_id;

        LocationWatch watch{
            .id = watch_id,
            .app_id = sandbox->app_id(),
            .accuracy = accuracy,
            .callback = callback,
            .last_update = std::chrono::steady_clock::time_point::min()
        };

        m_watches[watch_id] = watch;

        // Start hardware updates if first watch
        if (m_watches.size() == 1) {
            m_hardware_location->StartUpdates([this](Location loc) {
                OnLocationUpdate(loc);
            });
        }

        return watch_id;
    }

    void ClearWatch(LuaSandbox* sandbox, uint32_t watch_id) {
        auto it = m_watches.find(watch_id);
        if (it == m_watches.end() || it->second.app_id != sandbox->app_id()) {
            return;
        }

        m_watches.erase(it);

        if (m_watches.empty()) {
            m_hardware_location->StopUpdates();
        }
    }

private:
    Location ReducePrecision(const Location& loc, int meters) {
        // Round coordinates to reduce precision
        // 0.01 degree ≈ 1.1km at equator
        double precision = meters / 111000.0;  // degrees per meter

        Location reduced = loc;
        reduced.latitude = std::round(loc.latitude / precision) * precision;
        reduced.longitude = std::round(loc.longitude / precision) * precision;
        reduced.accuracy = std::max(loc.accuracy, static_cast<float>(meters));
        return reduced;
    }

    void OnLocationUpdate(Location loc) {
        auto now = std::chrono::steady_clock::now();

        for (auto& [id, watch] : m_watches) {
            // Check update interval
            int interval = m_policy.min_update_interval_ms;
            auto elapsed = now - watch.last_update;
            if (elapsed < std::chrono::milliseconds(interval)) {
                continue;
            }

            // Apply precision reduction
            Location delivered = loc;
            if (watch.accuracy == LocationAccuracy::Coarse) {
                delivered = ReducePrecision(loc, m_policy.coarse_accuracy_meters);
            }

            watch.last_update = now;
            watch.callback(delivered);
        }
    }

    std::unordered_map<uint32_t, LocationWatch> m_watches;
    LocationPolicy m_policy;
    IHardwareLocation* m_hardware_location;
    RateLimiter m_rate_limiter;
    uint32_t m_next_watch_id = 0;
};

Location Lua API

-- Request permission
local granted = permissions.request("location.fine")  -- or "location.coarse"

-- Get current location
local loc = location.get()
if loc then
    print(loc.latitude, loc.longitude, loc.accuracy)
end

-- Watch location changes
local watch_id = location.watch(function(loc)
    print("Moved to:", loc.latitude, loc.longitude)
end, {
    accuracy = "fine",  -- "fine" or "coarse"
    interval = 5000     -- minimum ms between updates
})

-- Stop watching
location.clearWatch(watch_id)

Sensor Interface Security

Sensor Security (Accelerometer, Gyroscope, etc.)

Status: Designed

struct SensorPolicy {
    // Rate limiting (prevent fingerprinting via sensor noise patterns)
    int max_sample_rate_hz = 60;

    // Precision reduction
    float accelerometer_precision = 0.01f;  // m/s²
    float gyroscope_precision = 0.001f;     // rad/s

    // Background access
    bool allow_background_sensors = false;

    // Body sensors (heart rate, etc.) require special permission
    std::vector<std::string> body_sensors = {"heart_rate", "blood_pressure"};
};

class SensorManager {
public:
    std::optional<uint32_t> RegisterListener(LuaSandbox* sandbox,
                                             SensorType type,
                                             SensorCallback callback) {
        // Body sensors require dangerous permission
        if (IsBodySensor(type) && !sandbox->HasPermission("sensors.body")) {
            return std::nullopt;
        }

        // Background check
        if (!sandbox->IsAppForeground() && !m_policy.allow_background_sensors) {
            return std::nullopt;
        }

        uint32_t listener_id = ++m_next_listener_id;

        SensorListener listener{
            .id = listener_id,
            .app_id = sandbox->app_id(),
            .type = type,
            .callback = callback,
            .last_reading = std::chrono::steady_clock::time_point::min()
        };

        m_listeners[listener_id] = listener;

        // Start sensor if first listener
        EnableSensorIfNeeded(type);

        return listener_id;
    }

    void UnregisterListener(LuaSandbox* sandbox, uint32_t listener_id) {
        auto it = m_listeners.find(listener_id);
        if (it == m_listeners.end() || it->second.app_id != sandbox->app_id()) {
            return;
        }

        SensorType type = it->second.type;
        m_listeners.erase(it);

        // Stop sensor if no more listeners
        DisableSensorIfUnneeded(type);
    }

private:
    void OnSensorReading(SensorType type, SensorReading reading) {
        auto now = std::chrono::steady_clock::now();
        int min_interval_ms = 1000 / m_policy.max_sample_rate_hz;

        for (auto& [id, listener] : m_listeners) {
            if (listener.type != type) continue;

            // Rate limit
            auto elapsed = now - listener.last_reading;
            if (elapsed < std::chrono::milliseconds(min_interval_ms)) {
                continue;
            }

            // Apply precision reduction
            SensorReading reduced = ReducePrecision(reading, type);

            listener.last_reading = now;
            listener.callback(reduced);
        }
    }

    SensorReading ReducePrecision(const SensorReading& reading, SensorType type) {
        SensorReading reduced = reading;

        float precision = 0.01f;
        switch (type) {
            case SensorType::Accelerometer:
                precision = m_policy.accelerometer_precision;
                break;
            case SensorType::Gyroscope:
                precision = m_policy.gyroscope_precision;
                break;
            default:
                break;
        }

        reduced.x = std::round(reading.x / precision) * precision;
        reduced.y = std::round(reading.y / precision) * precision;
        reduced.z = std::round(reading.z / precision) * precision;

        return reduced;
    }

    std::unordered_map<uint32_t, SensorListener> m_listeners;
    SensorPolicy m_policy;
    uint32_t m_next_listener_id = 0;
};

Sensor Lua API

-- Motion sensors (no special permission)
local accel_id = sensors.listen("accelerometer", function(reading)
    print("Acceleration:", reading.x, reading.y, reading.z)
end)

local gyro_id = sensors.listen("gyroscope", function(reading)
    print("Rotation:", reading.x, reading.y, reading.z)
end)

-- Stop listening
sensors.unlisten(accel_id)
sensors.unlisten(gyro_id)

-- Body sensors (requires "sensors.body" permission)
local granted = permissions.request("sensors.body")
if granted then
    sensors.listen("heart_rate", function(reading)
        print("Heart rate:", reading.value, "bpm")
    end)
end

Bluetooth Interface Security

Bluetooth Security Implementation

Status: Designed

struct BluetoothPolicy {
    // Discovery
    bool allow_discovery = true;
    int discovery_timeout_seconds = 30;
    int max_discovered_devices = 50;

    // Connections
    int max_connections_per_app = 3;
    bool require_pairing_consent = true;

    // Data
    size_t max_transfer_size = 1 * 1024 * 1024;  // 1 MB
};

class BluetoothManager {
public:
    // Discover nearby devices
    std::optional<uint32_t> StartDiscovery(LuaSandbox* sandbox,
                                           DiscoveryCallback callback) {
        if (!sandbox->HasPermission("bluetooth")) {
            return std::nullopt;
        }

        // Only one discovery at a time per app
        if (HasActiveDiscovery(sandbox->app_id())) {
            return std::nullopt;
        }

        uint32_t discovery_id = ++m_next_discovery_id;

        Discovery discovery{
            .id = discovery_id,
            .app_id = sandbox->app_id(),
            .callback = callback,
            .devices = {},
            .started_at = std::chrono::steady_clock::now()
        };

        m_discoveries[discovery_id] = discovery;

        // Start hardware discovery with timeout
        m_hardware_bt->StartDiscovery(
            m_policy.discovery_timeout_seconds,
            [this, discovery_id](BluetoothDevice device) {
                OnDeviceDiscovered(discovery_id, device);
            });

        return discovery_id;
    }

    // Connect to device
    std::optional<uint32_t> Connect(LuaSandbox* sandbox,
                                    const std::string& device_address) {
        if (!sandbox->HasPermission("bluetooth")) {
            return std::nullopt;
        }

        // Connection limit
        if (CountAppConnections(sandbox->app_id()) >= m_policy.max_connections_per_app) {
            return std::nullopt;
        }

        // Require user consent for pairing
        if (m_policy.require_pairing_consent) {
            if (!ShowPairingDialog(device_address)) {
                return std::nullopt;
            }
        }

        // Connect
        auto conn = m_hardware_bt->Connect(device_address);
        if (!conn) return std::nullopt;

        uint32_t conn_id = ++m_next_conn_id;

        BluetoothConnection connection{
            .id = conn_id,
            .app_id = sandbox->app_id(),
            .device_address = device_address,
            .hardware_handle = *conn
        };

        m_connections[conn_id] = connection;
        return conn_id;
    }

private:
    void OnDeviceDiscovered(uint32_t discovery_id, BluetoothDevice device) {
        auto it = m_discoveries.find(discovery_id);
        if (it == m_discoveries.end()) return;

        // Limit discovered devices
        if (it->second.devices.size() >= m_policy.max_discovered_devices) {
            return;
        }

        it->second.devices.push_back(device);
        it->second.callback(device);
    }

    std::unordered_map<uint32_t, Discovery> m_discoveries;
    std::unordered_map<uint32_t, BluetoothConnection> m_connections;
    BluetoothPolicy m_policy;
    IHardwareBluetooth* m_hardware_bt;
    uint32_t m_next_discovery_id = 0;
    uint32_t m_next_conn_id = 0;
};

Bluetooth Lua API

-- Request permission
local granted = permissions.request("bluetooth")
if not granted then return end

-- Discover devices
local discovery = bluetooth.discover(function(device)
    print("Found:", device.name, device.address)
end)

-- Stop discovery
discovery:stop()

-- Connect to device (shows pairing dialog)
local conn = bluetooth.connect("AA:BB:CC:DD:EE:FF")
if conn then
    -- Send data
    conn:send("Hello")

    -- Receive data
    conn:onData(function(data)
        print("Received:", data)
    end)

    -- Disconnect
    conn:close()
end

Contacts Interface Security

Contacts Security Implementation

Status: Designed

struct ContactsPolicy {
    // Access scope
    bool allow_read_all = true;
    bool allow_write = true;
    bool allow_delete = true;

    // Rate limits
    int max_contacts_per_query = 1000;
    int max_writes_per_minute = 100;

    // Privacy
    bool require_purpose_string = true;  // App must explain why
    bool log_all_access = true;
};

class ContactsManager {
public:
    std::optional<std::vector<Contact>> GetContacts(LuaSandbox* sandbox,
                                                    const ContactQuery& query) {
        if (!sandbox->HasPermission("contacts.read")) {
            return std::nullopt;
        }

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "contacts_read")) {
            return std::nullopt;
        }

        // Query contacts
        auto contacts = m_contacts_store->Query(query);

        // Limit results
        if (contacts.size() > m_policy.max_contacts_per_query) {
            contacts.resize(m_policy.max_contacts_per_query);
        }

        AuditLog::Log({sandbox->app_id(), AuditEvent::ContactsAccess,
                      "read " + std::to_string(contacts.size()) + " contacts", true});

        return contacts;
    }

    bool CreateContact(LuaSandbox* sandbox, const Contact& contact) {
        if (!sandbox->HasPermission("contacts.write")) {
            return false;
        }

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "contacts_write")) {
            return false;
        }

        // Validate contact data
        if (!ValidateContact(contact)) {
            return false;
        }

        bool success = m_contacts_store->Create(contact);

        AuditLog::Log({sandbox->app_id(), AuditEvent::ContactsAccess,
                      "create contact: " + contact.name, success});

        return success;
    }

    bool DeleteContact(LuaSandbox* sandbox, const std::string& contact_id) {
        if (!sandbox->HasPermission("contacts.write")) {
            return false;
        }

        // Rate limit
        if (!m_rate_limiter.Check(sandbox->app_id(), "contacts_write")) {
            return false;
        }

        bool success = m_contacts_store->Delete(contact_id);

        AuditLog::Log({sandbox->app_id(), AuditEvent::ContactsAccess,
                      "delete contact: " + contact_id, success});

        return success;
    }

private:
    bool ValidateContact(const Contact& contact) {
        // Name required
        if (contact.name.empty()) return false;
        if (contact.name.length() > 256) return false;

        // Validate phone numbers
        for (const auto& phone : contact.phones) {
            if (phone.number.length() > 50) return false;
        }

        // Validate emails
        for (const auto& email : contact.emails) {
            if (!IsValidEmail(email.address)) return false;
        }

        return true;
    }

    ContactsPolicy m_policy;
    IContactsStore* m_contacts_store;
    RateLimiter m_rate_limiter;
};

Contacts Lua API

-- Request permission
local granted = permissions.request("contacts.read")

-- Query contacts
local contacts = contacts.getAll()
for _, c in ipairs(contacts) do
    print(c.name, c.phones[1], c.emails[1])
end

-- Search contacts
local results = contacts.search("John")

-- Get single contact
local contact = contacts.get(contact_id)

-- Create contact (requires contacts.write)
contacts.create({
    name = "John Doe",
    phones = {{type = "mobile", number = "+1234567890"}},
    emails = {{type = "home", address = "john@example.com"}}
})

-- Update contact
contacts.update(contact_id, {
    name = "John Smith"
})

-- Delete contact
contacts.delete(contact_id)

Implementation Status - Virtual Hardware

Interface Design Implementation Testing
Network HTTP Complete Not started
Network WebSocket Complete Not started
Filesystem Complete Not started
Database Complete Not started
Camera Complete Not started
Microphone Complete Not started
Speaker/Audio Complete Not started
Location Complete Not started
Sensors Complete Not started
Bluetooth Complete Not started
Contacts Complete Not started

References


Last updated: 2026-01-18