# Milestone 4: App Sandboxing **Status**: Not Started **Goal**: Secure app runtime with defined package format and permission system. --- ## Overview Apps in Mosis need: - Isolation from each other and the system - Defined package format for distribution - Permission model for hardware/data access - Lifecycle management --- ## Runtime Architecture ### Recommendation: Hybrid Approach | Component | Runtime | Reason | |-----------|---------|--------| | UI Scripts | Lua | Native RmlUi integration, simple | | App Logic | Lua (now), WASM (future) | Start simple, add WASM for isolation | | System Services | C++ | Performance, direct hardware access | ### Lua Sandbox RmlUi already uses Lua for UI scripting. We enhance it with: ```lua -- Sandboxed globals per app app = { id = "com.example.myapp", storage = AppStorage("com.example.myapp"), permissions = {"camera", "storage"}, } -- Restricted stdlib -- Remove: os.execute, io.popen, loadfile, dofile -- Keep: string, table, math, coroutine ``` **Sandbox Implementation** (`src/main/kernel/src/lua_sandbox.cpp`): ```cpp class LuaSandbox { public: lua_State* CreateAppState(const std::string& app_id, const std::vector& permissions); void RestrictGlobals(lua_State* L); void InjectAppAPIs(lua_State* L, const AppManifest& manifest); private: void RemoveDangerousFunctions(lua_State* L); void SetupPermissionChecks(lua_State* L); }; ``` --- ## Package Format (.mpkg) ### Directory Structure ``` myapp.mpkg/ ├── manifest.json # Required: metadata, permissions ├── ui/ │ ├── main.rml # Entry point │ ├── screens/ # Additional screens │ │ └── settings.rml │ ├── styles/ │ │ └── app.rcss │ └── scripts/ │ └── app.lua ├── assets/ │ ├── icon.png # 48x48 app icon │ ├── icon_large.png # 192x192 for store │ └── images/ └── locales/ # Optional: i18n ├── en.json └── es.json ``` ### Manifest Schema **File**: `manifest.json` ```json { "$schema": "https://mosis.dev/schemas/manifest-v1.json", "id": "com.example.myapp", "name": "My App", "version": "1.0.0", "version_code": 1, "description": "A sample Mosis app", "author": "Developer Name", "website": "https://example.com", "entry": "ui/main.rml", "icon": "assets/icon.png", "permissions": [ "camera", "microphone", "storage", "network", "contacts.read", "contacts.write" ], "min_mosis_version": "1.0.0", "intents": { "share": { "types": ["image/*", "text/plain"], "action": "ui/share.rml" } } } ``` ### Manifest TypeScript Interface (for validation) ```typescript interface MosisManifest { id: string; // Reverse domain notation name: string; // Display name version: string; // SemVer version_code: number; // Incremental integer description?: string; author?: string; website?: string; entry: string; // Path to main RML icon: string; // Path to icon permissions: Permission[]; min_mosis_version?: string; intents?: Record; } type Permission = | "camera" | "microphone" | "speaker" | "storage" | "network" | "contacts.read" | "contacts.write" | "messages.read" | "messages.write" | "location" | "phone.call" | "notifications"; ``` --- ## Permission System ### Permission Levels | Level | Description | Example | |-------|-------------|---------| | Normal | Auto-granted | `storage` (app's own data) | | Dangerous | User prompt required | `camera`, `contacts.read` | | Signature | System apps only | `phone.call`, `system.settings` | ### Permission Request Flow ``` App declares permission in manifest ↓ User installs app ↓ On first use of protected API: ↓ ┌─────────────────────────────────┐ │ "My App" wants to access your │ │ camera. Allow? │ │ │ │ [Deny] [Allow Once] [Allow] │ └─────────────────────────────────┘ ↓ Decision stored in PermissionManager ↓ API call proceeds or fails ``` ### Permission Manager **File**: `src/main/kernel/include/permission_manager.h` ```cpp namespace mosis { enum class PermissionStatus { NotRequested, Granted, Denied, AllowedOnce }; class IPermissionManager { public: virtual ~IPermissionManager() = default; // Check if app has permission virtual PermissionStatus Check(const std::string& app_id, const std::string& permission) = 0; // Request permission (may show UI) using PermissionCallback = std::function; virtual void Request(const std::string& app_id, const std::string& permission, PermissionCallback callback) = 0; // Revoke permission virtual void Revoke(const std::string& app_id, const std::string& permission) = 0; // Get all permissions for app virtual std::map GetAppPermissions(const std::string& app_id) = 0; }; } // namespace mosis ``` --- ## App Lifecycle ### States ``` INSTALLED → LAUNCHING → RUNNING → PAUSED → STOPPED → UNINSTALLED ↑ ↓ └──────────────────────┘ (resume) ``` ### Lifecycle Events ```lua -- In app.lua function onAppCreate() -- Initialize app state end function onAppResume() -- Returning from background end function onAppPause() -- Going to background, save state end function onAppDestroy() -- Cleanup end ``` ### App Manager **File**: `src/main/kernel/include/app_manager.h` ```cpp namespace mosis { struct InstalledApp { std::string id; std::string name; std::string version; std::string icon_path; std::string install_path; int64_t installed_time; }; class IAppManager { public: virtual ~IAppManager() = default; // Installation virtual bool Install(const std::string& mpkg_path) = 0; virtual bool Uninstall(const std::string& app_id) = 0; virtual bool Update(const std::string& mpkg_path) = 0; // Query virtual std::vector GetInstalledApps() = 0; virtual std::optional GetApp(const std::string& app_id) = 0; // Lifecycle virtual bool Launch(const std::string& app_id) = 0; virtual bool Stop(const std::string& app_id) = 0; virtual bool IsRunning(const std::string& app_id) = 0; // Inter-app communication virtual void SendIntent(const std::string& action, const std::map& data) = 0; }; } // namespace mosis ``` --- ## Storage Isolation ### Per-App Storage ```cpp class AppStorage { public: AppStorage(const std::string& app_id); // Key-value storage (like SharedPreferences) void SetString(const std::string& key, const std::string& value); std::string GetString(const std::string& key, const std::string& default_value = ""); void SetInt(const std::string& key, int value); int GetInt(const std::string& key, int default_value = 0); void SetBool(const std::string& key, bool value); bool GetBool(const std::string& key, bool default_value = false); // File storage (app-private) std::string GetFilesDir(); std::string GetCacheDir(); private: std::string m_app_id; std::string m_base_path; }; ``` ### Lua API ```lua -- Key-value storage app.storage:set("username", "john") local name = app.storage:get("username", "anonymous") -- File access (sandboxed) local data = app.files:read("state.json") app.files:write("state.json", json.encode(state)) ``` --- ## Implementation Plan ### Phase 1: Package Format - [ ] Define manifest schema - [ ] Create manifest parser/validator - [ ] Implement .mpkg directory loader ### Phase 2: App Manager - [ ] Install/uninstall apps - [ ] App registry (installed apps database) - [ ] Launch apps from package ### Phase 3: Lua Sandbox - [ ] Restrict dangerous globals - [ ] Inject app-specific APIs - [ ] Per-app Lua state management ### Phase 4: Permission System - [ ] Permission declaration in manifest - [ ] Runtime permission checks - [ ] Permission request UI ### Phase 5: Storage Isolation - [ ] Per-app directories - [ ] Key-value storage - [ ] Quota management --- ## Security Considerations 1. **Lua Sandbox Escape**: Audit all exposed functions 2. **Path Traversal**: Validate all file paths 3. **Memory Limits**: Set Lua memory quotas 4. **CPU Limits**: Timeout long-running scripts 5. **Network Isolation**: Apps only access allowed domains --- ## Acceptance Criteria - [ ] Apps installable from .mpkg directories - [ ] Apps launch in isolated Lua environment - [ ] Permission requests shown to user - [ ] App data isolated per app - [ ] Apps can be uninstalled cleanly - [ ] Store app can browse and install packages