Permissions
A permission in WAMP gates a named host API or discloses an extension’s intent.
There is no permission prompt and no runtime consent dialog. If you did not
declare notifications, then ctx.api.notifications is undefined. Understanding
which form applies saves you from both silent absence and rejected calls.
After this page you can tell which declarations actually withhold something, which are disclosure labels the runtime does not enforce, and how to write main-process code that fails loudly rather than quietly when a permission is missing.
Declaring them
Section titled “Declaring them”{ "permissions": [ "notifications", "process", { "auth.outbound": ["https://api.example.com"] } ]}Eight string values are valid — network, ai, terminal,
filesystem, http-routes, notifications, process, auth.identity — plus two structured entries, auth.outbound and
auth.resource, which carry data. There is no other form, and the array rejects
anything else.
terminal remains an accepted disclosure label but unlocks no current API.
New extensions should omit it.
Permissions are read once, at registration. Changing them means editing
extension.json, which triggers a re-scan and a fresh context — the change
takes effect on the next activation, not on the current one.
Enforced permissions
Section titled “Enforced permissions”These withhold a capability. Declare them or the namespace is not there.
| Permission | Unlocks | Enforced where |
|---|---|---|
http-routes |
ctx.api.http — local HTTP routes for webhooks |
Main process |
notifications |
ctx.api.notifications — system notifications |
Main process |
process |
ctx.api.process, ctx.api.pty, ctx.api.git |
Main process |
auth.identity |
ctx.api.auth — identity of the signed-in user |
Main process |
{ "auth.outbound": [...] } |
The origins auth.fetch may target |
At every call |
{ "auth.resource": [...] } |
pluginAPI.auth.resourceFetch — reads of a connected resource as the viewer, WAMP Desktop only |
Desktop main, at every call |
Four of those deserve their own note.
process unlocks three namespaces
Section titled “process unlocks three namespaces”process (child processes), pty (interactive terminal sessions), and git
(worktrees and diffs) all sit behind the single process permission. Every
operation in git is a git subprocess spawn, so it grants nothing process
does not already imply, and a pty is a child process with a terminal
attached. One permission, three namespaces, deliberately — the vocabulary stays
shorter than the surface.
pty is the one to reach for when you are running a CLI: it negotiates size,
reports itself as xterm-256color, and lets a full terminal UI run the way it
does in a real terminal. Piping stdio through process instead gets you
line-buffered text with no resize and no interactivity.
auth.identity and auth.outbound are two different things
Section titled “auth.identity and auth.outbound are two different things”auth.identity unlocks ctx.api.auth in the main process: read the signed-in
user, subscribe to session changes. Declaring it alone gets you read-only
identity.
auth.outbound is the origin allowlist for auth.fetch, the call that reaches
your own backend with a WAMP-signed, audience-bound token attached. Matching is
exact origin — scheme, host, and port must all match, and there are no
globs. Loopback origins are auto-allowed in a development build; a production
build is strict.
{ "permissions": [ "auth.identity", { "auth.outbound": ["https://api.example.com", "https://events.example.com"] } ]}The origin check runs inside the auth service, so it applies to the call
regardless of which half made it. Today, auth.identity itself gates only the
main-process ctx.api.auth; the renderer’s pluginAPI.auth.getSession is not
withheld when the permission is absent. Declare it anyway — the catalog shows
what you declared, and relying on the gap is relying on a bug.
auth.resource reads someone else’s service as the viewer
Section titled “auth.resource reads someone else’s service as the viewer”auth.outbound reaches your backend with a token minted for your extension.
auth.resource reaches a registered connected resource — a service another
team runs, such as an organization knowledge base — with the viewer’s resource
identity. Each entry names the registered audience and the API base:
{ "permissions": [ { "auth.resource": [ { "audience": "internal-docs", "baseUrl": "https://docs.example.com/api/v1" } ] } ]}WAMP Desktop resolves the entry from the loaded manifest on every call, checks
the path against baseUrl, and sends one GET; the renderer gets status, three
headers and parsed JSON, never the token. It unlocks no ctx.api namespace.
Cloud hosts reject the call with host_unsupported, so mark such an extension
requiresElectron. Rules, limits and error codes:
Read your service from extension UI.
Declarative permissions
Section titled “Declarative permissions”These declarations label the extension’s intent. They do not withhold the
ordinary plugin APIs below. The ai declaration additionally gates host model
access for ACP runtimes that declare modelAccess: "host"; it is not a general
plugin inference sandbox.
| Permission | Describes |
|---|---|
ai |
Model calls — ctx.api.ai, pluginAPI.ai, agent delegation |
filesystem |
File reads and writes — ctx.api.fs, pluginAPI.fs |
network |
Outbound HTTP from the server half; use ctx.api.auth.fetch for declared backend origins |
terminal |
No current API; use ctx.api.process in the main half |
Always available, no permission needed
Section titled “Always available, no permission needed”Everything else on ctx.api is present regardless of what you declared:
workspace, ai, agents, fs, events, data, tools, secrets,
dialog, shell, ipc, tokens, services, commands, disposables,
storage, webviews, and settings.
Two of those are ungated for a reason worth knowing. dialog opens a native
file picker, which cannot happen without the user choosing a file — the
interaction is its own gate. tools.register adds a tool to the assistant’s
surface, which the user then sees in every tool card; hiding it behind a
permission would not make it more visible.
Capabilities are a separate list
Section titled “Capabilities are a separate list”capabilities in the manifest is not a permission list and does not overlap
with one. It carries four values — webviews.navigate,
webviews.executeScript, webviews.interceptRequests, and
webRequestRewrite — and it gates individual operations on
ctx.api.webviews rather than the namespace as a whole. Declaring any of the
three webviews.* values also selects the more capable webview backing, so it
changes what the webview is, not only what you may ask of it. An unrecognized
string is accepted and matches nothing. See
the manifest.
Absence, not refusal — and how to write for it
Section titled “Absence, not refusal — and how to write for it”Here is the trap, and it costs real time.
A missing permission does not produce an error naming the permission. The
namespace is absent, so an unchecked call throws a TypeError about a property
of undefined. The SDK marks every gated namespace optional — http?,
notifications?, process?, pty?, git?, auth? — so the compiler catches
unchecked access. Guard once at activation and name the missing permission:
import type { PluginContext } from '@wamp/extension-sdk';
export async function activate(ctx: PluginContext): Promise<void> { const { notifications } = ctx.api; if (!notifications) { throw new Error('reminders: needs the "notifications" permission'); } notifications.show({ title: 'Daily check', body: 'Nothing overdue.' });}Use the SDK declarations as the supported contract. Runtime inspection can show additional methods, but those are not a stable authoring surface. Known gaps are called out in the API reference.
Host-conditional namespaces are not permissions
Section titled “Host-conditional namespaces are not permissions”Two namespaces are optional for a reason that has nothing to do with what you declared, and they look identical from inside your code:
| Namespace | Absent when |
|---|---|
ctx.api.sessions |
The host has no user interface — nobody is looking at a session |
ctx.api.clientAffordances |
The host does not answer reverse calls from a remote core |
No permission makes either appear. Guard with ?. here — this is the one place
where the optional-chaining shortcut is the correct answer, because absence is
a legitimate deployment shape rather than a misconfiguration.
- The plugin API — the surface each permission unlocks, with a working snippet per namespace.
- The manifest — where permissions sit among the other twenty fields.