7.6 KiB
Plugin Framework Architecture
Technical architecture for the universal plugin lifecycle, sandboxing, and registry framework.
Overview
The Plugin Framework provides the core extensibility infrastructure for the Stella Ops platform. It defines how plugins are discovered, loaded, initialized, monitored, and shut down. A three-tier trust model ensures that untrusted plugins cannot compromise the host process, while built-in plugins benefit from zero-overhead in-process execution. The framework is consumed as a library by other modules; it does not expose HTTP endpoints.
Design Principles
- Security by default - Untrusted plugins are process-isolated; capabilities are explicitly declared and enforced
- Lifecycle consistency - All plugins follow the same state machine regardless of trust level
- Zero-overhead for built-ins - BuiltIn plugins run in-process with direct method calls; no serialization or IPC cost
- Testability - Every component has an in-memory or mock alternative for deterministic testing
Components
Plugin/
├── StellaOps.Plugin.Abstractions/ # Core interfaces (IPlugin, PluginInfo, PluginCapabilities)
├── StellaOps.Plugin.Host/ # Plugin host, lifecycle manager, trust enforcement
├── StellaOps.Plugin.Registry/ # Plugin catalog (InMemory + PostgreSQL backends)
├── StellaOps.Plugin.Sandbox/ # Process isolation and gRPC IPC for untrusted plugins
├── StellaOps.Plugin.Sdk/ # SDK for plugin authors (base classes, helpers)
├── StellaOps.Plugin.Testing/ # Test utilities (mock host, fake registry)
├── Samples/
│ └── HelloWorld/ # Sample plugin demonstrating the SDK
└── __Tests/
└── StellaOps.Plugin.Tests/ # Unit and integration tests
Core Interfaces
IPlugin
public interface IPlugin
{
PluginInfo Info { get; }
PluginCapabilities Capabilities { get; }
Task InitializeAsync(IPluginContext context, CancellationToken ct);
Task StartAsync(CancellationToken ct);
Task StopAsync(CancellationToken ct);
}
PluginInfo
public sealed record PluginInfo
{
public required string Id { get; init; }
public required string Name { get; init; }
public required Version Version { get; init; }
public required PluginTrustLevel TrustLevel { get; init; }
public string? Description { get; init; }
public string? Author { get; init; }
}
PluginCapabilities
Declares what the plugin can do (e.g., CanScan, CanEvaluatePolicy, CanConnect). The host checks capabilities before routing work to a plugin.
Plugin Lifecycle
[Discovery] --> [Loading] --> [Initialization] --> [Active] --> [Shutdown]
│ │ │ │
│ │ └── failure ──> [Failed] │
│ └── failure ──> [Failed] │
└── not found ──> (skip)
| State | Description |
|---|---|
| Discovery | Host scans configured paths for assemblies or packages containing IPlugin implementations |
| Loading | Assembly or process is loaded; plugin metadata is read and validated |
| Initialization | InitializeAsync is called with an IPluginContext providing configuration and service access |
| Active | Plugin is ready to receive work; StartAsync has completed |
| Shutdown | StopAsync is called during graceful host shutdown or plugin unload |
| Failed | Plugin encountered an unrecoverable error during loading or initialization; logged and excluded |
Trust Levels
| Level | Execution Model | IPC | Use Case |
|---|---|---|---|
| BuiltIn | In-process, direct method calls | None | First-party plugins shipped with the platform |
| Trusted | In-process with monitoring | None | Vetted third-party plugins with signed manifests |
| Untrusted | Separate process via ProcessSandbox |
gRPC | Community or unverified plugins |
ProcessSandbox (Untrusted Plugins)
Untrusted plugins run in a child process managed by ProcessSandbox:
- Process creation: The sandbox spawns a new process with restricted permissions
- gRPC channel: A bidirectional gRPC channel is established for host-plugin communication
- Capability enforcement: The host proxy only forwards calls matching declared capabilities
- Resource limits: CPU and memory limits are enforced at the process level
- Crash isolation: If the plugin process crashes, the host logs the failure and marks the plugin as Failed; the host process is unaffected
Database Schema
Database: PostgreSQL (via PostgresPluginRegistry)
| Table | Purpose |
|---|---|
plugins |
Registered plugins (id, name, trust_level, status, config_json, registered_at) |
plugin_versions |
Version history per plugin (plugin_id, version, assembly_hash, published_at) |
plugin_capabilities |
Declared capabilities per plugin version (plugin_version_id, capability, parameters) |
The InMemoryPluginRegistry provides an equivalent in-memory implementation for testing and offline scenarios.
Data Flow
[Module Host] ── discover ──> [Plugin.Host]
│
load plugins
│
┌───────────────┼───────────────┐
│ │ │
[BuiltIn] [Trusted] [Untrusted]
(in-process) (in-process) (ProcessSandbox)
│ │ │
└───────────────┼───────────────┘
│
[Plugin.Registry] ── persist ──> [PostgreSQL]
Security Considerations
- Trust level enforcement: The host never executes untrusted plugin code in-process; all untrusted execution is delegated to the sandbox
- Capability restrictions: Plugins can only perform actions matching their declared capabilities; the host rejects unauthorized calls
- Assembly hash verification: Plugin assemblies are hashed at registration; the host verifies the hash at load time to detect tampering
- No network access for untrusted plugins: The sandbox process has restricted network permissions; plugins that need network access must be at least Trusted
- Audit trail: Plugin lifecycle events (registration, activation, failure, shutdown) are logged with timestamps and actor identity
Observability
- Metrics:
plugin_active_count{trust_level},plugin_load_duration_ms,plugin_failures_total{plugin_id},sandbox_process_restarts_total - Logs: Structured logs with
pluginId,trustLevel,lifecycleState,capability - Health: The registry exposes plugin health status; modules can query whether a required plugin is active
Performance Characteristics
- BuiltIn plugins: zero overhead (direct method dispatch)
- Trusted plugins: negligible overhead (monitoring wrapper)
- Untrusted plugins: gRPC serialization cost per call (~1-5ms depending on payload size)
- Plugin discovery: runs at host startup; cached until restart or explicit re-scan
References
- Module README
- Integrations Architecture - Primary consumer
- Scanner Architecture - Plugin-based analysis