Permissions

Applies to: StablEdge Desktop 0.4.4, plugin manifest protocol v3

Every capability a plugin uses must be declared in requested_permissions. A declaration only sets the ceiling you ask for, and the grants you give are what count; the host checks the manifest, the grants, the distribution policy and the current session again on every call.

0.4.5 adds pages, profile_items, editors and windows, plus the ui.window* permissions; see Surfaces (Draft).

Declaration format

"requested_permissions": [
  { "permission": "storage.kv", "worker": "host", "optional": true },
  {
    "permission": "net.fetch",
    "worker": "main",
    "optional": false,
    "scope": { "domains": ["api.example.com"], "methods": ["GET", "POST"] }
  }
]
FieldRequiredConstraintsNotes
permissionYesone of the 14, see Permission listPermission name.
workerYeslocal identifier or hostWho holds it: a real worker id, or host for the interface side (a surface).
optionalNoboolean, default falseOptional permissions are ticked at install and can be revoked later.
scope.domainsNo≤64 items, such as api.example.com, *.example.com, host:portDomains a network request may target.
scope.methodsNoGET | HEAD | POST | PUT | PATCH | DELETEAllowed HTTP methods.
scope.pathsNo≤64 itemsKnowledge source paths that may be read; only for fs.source.read.
scope.allow_insecure_httpNoboolean, default falseWhether plain HTTP is allowed.

How scope is used: net.fetch may carry domains and methods but not paths; fs.source.read must carry paths; no other permission may carry scope.

Who holds a permission: host and Worker

One permission name means different things depending on who holds it. The host records grants for the two separately and they do not carry over: a grant given to the interface side (host) does not let a worker reach the network, and the other way round.

Held byPermissions available
Host level (host)net.fetch, system.metrics.read
Workspace scope (may be written as host, granted per workspace)agent.context, agent.events, storage.kv, storage.blob, files.dialog, runtime.ai, ui.open_external, agent.trigger, agent.capture
Must be a real worker idfs.source.read, service.invoke, events.connect, and net.fetch for a worker

Workspace-scoped permissions have nothing to do with the worker field, so there is no need to create an empty worker just to hold a permission.

How grants work

  • Required permissions (optional: false): granted when you confirm the install.
  • Optional permissions (optional: true): you tick them in the install preview, and can turn them on or revoke them later on the plugin detail page. A plugin must handle the ungranted case, show how to enable the permission, and not retry in a loop.
  • Worker permissions: recorded separately in worker grants after install, apart from interface-side grants.
  • Upgrades: interface-side grants are recorded by permission name, so adding or removing scope or connect during an upgrade does not reset them; required permissions are granted automatically and optional ones keep the choice you made before.

Permission list

The interface names below are what the desktop app shows in the install preview and on the plugin detail page.

PermissionInterface nameHeld byWhat it is for
fs.source.readRead knowledge sourceWorkerLets a worker read knowledge source files you authorized.
net.fetchNetwork requestshost or WorkerHTTP requests made through the host's proxy.
storage.kvKey-value storageWorkspaceKeeps a small amount of settings and index data.
storage.blobImage and file storageWorkspaceKeeps larger data such as documents, images, audio and media files.
events.connectLong-lived event connectionWorkerThe host keeps an SSE, WebSocket or Webhook relay connection open.
files.dialogSystem file dialogs (open and export)WorkspaceLets you import or export files through system dialogs.
runtime.ai—WorkspaceCalls the AI runtime the host runs; official source only.
agent.contextAgent contextWorkspacePublishes a snapshot of plugin state to the Agent in the current chat.
agent.eventsAgent eventsWorkspaceReceives actions from the Agent and changes plugin state.
ui.open_externalOpen links in the system browserWorkspaceOpens a URL in your system browser when you click.
agent.triggerProactively trigger the AgentWorkspaceThe plugin sends a message to the main chat on its own.
agent.captureProvide the Agent with plugin screenshotsWorkspaceGives the Agent a screenshot of a chosen part of the plugin interface.
service.invokeCall servicesWorkerCalls a global Worker service.
system.metrics.readRead aggregated system metricshostReads aggregated system metrics such as CPU and memory.

Permission by permission

fs.source.read

  • What it is for: A worker reads knowledge source files through host.fs.stat and host.fs.read.
  • Grant: Held by a real worker id, and it must carry scope.paths.
  • Notes: A worker only receives relative paths or an opaque file token, never the real root. A token is bound to the plugin, version, worker, grant and an expiry, and stops working when the grant is revoked, the plugin is disabled or the plugin is upgraded.

net.fetch

  • What it is for: The interface side calls stabledgePlugin.net.fetch, or a worker calls host.net.fetch.
  • Grant: Write "worker": "host" for the interface side and a real worker id for the worker side; these are two independent grants. A workspace or top bar item that declares connect must also request net.fetch for host.
  • Notes: The interface side can reach any http/https address, this machine and the local network included, and scope and connect are only shown on the install page rather than enforced. The worker side allows https on port 443 only, with the domain and method matching scope and the target resolving to a public address; reach local or network services from the interface side. It cannot carry scope.paths. See Storage, files and network.

storage.kv

  • What it is for: storage.get / set / delete, keeping settings, indexes and blob_id.
  • Grant: Workspace scope, may be written as host.
  • Notes: 64 KiB per entry and 1 MiB per plugin in total. Keep documents, thumbnails, Base64 data and unbounded arrays out of KV and use storage.blob instead.

storage.blob

  • What it is for: blob.put / delete / list / resolve for larger data; it is also the precondition for file drops, file dialogs, screenshots and network responses of type blob.
  • Grant: Workspace scope, may be written as host.
  • Notes: 64 MiB per blob and 512 MiB per plugin by default, adjustable in Settings → Plugins, and the value storage.getUsage() returns is what counts. It must also be requested when you declare drops, files.dialog or capture_providers.

events.connect

  • What it is for: Lets the host hold a long-lived connection for event_channels and deliver events to a worker.
  • Grant: Held by the real worker id that handles the events; without the grant no connection is made.
  • Notes: Revoking the permission, deleting the credential or disabling the plugin stops the connection at once.

files.dialog

  • What it is for: files.openFile opens a system dialog to import a file, and files.exportBlob writes a blob to the place you choose.
  • Grant: Workspace scope, may be written as host; storage.blob must be requested as well.
  • Notes: A plugin never receives a system path; an imported file is handed to it as a blob.

runtime.ai

  • What it is for: Calls the named requests declared in the manifest's ai_runtime.
  • Grant: Must come as a pair with ai_runtime, and a manifest with one and not the other is refused at install.
  • Notes: Open to the official source only, and a package from a third-party publisher is refused outright.

agent.context

  • What it is for: context.publish publishes a snapshot of plugin state that the Agent reads to see the current workspace.
  • Grant: Workspace scope, may be written as host.
  • Notes: With a workspace, agent.context and skills must come as a pair. In a workspace that can bind an Agent the permission cannot be optional. A serialized snapshot stays under 64 KiB and must not contain credentials, paths or unbounded body text.

agent.events

  • What it is for: Receives the actions the Agent sends, through events.on("agent.custom", …).
  • Grant: Workspace scope, may be written as host. It cannot be optional when the Agent may write, and a read-only binding can leave it out.
  • Notes: Bump the snapshot version and publish once after handling each action, otherwise the host treats the action as not applied.

ui.open_external

  • What it is for: ui.openExternal(url) opens a URL in your system browser.
  • Grant: Workspace scope, may be written as host, and is usually optional.
  • Notes: Call it only inside a real user activation such as a click, and it accepts absolute http(s) URLs only. Do not use window.open or a top-level navigation instead.

agent.trigger

  • What it is for: agent.trigger({ message }) sends a message to the main chat on its own.
  • Grant: Workspace scope, may be written as host; optional is recommended.
  • Notes: Only a workspace that is focused, has published a snapshot and holds a valid binding can trigger. The call fails while the Agent is already replying, when the workspace is unfocused, when the grant is revoked or when it is rate limited. Make it clear in the interface when a message will be sent, and show a notice on failure instead of retrying in a loop.

agent.capture

  • What it is for: Works with a workspace's capture_providers to give the Agent a screenshot of a chosen part of the plugin interface.
  • Grant: Workspace scope, may be written as host; storage.blob must be requested as well.
  • Notes: Only the plugin area declared in the manifest can be captured, and the result is a PNG with exact dimensions; the whole screen, the host interface, other plugins and other apps cannot be captured.

service.invoke

  • What it is for: A worker calls a global Worker service declared in required_services through host.service.invoke.
  • Grant: Held by a real worker id and must match that worker's required_services.
  • Notes: The service that gets called does not inherit the calling plugin's permissions. When an optional dependency is missing, the plugin needs a clear local fallback.

system.metrics.read

  • What it is for: The interface side reads aggregated system metrics through services.invoke("host.system_metrics", "sample", {}), which a top bar monitor uses a lot.
  • Grant: A host-level permission, written as "worker": "host".
  • Notes: services.invoke can call only the services and methods the host has registered; it is not a general command channel.

Combination rules

These rules are checked at pack time and at install, and a package that breaks one is refused:

WhenRequirement
Requesting files.dialogAlso request storage.blob
A workspace declares dropsRequest storage.blob
A workspace declares capture_providersRequest agent.capture and storage.blob
A workspace with skillsskills and agent.context as a pair
A workspace that can bind an Agentagent.context cannot be optional, and agent.events cannot be optional when the Agent may write
Declaring ai_runtimeAs a pair with runtime.ai, official source only
A workspace or top bar item declares connectRequest net.fetch with "worker": "host"
Declaring required_servicesThe matching worker requests service.invoke
Declaring event_channelsThe matching worker requests events.connect
Declaring credentialsThe matching worker requests net.fetch or events.connect

Recommendations

  • Ask only for the permissions the current feature really uses, and do not reserve any for "we may need it later".
  • Make non-core capabilities optional (external links, proactively triggering the Agent and so on) and handle the ungranted state in the interface.
  • When a grant is revoked, the plugin is disabled or it is upgraded, older sessions and tokens stop working; the plugin has to recover from that instead of showing a blank page.
On this page