404 lines
9.1 KiB
Markdown
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
|