Un agente da terminale avviato dentro una sessione SSH non lavora sulla workstation di chi lo lancia, ma sull’host remoto: legge i file del server, esegue comandi sul server, eredita quell’ambiente. Detto così sembra un dettaglio, ma cambia il piano della governance. Il confine di ciò che l’agente può toccare non lo decide più la macchina dell’operatore: lo decide la configurazione che vive sul server.
opencode — l’agente da terminale pubblicato da Anomaly (l’ex team SST) con licenza MIT — è un buon caso su cui ragionare, perché mette quel confine nero su bianco come dato dichiarativo invece di lasciarlo a una convenzione implicita. La domanda che mi interessa non è come si usa, ma cosa il file di policy garantisce davvero e cosa lascia fuori.
Contesto: l’agente eredita l’host, non la sessione
Un assistente di codice dentro l’IDE e un agente da terminale differiscono per un punto: il secondo è un binario CLI a sé, che lavora nella directory corrente. Lanciato dopo uno ssh operatore@srv.example, l’agente vede quello che vede quella shell: stesso filesystem, stesso utente, stessi privilegi. Se la sessione SSH è loggata come utente con sudo, quel raggio d’azione ce l’ha anche l’agente.
Da qui l’interesse per il triage operativo da remoto — sintesi dei log, audit dei pacchetti, diff proposti sui file di configurazione. E da qui anche lo spostamento del problema di sicurezza: non più cosa scrivo nel prompt, ma cosa il server concede a quel processo. Un prompt scritto male è un errore che si recupera; un confine di esecuzione configurato male è una superficie d’attacco.
L’architettura del confine: il file opencode.json
opencode tiene i permessi in un file opencode.json, con tre livelli per ogni strumento: allow (esecuzione senza approvazione), ask (conferma interattiva), deny (azione bloccata). La documentazione del progetto chiarisce che le regole si valutano per pattern match e che vince l’ultima regola che corrisponde — non un dettaglio da poco quando si scrivono policy non banali.
La granularità arriva al singolo comando di shell e al singolo path:
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"*": "ask",
"git *": "allow",
"rm *": "deny"
},
"edit": {
"*": "deny",
"/home/operatore/ops-session/*": "allow"
}
}
}
Letta come policy: ogni comando di shell chiede conferma tranne i prefissi git, ogni rm è negato a monte, ogni scrittura di file è negata fuori dalla directory di sessione. Rispetto all’approvazione interattiva a mano cambia una cosa: il deny non passa mai dall’operatore. Si rifiuta il processo, a prescindere da cosa l’operatore avrebbe approvato per distrazione alle tre di notte, in mezzo a un incidente.
Sul piano della governance conta soprattutto questo: un confine scritto come dato versionabile è un confine auditabile e riproducibile, che si distribuisce sui server gestiti con gli stessi strumenti di qualsiasi altra configurazione.
Il punto critico: la policy locale non copre i dati che escono
Il file dei permessi governa cosa l’agente fa sull’host. Non governa cosa l’agente manda fuori dall’host. Sono due piani diversi, e confonderli è l’errore di lettura più comune.
Un agente con endpoint cloud manda al provider del modello pezzi di contesto: comandi, output, frammenti dei file letti. Su un server con dati regolati — sanitari, anagrafici, fiscali — questo è un trasferimento verso terzi che nessuna regola deny su bash intercetta, perché non è un comando: è il funzionamento ordinario dell’inferenza. La mitigazione strutturale è un endpoint che non esce dal perimetro; un permesso più stretto non basta. opencode parla con qualsiasi runtime compatibile con le API OpenAI, quindi un modello locale via Ollama o vLLM sullo stesso host (o su un nodo interno) tiene il contesto dentro l’infrastruttura.
C’è poi un secondo canale d’uscita, più sottile: la condivisione di sessione. opencode ha una funzione di share con tre modalità configurabili — manual (il default, condivisione solo sul comando /share esplicito), auto (ogni nuova sessione condivisa in automatico) e disabled. La documentazione del progetto è esplicita: una sessione condivisa carica la cronologia della conversazione sui server del progetto, e da quel momento è raggiungibile da chiunque abbia il link. Su un host che gestisce dati di clienti, "share": "disabled" nel file di configurazione è il default che regge a un controllo. Restare sul default manual significa affidarsi alla disciplina dell’operatore, perché non gli scappi un /share su una sessione che contiene output di log con identificatori reali.
Implicazioni: l’audit trail e i suoi residui
Una sessione di agente lascia un transcript leggibile, e per chi gestisce sistemi per conto terzi è un vantaggio concreto: il transcript registra cosa è stato proposto, cosa approvato, cosa eseguito. È più vicino a un audit trail di dieci comandi incollati a mano in una chat.
Il rovescio è che quella traccia è materiale sensibile. opencode scrive dati di sessione, log e output degli strumenti sotto ~/.local/share/opencode (modificabile con OPENCODE_DATA_DIR); nello stesso percorso il file auth.json può contenere chiavi API e token. Su un host condiviso o destinato alla dismissione, questi residui vanno trattati con la stessa cura dei log di shell: cifratura a riposo dove serve, rimozione a fine sessione se contengono dettagli dei clienti. Un transcript che documenta bene un intervento documenta altrettanto bene l’ambiente in cui è avvenuto.
Limiti
Il confine dichiarativo descritto qui sopra resta locale al singolo host e alla singola installazione: niente impedisce a un operatore con accesso al filesystem di modificare opencode.json, e niente lega quel file a una policy centrale verificata lato server. È un controllo a livello di tool, non un controllo imposto dal perimetro per costruzione. Per ambienti regolati resta un layer fra gli altri, non il layer.
L’ecosistema agentic, poi, si muove in fretta e i default cambiano tra una release e l’altra. Quanto scrivo qui vale sulla documentazione del progetto consultata a inizio marzo 2026; prima di un deploy la posizione corretta è leggere le release notes della versione che si installa, senza dare per scontato che un default favorevole alla riservatezza resti tale.
https://opencode.ai/docs/permissions/ https://opencode.ai/docs/share/ https://opencode.ai/docs/config/ https://github.com/anomalyco/opencode https://www.noze.it/insights/opencode-remote-ops-ssh/
Immagine di copertina: Schermo di un terminale OpenBSD con testo a riga di comando che mostra l’avvio del server SSH (sshd) — foto di Hpott, CC BY-SA 4.0 — https://commons.wikimedia.org/wiki/File:OpenBSD_starting_SSH_server.jpg