Skip to content

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.

{
"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.

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 (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.

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

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 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.