Skip to content
DocsPermissions

PluginsEarly

Permissions

How a plugin asks for what it needs, what a person approves, every request and what it allows, and what runs outside the sandbox.

A plugin may do nothing until a person approves it, and then only what its manifest asked for and the person granted. Each plugin’s script runs in a process of its own. It reaches anything outside that process only by asking the daemon, which checks every call against the grant.

Asking and granting

A plugin asks in the [allow] table of its plugin.toml:

[allow]
fs = ["read:workspace"]
net = ["api.github.com"]

A person approves on their own terminal or in the app:

gr plugin review notes    # what you would approve; approves nothing
gr plugin approve notes   # the same review, then the question

The review says, in words, what each request lets the plugin do and what they mean together, lists its files, and shows its panels as text. You choose which requests to grant; a call that needs a request you did not grant fails with capability.denied.

An approval is for one version: the hash of the folder’s contents. When any file changes, by an edit or an update, the plugin waits for approval again. A grant the manifest did not ask for is refused (approve.capability).

Only a person approves. The daemon refuses an approval, a switch to dev mode, a raised limit or a rollback that comes from inside a coding agent’s process (approve.from_agent), and it fails closed when it cannot tell. It stops an agent approving a plugin on its own; it is not a wall against a program on your machine that sets out to get around it.

Plugins that ship with Graphide and only read need no approval: the release vouches for them. One that asks for more waits like any other.

Every request

The middle column is what the review says.

In [allow] The review says What it allows
fs = ["read:workspace"] reads the files in your workspace Reading, listing and finding files inside the project, and the text of the current line and selection. Never Graphide’s own folders, ~/.ssh and other secret folders.
fs = ["write:data"] keeps files in its own data folder Writing files in a folder of the plugin’s own, and nowhere else.
exec = ["rg"] runs rg in the sandbox, with your workspace read-only and no network Running that program with any arguments. Its path is recorded when you approve.
net = ["api.github.com"] sends to and reads from api.github.com HTTPS requests to that host. *.example.com allows one level of subdomain.
net = ["localhost:8080"] talks to a program on this machine at port 8080 HTTP or HTTPS to that port on this machine.
graph = "read" reads your workspace’s graph Reading the map: nodes, their children and neighbours, and search.
graph = ["read", "write"] changes this workspace’s graph: adds, renames, describes, tags, moves and removes nodes; writes are attributed to the plugin and cannot be undone Changing the map of the project it runs in.
agents = "read" sees your agents, what they are doing and what they say Listing your agents, and reading what each agent and its person said, without tool results or command output.
agents = ["read", "write"] writes to your agents: messages and text they will read and may act on Sending an agent a message, or putting text in its composer.
agents = ["read", "start"] starts agents, which costs money: at most 3 every 10 minutes unless you raise it Starting agents.
agents = ["tools"] offers tools your agents may call; what a tool answers is read by the agent Offering [[tools]] to your coding agents through Graphide’s MCP server.
clipboard = "write" puts text on your clipboard Writing to the clipboard. Never reading it.
terminal = true opens terminals that run commands as you, outside the sandbox; each one is shown Opening terminals of its own.
docs = "write" changes the text of files you have open, each change one undo step Editing documents open in the app.
setup = "read" reads how your coding agents are set up on this machine: the names and paths of their instructions, skills, hooks, MCP servers and settings, never a key or a token Reading your agents’ setup.
net = ["any"] its server may reach any host The network, for the plugin’s own MCP servers that ask for it. The script’s own requests still reach only the hosts named.

Requests that arrived after the first interface version need api = "0.2" or later in the manifest. Capabilities in the reference has the methods each one opens.

The sandbox

A program a plugin runs (exec) and a plugin’s MCP server run in a sandbox:

  • The workspace is read-only, and so is its git folder.
  • There is no network, unless an MCP server asked for net = ["any"] and you granted it.
  • The program starts with only PATH, HOME, LANG, TERM, TMPDIR and the XDG folder names in its environment. Nothing else of yours leaks in.
  • It stops after 30 seconds or at 1 GiB of memory, and its output is capped at 1 MiB each way.

On Linux the sandbox is bubblewrap (bwrap). Where there is no sandbox, a plugin’s programs do not run at all (exec.unsandboxed); they never fall back to running unconfined. macOS’s sandbox cannot yet keep a workspace read-only, so on a Mac a plugin’s programs and MCP servers are refused this way for now. The rest of a plugin runs. exec is also refused when the project is your home folder (exec.root).

The script’s own requests (net) go only to the hosts it named, over HTTPS. Plain HTTP, Graphide’s own servers, private and cloud metadata addresses are refused, and each redirect is checked again.

What runs as you

Three things a plugin can add run as you, outside the sandbox. The review says so for each, and only approve them for a plugin you trust as you would any program you install:

  • Terminals (terminal = true). Each one is shown in the app.
  • New kinds of agent ([[agents]]). An agent runs in the workspace with the reach Graphide’s own agents have.
  • A language’s server ([languages.server]), and only in projects you trust.

Settings and secrets

A plugin’s settings are yours, in your config.toml. A secret setting, such as an API token, is different: you enter it with gr plugin secret set, it is kept in your system’s keychain, and the plugin never reads it. Graphide adds it to requests going to the one host the setting names, or hands it to the plugin’s own MCP server. The plugin learns only whether it is set.

Limits that stop a plugin

A plugin that runs too long, uses too much memory or keeps failing is switched off for the session, and no other plugin notices. Its own time and memory limits can be raised up to a cap by its manifest; others are fixed, or yours to set with gr plugin config <name> --limit. See Limits and When a plugin misbehaves.

Errors you may see

Code Means
capability.undeclared The script called a method whose request is not in [allow].
capability.denied The request is there, and the person did not grant it.
capability.scope A call outside what was granted, such as a host not named.
fs.refused A path outside the project, or in a folder no plugin may read.
net.refused Plain HTTP, Graphide’s own hosts, or a private address.
exec.unsandboxed No sandbox on this machine, so the program did not run.
exec.killed The program reached 30 seconds or 1 GiB.
approve.stale The files changed after the review you approved. Review again.
approve.from_agent The approval came from inside an agent.

Error codes lists every one.

These docs describe Graphide 0.2.16, the latest release. Something wrong or missing? Tell us onDiscord or at hello@graphide.com.