Files
MosisService/MILESTONE-4.md

404 lines
9.1 KiB
Markdown

# 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<std::string>& 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<string, Intent>;
}
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<void(PermissionStatus)>;
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<std::string, PermissionStatus>
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<InstalledApp> GetInstalledApps() = 0;
virtual std::optional<InstalledApp> 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<std::string, std::string>& 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