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