Execution Modes¶
Xcore supports two primary execution modes for plugins: Trusted and Sandboxed. The choice depends on the source of the plugin and the required level of performance versus security.
Comparison Table¶
| Feature | Trusted Mode | Sandboxed Mode |
|---|---|---|
| Process | Main Process | Isolated Subprocess |
| Performance | Native (Fast) | IPC Overhead (Medium) |
| Security | None (Full Access) | High (Restricted) |
| Filesystem | Full Access | Restricted (FilesystemGuard) |
| Imports | All Python modules | Whitelisted modules only |
| Resource Limits | Shared with App | CPU, Memory & Disk Quotas |
| Use Case | Core business logic | 3rd-party, Untrusted, or Experimental |
Trusted Mode¶
Trusted plugins run directly in the main FastAPI process. They have full access to the system, including environment variables, sensitive files, and the entire Python standard library.
Signature Verification¶
To prevent malicious code from being injected into the plugins/ directory, Xcore supports mandatory signature verification for Trusted plugins.
When enabled, Xcore will refuse to load any Trusted plugin that does not have a valid plugin.sig file generated using the xcore plugin sign command.
Sandboxed Mode¶
Sandboxed plugins run in an isolated subprocess. Xcore applies multiple layers of security to ensure that the sandboxed code cannot compromise the host system.
1. Filesystem Guard¶
The FilesystemGuard intercepts all file operations (via open, os, and pathlib).
- Fail-closed: By default, the plugin can only access its own data/ directory.
- Traversal Protection: Relative paths are resolved and checked against the plugin root.
2. Import Restrictions¶
The sandbox blocks access to sensitive Python modules such as subprocess, os, ctypes, socket, and shutil. Any attempt to import these will raise a PermissionError.
3. Resource Limits¶
Resource limits are enforced via RLIMIT_AS (Memory) and RLIMIT_CPU (CPU) on Linux systems.
4. Inter-Process Communication (IPC)¶
Since the plugin runs in a separate process, communication happens via a JSON newline-delimited protocol over standard I/O pipes.
sequenceDiagram
participant K as Kernel (Main)
participant S as Supervisor
participant W as Worker (Subprocess)
participant P as Plugin Instance
K->>S: call("my_plugin", "sum", {a:1, b:2})
S->>W: {"action": "sum", "payload": {a:1, b:2}}\n
W->>P: handle("sum", {a:1, b:2})
P-->>W: {status: "ok", result: 3}
W-->>S: {"status": "ok", "result": 3}\n
S-->>K: {status: "ok", result: 3}
Configuration¶
Declaring Mode in plugin.yaml¶
- Accepted values:
trusted|sandboxed.
Common Errors & Pitfalls¶
Sandboxed: PermissionDenied
If a sandboxed plugin tries to access a file outside allowed_paths, or imports a forbidden module, it will receive a PermissionError.
Fix: Explicitly add the path to allowed_paths or use a Trusted plugin if full access is required.
IPC Overhead
Every call to a sandboxed plugin involves JSON serialization/deserialization and process context switching. Avoid using sandboxed plugins for high-frequency operations (e.g., millions of calls per second).
DiskQuotaExceeded
If the data/ directory exceeds max_disk_mb, the SandboxProcessManager will block further call() attempts.
Fix: Clean up temporary files or increase the quota in plugin.yaml.
Best Practices¶
Use Sandboxing for Extension Points
If your framework allows users to upload their own logic (e.g., a "Transformation Plugin"), always use Sandboxed mode.
Health Checks
Enable health checks for sandboxed plugins to ensure the supervisor can automatically restart them if they crash or hang.