# Permissions What each permission in extension.json actually unlocks, which ones the runtime enforces, and why a missing declaration surfaces as undefined rather than as an error. Source: https://docs.vampikez.fun/build/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. Permissions constrain WAMP APIs only; they do not sandbox extension JavaScript. A `main` or `server` bundle runs as Node code in the host process and can read files and `process.env`, open sockets, or spawn processes without declaring the matching permission. A UI bundle runs in the host renderer's page realm and can reach its DOM and exposed globals. Install an extension only when you trust its code, not because its permission list looks narrow. 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 ```json { "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 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 `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 `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. ```json { "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 `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: ```json { "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](/identity/connected-resources/#read-your-service-from-extension-ui). ## 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 | Leaving `ai` undeclared does not prevent an extension from calling a model, and leaving `filesystem` undeclared does not prevent it from reading files. These declarations are honest labelling, not a sandbox. Declare them accurately so the catalog disclosure is accurate — and, if you are reviewing an extension, read its code rather than its permission list for these four. ## 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 `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](/build/manifest/#capabilities). ## 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: ```ts export async function activate(ctx: PluginContext): Promise { 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](/reference/plugin-api/). ## 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. ## Next - [The plugin API](/build/plugin-api/) — the surface each permission unlocks, with a working snippet per namespace. - [The manifest](/build/manifest/) — where permissions sit among the other twenty fields.