diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 47408d7..5a78305 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -29,11 +29,11 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides |---|-----------|--------|-------------| | 1 | Cross-Platform Kernel | ✅ Complete | Desktop designer with shared kernel code | | 2 | Testing Framework | ✅ Complete | Automated UI testing and inspection | -| 3 | Virtual Hardware | ❌ Not started | Camera, mic, speaker, filesystem APIs | -| 4 | App Sandboxing | ❌ Not started | Lua/WASM runtime, package format | +| 3 | Virtual Hardware | ✅ Complete | Camera, mic, speaker, filesystem APIs (via sandbox) | +| 4 | App Sandboxing | ✅ Complete | Lua runtime, package format, 149 security tests | | 5 | WebRTC Bridge | ❌ Not started | Phone-to-phone communication | | 6 | System Apps | 🔶 75% | Core phone apps | -| 7 | Game Integration | ❌ Not started | Unity/Unreal plugin polish | +| 7 | Game Integration | 🔶 In Progress | Unity/Unreal plugins (Vulkan working) | --- @@ -101,118 +101,48 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides --- -## Milestone 3: Virtual Hardware ❌ NOT STARTED +## Milestone 3: Virtual Hardware ✅ COMPLETE **Goal**: Hardware-like APIs backed by game engine or real devices. -### 3.1 Camera Interface +Implemented as part of the Lua Sandbox system. See [SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md) for details. -```cpp -class ICamera { - virtual void RequestFrame(FrameCallback callback) = 0; - virtual void SetResolution(int width, int height) = 0; - virtual bool IsAvailable() const = 0; -}; -``` +### Completed Components -**Implementations**: -- **Game Mode**: Receives texture from Unity/Unreal -- **Desktop Mode**: System webcam (optional) -- **Android Test Mode**: SharedTexture from MainActivity -- **Mock Mode**: Test patterns - -### 3.2 Microphone Interface - -```cpp -class IMicrophone { - virtual void StartCapture(AudioCallback callback) = 0; - virtual void StopCapture() = 0; - virtual bool IsAvailable() const = 0; -}; -``` - -### 3.3 Speaker Interface - -```cpp -class ISpeaker { - virtual void PlayAudio(AudioBuffer buffer) = 0; - virtual void SetVolume(float volume) = 0; -}; -``` - -### 3.4 Filesystem Interface - -```cpp -class IFileSystem { - virtual FileHandle Open(const std::string& path, Mode mode) = 0; - virtual std::vector List(const std::string& dir) = 0; - virtual bool CreateDirectory(const std::string& path) = 0; - virtual bool Delete(const std::string& path) = 0; -}; -``` - -### 3.5 Network Interface - -```cpp -class INetwork { - virtual HttpResponse Fetch(const HttpRequest& request) = 0; - virtual WebSocket Connect(const std::string& url) = 0; - virtual bool IsOnline() const = 0; -}; -``` +| Component | Sandbox Milestone | Location | +|-----------|------------------|----------| +| Camera | Milestone 11 | `sandbox/camera_interface.cpp` | +| Microphone | Milestone 12 | `sandbox/microphone_interface.cpp` | +| Audio Output | Milestone 13 | `sandbox/audio_output.cpp` | +| Location/GPS | Milestone 14 | `sandbox/location_interface.cpp` | +| Sensors | Milestone 15 | `sandbox/sensor_interface.cpp` | +| Bluetooth | Milestone 16 | `sandbox/bluetooth_interface.cpp` | +| Filesystem | Milestone 7 | `sandbox/virtual_fs.cpp` | +| Network/HTTP | Milestone 9 | `sandbox/network_manager.cpp` | +| WebSocket | Milestone 10 | `sandbox/websocket_manager.cpp` | --- -## Milestone 4: App Sandboxing ❌ NOT STARTED +## Milestone 4: App Sandboxing ✅ COMPLETE **Goal**: Secure app runtime with defined package format. -### 4.1 Runtime Decision +Implemented with 20 sub-milestones covering security, APIs, and hardware interfaces. 149 security tests passing. -| Aspect | Lua | WASM | -|--------|-----|------| -| Isolation | Weak | Strong | -| Performance | Good for UI | Near-native | -| RmlUi Integration | Native | Needs bridge | -| Ecosystem | Small | Large | +See: +- [SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md) - Implementation milestones +- [LUA-SANDBOX.md](LUA-SANDBOX.md) - Security features, permissions, APIs +- [APP-MANAGEMENT.md](APP-MANAGEMENT.md) - Package format, manifest, lifecycle -**Recommendation**: Hybrid approach -- Lua for UI scripting (RmlUi integration) -- WASM for app logic needing isolation (future) +### Key Features -### 4.2 Package Format (.mpkg) - -``` -myapp.mpkg/ -├── manifest.json # Metadata, permissions, entry -├── ui/ -│ ├── main.rml -│ ├── styles.rcss -│ └── scripts/ -├── assets/ -│ ├── icons/ -│ └── images/ -└── data/ -``` - -### 4.3 Manifest Schema - -```json -{ - "id": "com.example.myapp", - "name": "My App", - "version": "1.0.0", - "permissions": ["camera", "network", "storage"], - "entry": "ui/main.rml", - "icon": "assets/icons/app.png" -} -``` - -### 4.4 App Lifecycle - -``` -INSTALLED → LAUNCHING → RUNNING → PAUSED → STOPPED → UNINSTALLED -``` +- **Lua Runtime**: Isolated `lua_State` per app with dangerous globals removed +- **Resource Limits**: Memory (16MB), CPU (instruction hooks), timers (100 max) +- **Permission System**: Normal/Dangerous/Signature categories with user gesture requirements +- **Virtual Filesystem**: Per-app storage with 50MB quota +- **SQLite Database**: Per-app with authorizer blocking dangerous operations +- **Network Security**: HTTPS required, private IP blocking, domain whitelists +- **Hardware Interfaces**: Camera, mic, location, sensors with mandatory indicators --- @@ -420,24 +350,22 @@ cmake --build build --config Debug --- -## Current Sprint: Complete Partial Tasks +## Current Sprint: Next Steps -### Priority 1: Testing Framework Completion ✅ DONE -- [x] Action recording (capture interactions to JSON) -- [x] Action playback (replay with timing) -- [x] Screenshot diff (visual regression) -- [x] GLFW input hooks for automatic recording - -### Priority 2: Remaining System Apps +### Priority 1: System Apps Completion - [ ] Store app (UI only - browse, install) -- [ ] Camera app (UI + shared texture from MainActivity) +- [ ] Camera app (UI + game engine texture) - [ ] Music app (UI outline only) -### Priority 3: App Data Persistence -- [ ] JSON/SQLite storage layer -- [ ] Contact CRUD operations -- [ ] Message history -- [ ] Settings persistence +### Priority 2: Game Engine Integration +- [x] MosisUnreal - Vulkan texture import working +- [ ] MosisUnreal - Blueprint API polish +- [ ] MosisVR (Unity) - Vulkan backend + +### Priority 3: WebRTC Bridge (Milestone 5) +- [ ] libdatachannel integration +- [ ] Phone-to-phone communication +- [ ] Real smartphone bridge app --- @@ -454,10 +382,11 @@ cmake --build build --config Debug ### Documentation +All documentation is in `docs/`: - `CLAUDE.md` - Development guide -- `TESTING.md` - Testing framework documentation +- `SANDBOX_MILESTONES.md` - Sandbox implementation details - `ROADMAP.md` - This file --- -*Last updated: 2026-01-16* +*Last updated: 2026-01-19* diff --git a/docs/SANDBOX.md b/docs/SANDBOX.md index 464ca81..a36f8d2 100644 --- a/docs/SANDBOX.md +++ b/docs/SANDBOX.md @@ -1,6 +1,6 @@ # Mosis Lua Sandbox Security -**Status**: Design Phase +**Status**: ✅ Complete (149 security tests passing) **Goal**: Secure app isolation with defense-in-depth approach. --- @@ -74,3496 +74,105 @@ We assume a malicious app developer can: ## Security Layers ### Layer 1: Dangerous Globals Removal - -**Status**: Designed - -Remove all dangerous functions before any app code runs. - -```cpp -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.*` +Remove all dangerous functions before any app code runs: `os`, `io`, `debug`, `package`, `require`, `ffi`, `jit`, `dofile`, `loadfile`, `load`, `loadstring`, `rawget`, `rawset`, `collectgarbage`, `string.dump`. ### Layer 2: Bytecode Prevention - -**Status**: Designed - -Prevent loading of binary Lua chunks which can bypass sandbox checks. - -```cpp -// 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. +Only allow text chunks (`"t"` mode), reject binary Lua bytecode. ### Layer 3: Memory Limits - -**Status**: Designed - -Custom allocator tracks and limits memory usage per app. - -```cpp -void* SandboxAlloc(void* ud, void* ptr, size_t osize, size_t nsize) { - auto* sandbox = static_cast(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 | +Custom allocator tracks and limits memory per app (16 MB default). ### Layer 4: CPU Limits +Instruction count hook interrupts runaway code (10M instructions default). -**Status**: Designed +### Layer 5: Metatable Protection +Freeze `_G` and string metatable to prevent modification. -Instruction count hook prevents infinite loops and excessive computation. +### Layer 6: Permission System +Three categories: Normal (auto-granted), Dangerous (user prompt), Signature (system only). -```cpp -void InstructionHook(lua_State* L, lua_Debug* ar) { - auto* sandbox = GetSandbox(L); - sandbox->m_instructions += 1000; +### Layer 7: Rate Limiting +Token bucket rate limiting on sensitive operations. - 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. - -```cpp -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. - -```cpp -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. - -```cpp -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**: -```cpp -// 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. - -```cpp -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 | +### Layer 8: Audit Logging +All security events logged for forensics. --- -## Additional Security Measures +## Implementation -### Network Request Sandboxing +The sandbox is implemented across 20 milestones with 22 modules. See: -**Status**: Planned +- **[SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md)** - Complete implementation details for all 20 milestones +- **[LUA-SANDBOX.md](LUA-SANDBOX.md)** - API documentation for app developers -Network requests are proxied through the kernel with domain filtering. +### Milestone Overview -```cpp -struct NetworkPolicy { - std::vector allowed_domains; // Whitelist - std::vector 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; -}; +| Phase | Milestones | Components | +|-------|------------|------------| +| **Foundation** | 1-4 | Core sandbox, permissions, audit logging, path security | +| **Core APIs** | 5-8 | Timers, JSON, crypto, virtual filesystem, SQLite | +| **Network** | 9-10 | HTTP requests, WebSocket connections | +| **Hardware** | 11-17 | Camera, mic, audio, location, sensors, Bluetooth, contacts | +| **System** | 18-20 | Inter-app messaging, security tests, kernel integration | -class NetworkSandbox { -public: - bool ValidateRequest(const HttpRequest& req) { - // Parse URL - auto url = ParseUrl(req.url); +### Source Files - // Block private IPs - if (!m_policy.allow_private_ips && IsPrivateIP(url.host)) { - return false; - } +All sandbox code is in `src/main/cpp/sandbox/`: - // 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**: -```json -{ - "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. - -```cpp -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**: -```lua --- 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. - -```cpp -struct AppMessage { - std::string from_app; - std::string to_app; - std::string action; - std::string data; // JSON - std::vector 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**: -```json -{ - "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. - -```cpp -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 GetAppLogs(const std::string& app_id, size_t limit); -}; -``` - -### Coroutine Safety - -**Status**: Planned - -Coroutines are allowed but resource-tracked. - -```cpp -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. - -```cpp -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); -} -``` +| File | Description | +|------|-------------| +| `sandbox_manager.cpp` | Multi-app orchestrator | +| `lua_sandbox.cpp` | Core Lua sandbox with resource limits | +| `permission_gate.cpp` | Permission system | +| `virtual_fs.cpp` | Per-app virtual filesystem | +| `database_manager.cpp` | SQLite per app | +| `network_manager.cpp` | HTTP request validation | +| `websocket_manager.cpp` | WebSocket connections | +| `timer_manager.cpp` | setTimeout/setInterval | +| `json_api.cpp` | Safe JSON encode/decode | +| `crypto_api.cpp` | SHA256, HMAC, secure random | +| `camera_interface.cpp` | Camera with indicators | +| `microphone_interface.cpp` | Microphone with indicators | +| `audio_output.cpp` | Audio playback | +| `location_interface.cpp` | GPS with precision control | +| `sensor_interface.cpp` | Accelerometer, gyroscope, etc. | +| `bluetooth_interface.cpp` | Bluetooth device access | +| `contacts_interface.cpp` | Contacts read/write | +| `message_bus.cpp` | Inter-app communication | --- -## Runtime Security Audit +## Testing -### Audit Function +149 security tests verify sandbox integrity. Run with: -```cpp -std::vector AuditSandbox(lua_State* L) { - std::vector 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; -} +```bash +cd sandbox-test +./run_tests.bat ``` -### Startup Audit - -```cpp -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 - -```cpp -// 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 - -```cpp -// 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. - -```cpp -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**: -```lua -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). - -```cpp -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 bytes(n); - if (!CSPRNG(bytes.data(), n)) { - return luaL_error(L, "random generation failed"); - } - - lua_pushlstring(L, reinterpret_cast(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. - -```cpp -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{}(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(lua_touserdata(L, -1)); - lua_pop(L, 1); - - int nargs = lua_gettop(L); - if (nargs == 0) { - // [0, 1) - std::uniform_real_distribution 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 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 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. - -```cpp -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(now)); - return 1; -} -``` - -### Rate Limiting - -**Status**: Planned - -Prevent abuse of expensive operations. - -```cpp -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 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> 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. - -```cpp -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 m_callback_refs; -}; -``` - -### App Signature Verification - -**Status**: Planned - -Verify app packages haven't been tampered with. - -```cpp -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 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 hash(32); - SHA256_Final(hash.data(), &ctx); - return hash; - } -}; -``` - -### Debug Mode Restrictions - -**Status**: Planned - -Development features disabled in production. - -```cpp -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. - -```cpp -class ClipboardManager { -public: - // Requires permission + recent user gesture - std::optional 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. - -```cpp -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 - -```cpp -struct HttpRequestPolicy { - // Domain restrictions - std::vector allowed_domains; // Whitelist from manifest - std::vector 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 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 - -```cpp -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 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 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 m_connections; - WebSocketPolicy m_policy; - HttpRequestValidator m_validator; - RateLimiter m_rate_limiter; -}; -``` - -### Network Lua API - -```lua --- 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 - -```json -{ - "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//data/ (auto-granted) │ -│ ├── cache/ → /apps//cache/ (auto-granted) │ -│ ├── temp/ → /apps//temp/ (auto-granted) │ -│ ├── shared/ → /shared/ (permission required) │ -│ └── (nothing else visible) │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -### Filesystem Security Implementation - -**Status**: Designed - -```cpp -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 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 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 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 GetUsedStorage() { - return CalculateDirectorySize(m_app_root + "/data"); - } - - std::optional 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 - -```cpp -class SecureFileAPI { -public: - // Read file - std::optional 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> ListDirectory(LuaSandbox* sandbox, - const std::string& path) { - auto physical = m_vfs.ResolvePath(path, AccessMode::Read); - if (!physical) return std::nullopt; - - std::vector 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 - -```lua --- 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. - -```cpp -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& 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(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 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 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 - -```lua --- 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 - -```cpp -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 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 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 m_active_session; - std::optional 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 - -```lua --- 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 - -```cpp -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 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 m_active_session; - MicrophonePolicy m_policy; - IHardwareMicrophone* m_hardware_mic; - uint32_t m_next_session_id = 0; -}; -``` - -### Microphone Lua API - -```lua --- 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 - -```cpp -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 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 m_sounds; - AudioOutputPolicy m_policy; - IHardwareAudio* m_hardware_audio; - uint32_t m_next_id = 0; -}; -``` - -### Audio Lua API - -```lua --- 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 - -```cpp -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 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 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(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 m_watches; - LocationPolicy m_policy; - IHardwareLocation* m_hardware_location; - RateLimiter m_rate_limiter; - uint32_t m_next_watch_id = 0; -}; -``` - -### Location Lua API - -```lua --- 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 - -```cpp -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 body_sensors = {"heart_rate", "blood_pressure"}; -}; - -class SensorManager { -public: - std::optional 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 m_listeners; - SensorPolicy m_policy; - uint32_t m_next_listener_id = 0; -}; -``` - -### Sensor Lua API - -```lua --- 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 - -```cpp -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 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 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 m_discoveries; - std::unordered_map 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 - -```lua --- 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 - -```cpp -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> 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 - -```lua --- 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 | ❌ | +### Test Categories + +- Dangerous globals removal +- Bytecode rejection +- Memory limit enforcement +- CPU limit enforcement +- Metatable protection +- Path traversal prevention +- Permission checks +- Rate limiting +- Network security (private IP blocking, HTTPS enforcement) +- Hardware indicator requirements --- ## References -- [Lua 5.4 Manual - Sandboxing](https://www.lua.org/manual/5.4/manual.html#4.6) -- [Sandboxing Lua](http://lua-users.org/wiki/SandBoxes) -- [Lapis Lua Sandbox](https://github.com/leafo/lapis/blob/master/lapis/util/sandbox.moon) -- [Cloudflare Workers Lua Isolation](https://blog.cloudflare.com/cloudflare-workers-unleashed/) -- [OWASP Sandboxing Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Sandboxing_Cheat_Sheet.html) -- [Android Permission Model](https://developer.android.com/guide/topics/permissions/overview) -- [iOS App Sandbox Design Guide](https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/) -- [SQLite Security](https://www.sqlite.org/security.html) +- [Lua 5.4 Manual - Sandboxing](https://www.lua.org/manual/5.4/) +- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/) +- Android permission model --- -*Last updated: 2026-01-18* +*Last updated: 2026-01-19*