Permissions
Kunkun's capability-based permission model — declare, scope, request, and how it's enforced.
Extensions declare the capabilities they need in their manifest. Users approve them at install time; some sensitive operations additionally prompt at runtime. Enforcement always happens in the host — never in your plugin code.
Two forms of permission
Simple — a capability with no scope:
{ "kunkun": { "permissions": ["clipboard-read", "storage", "notifications"] } }Scoped — an object with an allow/deny list or domain list:
{
"permission": "fs-read",
"allow": ["$EXTENSION_SUPPORT/**", "$HOME/Documents/**"],
"deny": ["$HOME/Documents/Private/**"]
}Permission catalogue
| Permission | Controls | Scope shape |
|---|---|---|
clipboard-read / clipboard-write | Clipboard read / write | — |
storage | KV storage and record storage | — |
notifications | System notifications | — |
fs-read / fs-write | File I/O | allow / deny glob arrays |
network | fetch | domains glob array |
shell | Command / script execution | commands: [{ program, args? }] or scripts |
open-url / open-file / open-folder | Open with default app | allow / deny |
system-open / system-trash / system-finder / system-applications / system-selected-text | System integrations | — |
dialog | Native file/save/message dialogs | — |
window-control | Resize/move/vibrancy/etc. | — |
path | Path utilities & aliases | — |
ai | Plugin AI APIs | — |
services | Discover/call other extensions' services | — |
backend | Spawn a managed backend process | allow: [{ script, runtime?, capabilities? }] |
input-monitor | Keyboard/mouse event subscriptions | — |
Path aliases
Scopes use cross-platform aliases resolved by the host:
| Alias | Resolves to |
|---|---|
$HOME | User home |
$DESKTOP / $DOWNLOAD / $DOCUMENT | Standard user folders |
$EXTENSION | Your install directory (read-only) |
$EXTENSION_SUPPORT | Your writable data directory |
Globs: ** (recursive), * (one level), *.ext (extension match).
Least privilege
Prefer $EXTENSION_SUPPORT/** for your own data and request the narrowest scopes that work. Broad $HOME/** access is a red flag reviewers (and users) will notice.
Runtime (dynamic) grants
Some capabilities are best requested just before you need them, scoped to what the user is doing:
import { permissions } from "@kunkunsh/sdk";
const granted = await permissions.request(
"fs-read",
"$HOME/Documents/**",
"Import the files you selected", // shown in the prompt
);
if (granted) { /* … */ }- Allow Once → stored in a session map, cleared when the window closes.
- Always Allow → persisted in the
permission_grantstable, user-revocable in settings. - Deny → returns
false.
permissions.check(type, scope) is a preflight query — not an authorization boundary. Always let the host enforce.
How enforcement works
Notice scoped filesystem checks resolve symlinks before matching, so a symlink can't smuggle access outside your allow-list.
Service permission intersection
When extension A calls extension B's service, the effective permissions compose safely:
- Caller resource permissions (fs, network, open-*) for user-chosen resources are preserved.
- Provider implementation permissions (e.g. the provider's scoped
shellaccess toffmpeg) are supplied by the provider, so callers don't have to declare the provider's private details. - Everything else requires both sides.
This prevents privilege escalation: a low-permission extension can't gain capabilities by calling a high-permission service.
Raycast compatibility policy
Imported Raycast extensions run under a stricter policy. Each host method is tagged raycast-allowed or kunkun-only; Kunkun-only APIs (e.g. shell.spawn) are rejected for Raycast-sourced plugins even if the namespace is imported. Native Kunkun extensions get the full surface (still subject to manifest permissions).
Process sandbox
For Node modes and backends, the host derives runtime flags from your manifest:
| Runtime | Sandbox |
|---|---|
| Deno (preferred) | --allow-read/--allow-write scoped to your paths, --allow-net=domain per declared domain, scoped --allow-run/--allow-ffi/--allow-env/--allow-sys |
| Node.js v25+ | Node permission flags; blanket env/sys are implicit (Node never gates them), but scoped env/sys/subprocess, domain-scoped network, and FFI are rejected unless capabilities.unsandboxed is declared |
| Bun | Rejected unless capabilities.unsandboxed (Bun has no sandbox) |
Your install directory is read-only; your support directory is writable. See Backend Processes for spawning managed child processes.
Checklist
- Request the minimum permissions; scope filesystem/network tightly.
- Use
$EXTENSION_SUPPORTfor your own data. - Request sensitive scopes at runtime with a clear reason.
- Handle denial gracefully — check before acting, degrade rather than crash.
- Document why you need each permission in your README.