# Capabilities and permissions

`addon.json` `capabilities` is parsed into the struct below. The admin approves it at install, and every host call is checked against it.

Capabilities approved at install (from `addon.json`).

| Field | Type | Required | Description |
|---|---|---|---|
| `http` | `Vec<String>` | no | Allowed hosts: `api.stripe.com`, `*.example.com`, `$server`, `$setting:<key>`. |
| `secrets` | `Vec<String>` | no |  |
| `kv` | `bool` | no |  |
| `events` | `Vec<String>` | no |  |
| `hooks` | `Vec<Hook>` | no | Synchronous pre-action hooks (`invoice.before_create`, …). |
| `routes` | `Vec<Route>` | no | HTTP routes served under `/api/v1/addons/<id>/…`. |
| `ui` | `Vec<String>` | no | UI powers beyond the declarative schema: `iframe` = sandboxed pages and widgets served from the addon's `assets/` (admin-approved). |
| `bridge` | `Vec<String>` | no | Core `GET /api/v1/…` prefixes an iframe may read through the postMessage bridge (with the viewer's own permissions). |
| `http_private` | `bool` | no | `http: ["*"]` may also reach loopback/private/LAN addresses. Link-local and cloud metadata stay refused. |
| `money` | `MoneyCaps` | no | Change money amounts in pre-event payloads (audited, exact Decimal). |
| `db` | `DbCaps` | no | Core data access through the host (`power` interface). |
| `fs` | `bool` | no | Private data directory, mounted at `/data` (WASI preopen). |
| `exec` | `Vec<String>` | no | Absolute program paths the addon may run (no shell). Off in Docker. |
| `exec_args` | `HashMap<String, Vec<String>>` | no | Optional per-program argument allowlist: each argument must equal a pattern or match a `prefix*` pattern. Programs not listed only get the baseline checks in [`validate_exec_args`]. |
| `fs_quota_mb` | `Option<u32>` | no | Size cap for the `/data` directory in MiB (default 64, max 1024). Over quota the directory is mounted read-only. |
| `jobs` | `Vec<Job>` | no | Background jobs: ops the addon may queue, and cron schedules. |
| `auth_provider` | `bool` | no | Custom login: the host asks the addon to verify credentials (op `auth.verify`). It never sees stored password hashes. |
| `emits` | `Vec<String>` | no | Custom events this addon may emit (`<id>:<name>`, `*` suffix allowed). |
| `mixins` | `Vec<Mixin>` | no | Host-enforced wrappers around approved host operations. |

Source: `crates/pelty-wasm/src/runtime.rs` (`Capabilities`).

### Hook

A pre-event subscription: the host calls `call("hook", {event, payload})` before the action and the addon may continue, cancel or modify it.

| Field | Type | Required | Description |
|---|---|---|---|
| `event` | `String` | yes |  |
| `priority` | `i32` | no | Lower runs first; ties ordered by addon id. Accepts an integer or a Bukkit-style name: `lowest` (0), `low` (50), `normal` (100), `high` (150), `highest` (200), `monitor` (1000, observe only). |
| `ignore_cancelled` | `bool` | no | Also receive the event when an earlier listener cancelled it. |
| `fail_open` | `bool` | no | On error/timeout: `true` lets the action continue, `false` blocks it. |
| `timeout_ms` | `u64` | no | Hook budget in milliseconds (capped by the sandbox timeout). |

Source: `crates/pelty-wasm/src/runtime.rs` (`Hook`).

### Mixin

A mixin: wraps an approved host operation (an API route template such as `POST /api/v1/client/orders`). `at`: `before` (rewrite the input), `after` (rewrite the output within its schema), `around` (both, with shared state) or `return` (replace the output, same schema).

| Field | Type | Required | Description |
|---|---|---|---|
| `target` | `String` | yes |  |
| `at` | `String` | yes |  |
| `priority` | `i32` | no |  |
| `timeout_ms` | `u64` | no |  |

Source: `crates/pelty-wasm/src/runtime.rs` (`Mixin`).

### Route

An addon HTTP route. `access`: `admin` (needs `permission`), `client` (any signed-in user) or `public`.

| Field | Type | Required | Description |
|---|---|---|---|
| `method` | `String` | yes |  |
| `path` | `String` | yes | Path below the addon prefix, e.g. `/stats` or `/items/:id`. |
| `access` | `String` | yes |  |
| `permission` | `Option<String>` | no |  |

Source: `crates/pelty-wasm/src/runtime.rs` (`Route`).

### Job

A background job op; `every_secs` > 0 also runs it on a schedule.

| Field | Type | Required | Description |
|---|---|---|---|
| `op` | `String` | yes |  |
| `every_secs` | `u64` | no |  |

Source: `crates/pelty-wasm/src/runtime.rs` (`Job`).

### MoneyCaps

| Field | Type | Required | Description |
|---|---|---|---|
| `modify` | `bool` | no |  |

Source: `crates/pelty-wasm/src/runtime.rs` (`MoneyCaps`).

### DbCaps

| Field | Type | Required | Description |
|---|---|---|---|
| `read` | `Vec<String>` | no | Entities: `clients`, `services`, `invoices`, `orders`, `tickets`, … |
| `write` | `Vec<String>` | no | Typed write ops: `client.note`, `ticket.reply`, … |
| `sql` | `bool` | no | Free SQL on `addon_<id>_*` tables. |

Source: `crates/pelty-wasm/src/runtime.rs` (`DbCaps`).

### Dangerous capabilities

```rust
    /// Capabilities shown with a red warning at install; the admin must tick
    /// each one (`approve` list in the install request).
    pub fn dangerous(&self) -> Vec<&'static str> {
        let mut v = vec![];
        if self.iframe() {
            v.push("ui.iframe");
        }
        if self.iframe_same_origin() {
            v.push("ui.iframe.same_origin");
        }
        if self.http.iter().any(|h| h == "*") {
            v.push("http.any");
        }
        if self.http_private {
            v.push("http.private");
        }
        if self.money.modify {
            v.push("money.modify");
        }
        if !self.db.read.is_empty() {
            v.push("db.read");
        }
        if !self.db.write.is_empty() {
            v.push("db.write");
        }
        if self.db.sql {
            v.push("db.sql");
        }
        if self.fs {
            v.push("fs");
        }
        if !self.exec.is_empty() {
            v.push("exec");
        }
        if self.auth_provider {
            v.push("auth.provider");
        }
        if self.hooks.iter().any(|h| is_request_pattern(split_phase(&h.event).1)) {
            v.push("hooks.any");
        }
        if !self.mixins.is_empty() {
            v.push("mixins");
        }
        if self.mixins.iter().any(|m| m.at == "return" || m.at == "around") {
            v.push("mixins.return");
        }
        v
    }
```

## Permissions

Coarse package permissions in `addon.json` `permissions` (shown at install):

- `payments.process`
- `provisioning.manage`
- `domains.manage`
- `network.outbound`
- `db.migrate`
- `ui.extend`

Addon kinds: `provisioning`, `gateway`, `registrar`, `theme`, `plugin`, `captcha`, `sso`, `integration`.

