Files
MosisService/docs/MILESTONE-4.md

9.1 KiB

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:

-- 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):

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

{
  "$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)

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

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

-- 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

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

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

-- 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