Buff Development
Connector

Isolation and access boundaries

What an integration can do, what it cannot, and what ensures it: the OS user, directories, addresses, credentials.

This page is written for those who decide whether to let someone else’s code into their network. It describes mechanisms, not intentions: what exactly limits an integration and where the boundary lies beyond which a limit becomes your responsibility.

A process per connection, a user per connection

Each connection (not the integration, but precisely the link to a specific system) runs as its own process under its own OS user. The connector assigns the identifiers and remembers them so that nothing gets mixed up after a restart.

A user per connection rather than per integration, because two connections of one integration hold different credentials: production and a sandbox must not reach each other.

If the connector cannot switch users (it is not running as root — root specifically is checked — or the container was started with --user), integrations do not start at all. This is deliberate: working “as it happens, without isolation” is a different security promise, and we do not make it. The git bridge works fully in this mode.

What is visible on disk

The state directory is laid out so that OS permissions repeat the trust boundary:

DirectoryPermissionsWho and what
<state_dir>/0755traversable
<state_dir>/identity0700the agent’s key — the core only
<state_dir>/secrets0700tokens of git providers and connections — the core only
<state_dir>/plugins0755integration code and the interpreter — readable
<state_dir>/runtime0755the runtime — readable
<state_dir>/instances0711traversable but not listable: a connection cannot even learn its neighbours’ names
<state_dir>/instances/<GUID>0700, owned by the connection’s userthe working directory: the process’s HOME and TMPDIR

Secrets sit in a directory the connection’s user has no access to. The core reads them.

You can check this on your machine with one command — this is what a working connector with one connection looks like:

$ ls -ld /var/lib/connector /var/lib/connector/*
drwxr-xr-x root   /var/lib/connector
drwxr-xr-x root   /var/lib/connector/catalog
drwx------ root   /var/lib/connector/identity     ← the agent's key
drwx--x--x root   /var/lib/connector/instances    ← traversable, not listable
drwxr-xr-x root   /var/lib/connector/plugins
drwxr-xr-x root   /var/lib/connector/runtime
drwx------ root   /var/lib/connector/secrets      ← tokens
-rw------- root   /var/lib/connector/state.json

$ ls -ld /var/lib/connector/instances/*
drwx------ 61000  /var/lib/connector/instances/FHTZDP8A706GTS67   ← its own, owned by its user
-rw------- root   /var/lib/connector/instances/uids.json

Connection users are assigned by the connector from a separate range and remembered so that nothing gets mixed up after a restart.

Credentials: the plugin does not see them

An integration gets no tokens — neither in its settings nor in environment variables. It asks the connector to make a request, and the connector:

  1. checks the address, method and path against the declared rule;
  2. strips the authorisation header if the integration tried to set its own;
  3. inserts the credentials itself — as a token, an “account + secret” pair or the tracker’s own header, as declared in the manifest; and where the service’s tokens live for an hour (Google), it issues them itself — from the service account key via a signed JWT or from your own account’s refresh token — and refreshes them before they expire. Even then the plugin sees only the answer it got;
  4. records the call in the connection’s log: which credentials were used (by name, not by value), the method, the address, the response code and the duration.

The process’s environment variables contain only the connection’s identifier, its slug and the settings you entered in the form. Neither the agent’s key nor tokens are there.

Where it may go

The permission is derived from your own field in the connection form: “to the address from base_url”, not “to github.com”. So the catalogue shows the rule, and the specific address appears once you have entered it. The rule limits the host, methods and path prefixes.

In addition, the connector refuses to go to link-local addresses — that is where cloud metadata and credential services live. Private ranges, on the contrary, are allowed deliberately: the point of the connector is to reach systems inside your network, and banning private networks would ban the product itself.

The connector limits the rate of tool calls from runs on its side: no more than 120 calls per minute per connection.

The exception is the web rule of the “Web” integration: there the model names the address, not you, so the rule works the other way round. Instead of a host — your allow and block lists of sites and content types, and private ranges are always forbidden: the name is resolved before every request, a single non-public address means refusal, and the connection goes to exactly the checked address. Credentials are never attached to such a request.

The honesty boundary: isolation is at the level of the OS user and credentials, not of a network namespace. An integration’s process is an ordinary process on your machine and can technically open a socket itself; it has none of your secrets, so such a request authenticates nowhere. If you need a ban at the network level, put it in place with your own means (per-user firewall rules, a separate namespace, a dedicated machine for the connector) — this is exactly the case where we will not claim the question is closed.

What reaches us

The connector sends a status report — the same as what you see in the app:

  • the agent’s version, online/offline, the list of repositories it serves;
  • for each connection: the slug and name, the integration and its version, the status, the last error, the development project, the list of tools and the allowed addresses;
  • the facts of tool calls: which tool, how long it took, whether there was an error.

The report never contains secrets — this is a property of its format, not of the carefulness of the calling code.

Tracker events are sent separately, but only those addressed to the bot directly and only in the selected projects: what exactly.

At the model’s request, the content it read with the connection’s tools for a particular answer also leaves: pages, files, tickets, a pull request diff, web pages found — exactly what the tool returned and nothing more; every call is recorded in the connection’s log.

Everything that comes to us is then processed by the model according to the organisation’s plan: on OpenRouter plans — a third-party provider; on dedicated capacity and in a custom setup — a dedicated machine, without third-party providers. See Plans.

Deleting a connection

Deletion removes the connection from the config and stops its process. The secret files and the working directory stay on disk — the operator deletes them, deliberately; the connector does not clean up directories beyond what it created itself at that moment. If the machine is being decommissioned, remove the whole state_dir: it holds the agent’s key and all tokens.

On this page