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.
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"] }
}
]
| Field | Required | Constraints | Notes |
|---|---|---|---|
permission | Yes | one of the 14, see Permission list | Permission name. |
worker | Yes | local identifier or host | Who holds it: a real worker id, or host for the interface side (a surface). |
optional | No | boolean, default false | Optional permissions are ticked at install and can be revoked later. |
scope.domains | No | ≤64 items, such as api.example.com, *.example.com, host:port | Domains a network request may target. |
scope.methods | No | GET | HEAD | POST | PUT | PATCH | DELETE | Allowed HTTP methods. |
scope.paths | No | ≤64 items | Knowledge source paths that may be read; only for fs.source.read. |
scope.allow_insecure_http | No | boolean, default false | Whether 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 by | Permissions 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 id | fs.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
scopeorconnectduring 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.
| Permission | Interface name | Held by | What it is for |
|---|---|---|---|
fs.source.read | Read knowledge source | Worker | Lets a worker read knowledge source files you authorized. |
net.fetch | Network requests | host or Worker | HTTP requests made through the host's proxy. |
storage.kv | Key-value storage | Workspace | Keeps a small amount of settings and index data. |
storage.blob | Image and file storage | Workspace | Keeps larger data such as documents, images, audio and media files. |
events.connect | Long-lived event connection | Worker | The host keeps an SSE, WebSocket or Webhook relay connection open. |
files.dialog | System file dialogs (open and export) | Workspace | Lets you import or export files through system dialogs. |
runtime.ai | — | Workspace | Calls the AI runtime the host runs; official source only. |
agent.context | Agent context | Workspace | Publishes a snapshot of plugin state to the Agent in the current chat. |
agent.events | Agent events | Workspace | Receives actions from the Agent and changes plugin state. |
ui.open_external | Open links in the system browser | Workspace | Opens a URL in your system browser when you click. |
agent.trigger | Proactively trigger the Agent | Workspace | The plugin sends a message to the main chat on its own. |
agent.capture | Provide the Agent with plugin screenshots | Workspace | Gives the Agent a screenshot of a chosen part of the plugin interface. |
service.invoke | Call services | Worker | Calls a global Worker service. |
system.metrics.read | Read aggregated system metrics | host | Reads 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.statandhost.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 callshost.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 declaresconnectmust also requestnet.fetchfor host. - Notes: The interface side can reach any http/https address, this machine and the local network included, and
scopeandconnectare only shown on the install page rather than enforced. The worker side allows https on port 443 only, with the domain and method matchingscopeand the target resolving to a public address; reach local or network services from the interface side. It cannot carryscope.paths. See Storage, files and network.
storage.kv
- What it is for:
storage.get/set/delete, keeping settings, indexes andblob_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.blobinstead.
storage.blob
- What it is for:
blob.put/delete/list/resolvefor larger data; it is also the precondition for file drops, file dialogs, screenshots and network responses of typeblob. - 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 declaredrops,files.dialogorcapture_providers.
events.connect
- What it is for: Lets the host hold a long-lived connection for
event_channelsand 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.openFileopens a system dialog to import a file, andfiles.exportBlobwrites a blob to the place you choose. - Grant: Workspace scope, may be written as
host;storage.blobmust 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.publishpublishes 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.contextandskillsmust 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.openor 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_providersto give the Agent a screenshot of a chosen part of the plugin interface. - Grant: Workspace scope, may be written as
host;storage.blobmust 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_servicesthroughhost.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.invokecan 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:
| When | Requirement |
|---|---|
Requesting files.dialog | Also request storage.blob |
A workspace declares drops | Request storage.blob |
A workspace declares capture_providers | Request agent.capture and storage.blob |
A workspace with skills | skills and agent.context as a pair |
| A workspace that can bind an Agent | agent.context cannot be optional, and agent.events cannot be optional when the Agent may write |
Declaring ai_runtime | As a pair with runtime.ai, official source only |
A workspace or top bar item declares connect | Request net.fetch with "worker": "host" |
Declaring required_services | The matching worker requests service.invoke |
Declaring event_channels | The matching worker requests events.connect |
Declaring credentials | The 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.