Plugin Runtime and Security
Plugin security protects a local execution boundary, not merely a marketplace page. A Runner can hold third-party credentials, read user-authorized files, start binaries, and perform externally visible writes. Every remote instruction must therefore identify the exact user, device generation, immutable release, connection, permission, and payload.
Execution Boundary
If the selected device is offline, fenced, missing its verified runtime, or cannot durably persist an inbound event, the plugin is unavailable. The system does not select another device, execute on the server, or retain work for later execution.
Device Trust Chain
Registration, lease, tickets, and WSS solve different problems:
Reconnecting a valid Desktop must reuse its enrollment. Re-registering on every click creates duplicate identities and does not repair a lease or readiness failure. Revocation increments the generation, fencing previous WSS bindings, actions, runtime sessions, and calls.
Channel Trust Resources
Each Desktop release channel consumes one channel-specific trust resource. Startup validates all of the following:
- build channel equals trust-resource channel;
- plugin WSS origin is the same as the current backend origin;
- the path is exactly
/scilaxy/ws/v1/runner/device; - at least one Ed25519 public key is valid for the current time and not revoked;
- the server's signing key id resolves to that channel's trusted key.
Dev, Beta/Test, RC/UAT, and Stable/Prod never share a runtime signing identity. Cross-channel fallback would allow a weaker environment to control another channel's local Runner. Origin and exact-path checks prevent proxy drift from redirecting the local execution channel to an unrelated WebSocket endpoint.
These checks are appropriate at the execution boundary. Product surfaces must still distinguish lease_failed, trust_config_invalid, server_signing_unavailable, transport failures, protocol failures, and ordinary readiness blockers. Strong security does not justify an opaque generic error.
Runtime Packages
Optional first-party provider runtimes are not bundled into the Desktop installer and never execute in API or Worker containers.
The Desktop build carries a build-bound plugin-runtime-manifest.json. On first use, the selected Runner:
- resolves the release's platform archive;
- downloads it into the current channel's private cache;
- verifies the manifest, archive digest, and every declared member;
- enforces each member's declared executable role;
- atomically publishes the verified cache entry;
- re-verifies cached evidence before later execution.
Download failure, an unsupported platform, digest mismatch, or missing cache makes the capability unavailable. There is no server provider-host fallback.
Plugin code may contain protocol constants, schema versions, canonical API paths, closed enums, and safety bounds. Product catalog entries belong in builtin/catalog.json; environment origins, channel selection, signing keys, and deployment secrets come from typed build or runtime configuration. Provider protocol endpoints belong only in their provider adapter.
Credential Boundary
Device-local setup uses a plugin_device_action containing only a provider key, profile label, target device/generation, and request digest. The action expires if it is not claimed and completed.
For Feishu:
- the user chooses a profile label and online Desktop;
- Desktop claims the action using a short user-gesture ticket;
- Electron Main opens an isolated prompt for
cnorglobal, App ID, and App Secret; safeStorageencrypts the record into the current channel's private plugin directory;- Desktop signs completion; the server atomically completes the action and creates a
readyconnection; - Runner obtains a one-shot local bootstrap and starts the provider long connection.
The API, Worker, renderer, and public DTOs receive only a profile label, opaque profile id, device id, connection/credential revisions, and irreversible digests. For Feishu and GitHub device-local connections, plugin_connection.credential_id remains null and no operational secret is stored in plugin_secret.
Signed Provider Calls
The inner plugin-provider/1 protocol currently allows closed tools.list and tools.call requests. The outer signed envelope binds:
- signing key id, owner, device, channel, and app version;
- device generation, connection id, nonce, and request id;
- installation, release, capability, and authority digests;
- connection and permission revisions plus credential version;
- issue time, expiry, and complete body digest.
The Runner rejects unknown fields, duplicate JSON keys, expired requests, body mismatch, revision drift, authority drift, and device-generation drift before provider I/O.
External Effects
Potential external writes are represented by plugin_effect_intent. The immutable envelope records exactly what was approved: action, arguments, destination, release/tool/config evidence, connection/permission revisions, device generation, runtime/workspace authority, and provider idempotency key.
The state distinguishes safe failure from ambiguity:
failed_before_dispatch: no provider boundary was crossed; retry can be safe.uncertain: the provider may have accepted the write; do not automatically replay.reconciled_succeededorreconciled_failed: provider evidence resolved the ambiguity.
External writes cannot be configured as permanently allowed. They require a prompt or are denied.
Inbound and Webhooks
Feishu uses its official long connection from the local provider. Runner journals the event, sends it over the authenticated device channel, and waits for server durable acceptance. The server deduplicates, validates current route authority, creates the inbound event/outbox and Turn transactionally, then acknowledges the local digest. An offline device means no server-side collection or deferred delivery.
GitHub Webhook is the controlled public-ingress exception:
- the endpoint is bound to exact installation/connection revisions, device generation, release, and authority digest;
- the server verifies
X-Hub-Signature-256, event type, delivery id, size, and rate limits; - it forwards only while the bound device is online;
- Runner durably persists the event and returns a digest-bound ACK;
- the server returns provider success only after that ACK;
- offline, fenced, timeout, or local-persistence failure returns a retryable failure without an offline work queue.
The server stores webhook verifier material because it must authenticate public ingress, but that material grants no GitHub API authority. It does not store the GitHub token or raw delivery body.
Security Invariants
- Plugin code, operational credentials, processes, calls, and accessed local files live only on the selected Desktop/Runner.
- Device absence is an explicit unavailable result, never a reason to fall back to another device or cloud execution.
- Renderer, public DTOs, API/Worker logs, and audit metadata never contain device-local secrets or executable paths.
- Release, config, tool catalog, permission, connection, device generation, or runtime drift fences old asynchronous work before provider I/O.
- Inbound success requires durable acceptance; external-write ambiguity requires reconciliation rather than blind retry.
- Removed Session snapshot tables, legacy provider-host execution, fallback wire formats, and cross-channel trust are not supported.