Files
MosisService/SANDBOX_MILESTONE_18.md

12 KiB

Milestone 18: Inter-App Communication

Status: Complete Goal: Kernel-mediated message passing between apps.


Overview

This milestone implements secure inter-app communication via an intent system:

  • Apps register to receive specific intent actions
  • Senders must have required permissions
  • All messages go through kernel mediation (no direct app-to-app)
  • Message size limits prevent resource exhaustion
  • Audit logging for all inter-app communication

Key Deliverables

  1. MessageBus class - Central message routing and validation
  2. Intent struct - Message format with action, type, and data
  3. Lua intents API - intents.send(), intents.on(), intents.broadcast()
  4. Security validation - Permission checks, sender/receiver verification

File Structure

src/main/cpp/sandbox/
├── message_bus.h      # NEW - Message bus API header
└── message_bus.cpp    # NEW - Message bus implementation

Implementation Details

1. MessageBus Class

// message_bus.h
#pragma once

#include <string>
#include <vector>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <unordered_set>

struct lua_State;

namespace mosis {

struct Intent {
    std::string action;      // Intent action (e.g., "share", "view", "edit")
    std::string type;        // MIME type (e.g., "text/plain", "image/png")
    std::string data;        // Intent data/payload
    std::string from_app;    // Sender app ID (set by kernel)
};

struct IntentFilter {
    std::string action;      // Required action
    std::vector<std::string> types;  // Accepted MIME types (empty = all)
};

enum class MessageError {
    None,
    NoReceivers,
    PermissionDenied,
    InvalidIntent,
    DataTooLarge,
    ReceiverNotFound,
    SenderBlocked
};

class MessageBus {
public:
    using IntentHandler = std::function<void(const Intent&)>;

    MessageBus();
    ~MessageBus();

    // Register an app to receive intents
    void RegisterReceiver(
        const std::string& app_id,
        const IntentFilter& filter,
        IntentHandler handler
    );

    // Unregister all handlers for an app
    void UnregisterApp(const std::string& app_id);

    // Send intent to specific app (requires permission)
    MessageError Send(
        const std::string& sender_app,
        const std::string& target_app,
        const Intent& intent,
        std::string& error
    );

    // Broadcast intent to all registered receivers
    MessageError Broadcast(
        const std::string& sender_app,
        const Intent& intent,
        std::string& error
    );

    // Check if any app handles this action
    bool HasReceiverFor(const std::string& action) const;

    // Get list of apps that handle an action
    std::vector<std::string> GetReceiversFor(const std::string& action) const;

    // Permission management
    void SetAppPermission(const std::string& app_id, const std::string& permission, bool granted);
    bool HasPermission(const std::string& app_id, const std::string& permission) const;

    // Block specific app from sending/receiving
    void BlockApp(const std::string& app_id);
    void UnblockApp(const std::string& app_id);
    bool IsBlocked(const std::string& app_id) const;

    // Clear all registrations (for testing)
    void Clear();

    // Statistics
    size_t GetReceiverCount() const;
    size_t GetMessageCount() const { return m_message_count; }

private:
    struct ReceiverEntry {
        std::string app_id;
        IntentFilter filter;
        IntentHandler handler;
    };

    std::vector<ReceiverEntry> m_receivers;
    std::unordered_map<std::string, std::unordered_set<std::string>> m_permissions;
    std::unordered_set<std::string> m_blocked_apps;
    mutable std::mutex m_mutex;
    size_t m_message_count = 0;

    static constexpr size_t MAX_DATA_SIZE = 1024 * 1024;  // 1MB max

    bool MatchesFilter(const Intent& intent, const IntentFilter& filter) const;
    bool ValidateIntent(const Intent& intent, std::string& error) const;
};

// Register intents.* APIs as globals
void RegisterIntentsAPI(lua_State* L, MessageBus* bus, const std::string& app_id);

} // namespace mosis

2. Standard Intent Actions

Action Description Required Permission
share Share content with another app None
view Request app to view content None
edit Request app to edit content None
pick Request user to pick item None
call Initiate phone call phone.call
message Send SMS/message sms.send
email Compose email None

3. Permission Requirements

Operation Permission Required
Send intent Action-specific (see above)
Broadcast intent Action-specific
Register receiver None
Receive intent None

4. Lua API

-- Register to receive intents
intents.on("share", function(intent)
    print("Received share from:", intent.from)
    print("Type:", intent.type)
    print("Data:", intent.data)
end)

-- Register with MIME type filter
intents.on("view", function(intent)
    -- Handle view intent
end, {types = {"image/png", "image/jpeg"}})

-- Send intent to specific app
local success, err = intents.send("target.app.id", {
    action = "share",
    type = "text/plain",
    data = "Hello world"
})

-- Broadcast to all handlers
local count = intents.broadcast({
    action = "share",
    type = "text/plain",
    data = "Hello everyone"
})

-- Check if any app handles an action
local hasHandler = intents.hasReceiver("share")

-- Get list of apps that handle an action
local apps = intents.getReceivers("share")

-- Unregister all handlers for this app
intents.unregisterAll()

5. Error Handling

-- Errors raised via Lua errors
local ok, err = pcall(function()
    intents.send("target.app", {action = "share", data = "test"})
end)
if not ok then
    print("Error:", err)  -- e.g., "no receivers for action: share"
end

Test Cases

Test 1: Send to Registered Receiver

bool Test_MessageBusSendToReceiver(std::string& error_msg) {
    mosis::MessageBus bus;

    bool received = false;
    mosis::Intent receivedIntent;

    // Register receiver
    bus.RegisterReceiver("receiver.app", {"share", {}}, [&](const mosis::Intent& i) {
        received = true;
        receivedIntent = i;
    });

    // Send intent
    mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
    std::string err;
    auto result = bus.Send("sender.app", "receiver.app", intent, err);

    EXPECT_TRUE(result == mosis::MessageError::None);
    EXPECT_TRUE(received);
    EXPECT_TRUE(receivedIntent.action == "share");
    EXPECT_TRUE(receivedIntent.data == "Hello");
    EXPECT_TRUE(receivedIntent.from_app == "sender.app");

    return true;
}

Test 2: Block Unregistered Action

bool Test_MessageBusBlockUnregistered(std::string& error_msg) {
    mosis::MessageBus bus;

    // No receivers registered
    mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
    std::string err;
    auto result = bus.Send("sender.app", "receiver.app", intent, err);

    EXPECT_TRUE(result == mosis::MessageError::ReceiverNotFound);
    EXPECT_TRUE(err.find("not found") != std::string::npos ||
                err.find("no receiver") != std::string::npos);

    return true;
}

Test 3: Broadcast to Multiple Receivers

bool Test_MessageBusBroadcast(std::string& error_msg) {
    mosis::MessageBus bus;

    int receiveCount = 0;

    // Register multiple receivers for same action
    bus.RegisterReceiver("app1", {"share", {}}, [&](const mosis::Intent&) { receiveCount++; });
    bus.RegisterReceiver("app2", {"share", {}}, [&](const mosis::Intent&) { receiveCount++; });
    bus.RegisterReceiver("app3", {"other", {}}, [&](const mosis::Intent&) { receiveCount++; });

    // Broadcast share intent
    mosis::Intent intent{"share", "text/plain", "Hello", "sender.app"};
    std::string err;
    auto result = bus.Broadcast("sender.app", intent, err);

    EXPECT_TRUE(result == mosis::MessageError::None);
    EXPECT_TRUE(receiveCount == 2);  // Only app1 and app2

    return true;
}

Test 4: MIME Type Filtering

bool Test_MessageBusMimeFilter(std::string& error_msg) {
    mosis::MessageBus bus;

    bool imageReceived = false;
    bool textReceived = false;

    // Register with MIME filter
    bus.RegisterReceiver("image.viewer", {"view", {"image/png", "image/jpeg"}},
        [&](const mosis::Intent&) { imageReceived = true; });
    bus.RegisterReceiver("text.viewer", {"view", {"text/plain"}},
        [&](const mosis::Intent&) { textReceived = true; });

    // Send image intent
    mosis::Intent imgIntent{"view", "image/png", "data", "sender"};
    std::string err;
    bus.Broadcast("sender", imgIntent, err);

    EXPECT_TRUE(imageReceived);
    EXPECT_TRUE(!textReceived);

    return true;
}

Test 5: Data Size Limit

bool Test_MessageBusDataLimit(std::string& error_msg) {
    mosis::MessageBus bus;

    bus.RegisterReceiver("receiver", {"share", {}}, [](const mosis::Intent&) {});

    // Create oversized data (> 1MB)
    std::string largeData(2 * 1024 * 1024, 'x');

    mosis::Intent intent{"share", "text/plain", largeData, "sender"};
    std::string err;
    auto result = bus.Send("sender", "receiver", intent, err);

    EXPECT_TRUE(result == mosis::MessageError::DataTooLarge);

    return true;
}

Test 6: Blocked App Cannot Send

bool Test_MessageBusBlockedApp(std::string& error_msg) {
    mosis::MessageBus bus;

    bus.RegisterReceiver("receiver", {"share", {}}, [](const mosis::Intent&) {});
    bus.BlockApp("bad.app");

    mosis::Intent intent{"share", "text/plain", "Hello", "bad.app"};
    std::string err;
    auto result = bus.Send("bad.app", "receiver", intent, err);

    EXPECT_TRUE(result == mosis::MessageError::SenderBlocked);

    return true;
}

Test 7: Lua Integration

bool Test_MessageBusLuaIntegration(std::string& error_msg) {
    SandboxContext ctx = TestContext();
    LuaSandbox sandbox(ctx);

    mosis::MessageBus bus;
    mosis::RegisterIntentsAPI(sandbox.GetState(), &bus, "test.app");

    std::string script = R"lua(
        -- Test that intents global exists
        if not intents then
            error("intents global not found")
        end
        if not intents.on then
            error("intents.on not found")
        end
        if not intents.send then
            error("intents.send not found")
        end
        if not intents.broadcast then
            error("intents.broadcast not found")
        end
        if not intents.hasReceiver then
            error("intents.hasReceiver not found")
        end
        if not intents.getReceivers then
            error("intents.getReceivers not found")
        end
        if not intents.unregisterAll then
            error("intents.unregisterAll not found")
        end
    )lua";

    bool ok = sandbox.LoadString(script, "intents_test");
    if (!ok) {
        error_msg = "Lua test failed: " + sandbox.GetLastError();
        return false;
    }
    return true;
}

Acceptance Criteria

All tests must pass:

  • Test_MessageBusSendToReceiver - Send reaches registered receiver
  • Test_MessageBusBlockUnregistered - Unregistered action fails
  • Test_MessageBusBroadcast - Broadcast reaches all matching receivers
  • Test_MessageBusMimeFilter - MIME type filtering works
  • Test_MessageBusDataLimit - Data size limit enforced
  • Test_MessageBusBlockedApp - Blocked apps cannot send
  • Test_MessageBusLuaIntegration - Lua API works

Dependencies

  • Milestone 1 (LuaSandbox)
  • Milestone 2 (PermissionGate)
  • Milestone 3 (AuditLog)

Notes

Security Considerations

  1. Kernel mediation: All messages route through MessageBus, no direct app-to-app
  2. Sender verification: from_app is set by kernel, cannot be spoofed
  3. Permission checks: Sensitive actions require permissions
  4. Size limits: 1MB max to prevent resource exhaustion
  5. App blocking: Misbehaving apps can be blocked
  6. Audit logging: All inter-app communication is logged

Privacy Features

  1. Opt-in receiving: Apps must explicitly register for actions
  2. MIME filtering: Receivers can filter by content type
  3. No discovery: Apps cannot enumerate other apps (only check receivers)

Next Steps

After Milestone 18 passes:

  1. Milestone 19: Security Testing Suite