A terminal agent launched inside an SSH session does not work on the workstation of whoever launches it, but on the remote host: it reads the server’s files, runs commands on the server, inherits that environment. Put like this it sounds like a detail, but it changes the plane of governance. The boundary of what the agent can touch is no longer decided by the operator’s machine: it is decided by the configuration that lives on the server.
opencode — the terminal agent published by Anomaly (the former SST team) under the MIT licence — is a good case to reason about, because it puts that boundary down in black and white as declarative data instead of leaving it to an implicit convention. The question I care about is not how you use it, but what the policy file actually guarantees and what it leaves out.
Context: the agent inherits the host, not the session
A code assistant inside the IDE and a terminal agent differ on one point: the latter is a standalone CLI binary, working in the current directory. Launched after an ssh operator@srv.example, the agent sees what that shell sees: the same filesystem, the same user, the same privileges. If the SSH session is logged in as a user with sudo, the agent has that reach too.
Hence the appeal for remote operational triage — log synthesis, package audits, proposed diffs on configuration files. And hence, too, the shift in the security problem: no longer what I type in the prompt, but what the server grants that process. A badly worded prompt is a mistake you recover from; a badly configured execution boundary is an attack surface.
The architecture of the boundary: the opencode.json file
opencode keeps permissions in an opencode.json file, with three levels per tool: allow (run without approval), ask (interactive confirmation), deny (action blocked). The project documentation makes clear that rules are evaluated by pattern match and that the last matching rule wins — no small detail when you write non-trivial policy.
Granularity reaches the individual shell command and the individual path:
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"*": "ask",
"git *": "allow",
"rm *": "deny"
},
"edit": {
"*": "deny",
"/home/operator/ops-session/*": "allow"
}
}
}
Read as policy: every shell command asks for confirmation except the git prefixes, every rm is denied up front, every file write is denied outside the session directory. Against manual interactive approval one thing changes: the deny never passes through the operator. The process itself refuses, regardless of what the operator might have waved through absent-mindedly at three in the morning, in the middle of an incident.
On the governance side, what counts most is this: a boundary written as versionable data is an auditable and reproducible boundary, distributed across managed servers with the same tooling as any other configuration.
The critical point: the local policy does not cover data leaving the host
The permissions file governs what the agent does on the host. It does not govern what the agent sends off the host. These are two different planes, and conflating them is the most common reading error.
An agent with a cloud endpoint sends the model provider pieces of context: commands, output, fragments of the files it has read. On a server holding regulated data — health, civil-registry, tax — this is a transfer to a third party that no deny rule on bash intercepts, because it is not a command: it is the ordinary working of inference. The structural mitigation is an endpoint that never leaves the perimeter; a tighter permission is not enough. opencode talks to any OpenAI-API-compatible runtime, so a local model via Ollama or vLLM on the same host (or on an internal node) keeps the context inside the infrastructure.
There is then a second, subtler exit channel: session sharing. opencode has a share feature with three configurable modes — manual (the default, sharing only on the explicit /share command), auto (every new session shared automatically) and disabled. The project documentation is explicit: a shared session uploads the conversation history to the project’s servers, and from that moment it is reachable by anyone with the link. On a host managing client data, "share": "disabled" in the config file is the default that holds up to scrutiny. Staying on the manual default means relying on operator discipline, so that no /share slips out on a session containing log output with real identifiers.
Implications: the audit trail and its residue
An agent session leaves a readable transcript, and for anyone running systems on behalf of others that is a concrete benefit: the transcript records what was proposed, what was approved, what was executed. It is closer to an audit trail than ten commands pasted by hand into a chat.
The flip side is that this trace is sensitive material. opencode writes session data, logs and tool output under ~/.local/share/opencode (changeable with OPENCODE_DATA_DIR); in the same path the auth.json file can hold API keys and tokens. On a shared host, or one bound for decommissioning, this residue deserves the same care as shell logs: encryption at rest where warranted, removal at the end of a session if it holds client details. A transcript that documents an intervention well documents the environment it happened in just as well.
Limits
The declarative boundary described above stays local to the single host and the single installation: nothing stops an operator with filesystem access from editing opencode.json, and nothing ties that file to a central, server-verified policy. It is a tool-level control, not a control enforced from the perimeter by design. For regulated environments it remains one layer among others, not the layer.
The agentic ecosystem, moreover, moves fast and defaults change between one release and the next. What I write here holds against the project documentation consulted in early March 2026; before a deploy the correct posture is to read the release notes of the version you install, without taking for granted that a privacy-favourable default stays that way.
https://opencode.ai/docs/permissions/ https://opencode.ai/docs/share/ https://opencode.ai/docs/config/ https://github.com/anomalyco/opencode https://www.noze.it/en/insights/opencode-remote-ops-ssh/
Cover image: An OpenBSD terminal screen showing command-line text as the SSH server (sshd) starts up — photo by Hpott, CC BY-SA 4.0 — https://commons.wikimedia.org/wiki/File:OpenBSD_starting_SSH_server.jpg