OCSB_DISPLAY: agent windows on the desktop through the Wayland socket

Off by default. With OCSB_DISPLAY=1 the sandbox gets only the Wayland socket and /dev/dri: the agent's windows (e.g. a Playwright browser) appear on the desktop, but a Wayland client cannot read other windows or inject input into them. X11 and the X cookie are never passed in.
This commit is contained in:
2026-10-30 01:14:56 -07:00
parent 6dec88641a
commit 5a7567849e
5 changed files with 36 additions and 12 deletions
+9 -4
View File
@@ -124,6 +124,7 @@ files are read, so you can use it in values. Relative paths are relative to the
| `OCSB_PLUGINS` | skill-compact-resurrect | plugin files from `plugins/` (or absolute paths) | | `OCSB_PLUGINS` | skill-compact-resurrect | plugin files from `plugins/` (or absolute paths) |
| `OCSB_PANEL` | `1` | "Tokens" panel in the web UI | | `OCSB_PANEL` | `1` | "Tokens" panel in the web UI |
| `OCSB_NOTIFICATIONS` | `1` | desktop notifications through a filtered D-Bus proxy | | `OCSB_NOTIFICATIONS` | `1` | desktop notifications through a filtered D-Bus proxy |
| `OCSB_DISPLAY` | `0` | `1`: the agent's windows (e.g. a Playwright browser) on your desktop through the Wayland socket |
| `OCSB_TMP` | `private` | `private`: the agent's own `/tmp` in the state directory; `shared`: the host's `/tmp` | | `OCSB_TMP` | `private` | `private`: the agent's own `/tmp` in the state directory; `shared`: the host's `/tmp` |
| `OCSB_SANDBOX` | `1` | `0` = filter and netlock only, no bubblewrap | | `OCSB_SANDBOX` | `1` | `0` = filter and netlock only, no bubblewrap |
| `OCSB_WORKTREES_DIR` | `../<project>-worktrees` | where worktrees go | | `OCSB_WORKTREES_DIR` | `../<project>-worktrees` | where worktrees go |
@@ -243,9 +244,11 @@ Inside the sandbox:
- **Session bus:** replaced by `xdg-dbus-proxy` that talks only to - **Session bus:** replaced by `xdg-dbus-proxy` that talks only to
`org.freedesktop.Notifications`; the real bus would let the agent start processes outside `org.freedesktop.Notifications`; the real bus would let the agent start processes outside
the sandbox with `systemd-run --user`. the sandbox with `systemd-run --user`.
- **No display:** `DISPLAY`, `WAYLAND_DISPLAY` and `XAUTHORITY` are empty and the X cookie is - **Display:** by default none — `DISPLAY`, `WAYLAND_DISPLAY` and `XAUTHORITY` are empty and the
hidden, so GUI programs cannot reach your desktop; run browsers (Playwright MCP, Chrome) X cookie is hidden; run browsers headless. With `OCSB_DISPLAY=1` the agent gets only the
headless. `xdg-open` is disabled; the UI is opened by `run` from your normal session. Wayland socket (and `/dev/dri` for the GPU): its windows appear on your desktop, but unlike
X11 a Wayland client cannot read other windows or inject input into them. X11 is never
passed in. `xdg-open` is disabled; the UI is opened by `run` from your normal session.
- **No silent fallback:** with `OCSB_SANDBOX=1` and no working bubblewrap, `run` refuses to - **No silent fallback:** with `OCSB_SANDBOX=1` and no working bubblewrap, `run` refuses to
start. start.
@@ -267,6 +270,8 @@ place, requests to production — not to hold a determined attacker.
- **The project is writable.** Mistakes inside it are real; cc-safety-net blocks the common - **The project is writable.** Mistakes inside it are real; cc-safety-net blocks the common
destructive commands, git keeps history — commit often. destructive commands, git keeps history — commit often.
- With `OCSB_TMP=shared` the host's `/tmp` and its sockets are reachable. - With `OCSB_TMP=shared` the host's `/tmp` and its sockets are reachable.
- With `OCSB_DISPLAY=1` the agent can open windows on your desktop and use the clipboard while
its window is focused.
## Troubleshooting ## Troubleshooting
@@ -281,7 +286,7 @@ place, requests to production — not to hold a determined attacker.
| `bubblewrap cannot create a sandbox` | unprivileged user namespaces are disabled (`kernel.unprivileged_userns_clone`, AppArmor userns restrictions) | | `bubblewrap cannot create a sandbox` | unprivileged user namespaces are disabled (`kernel.unprivileged_userns_clone`, AppArmor userns restrictions) |
| no "Tokens" panel | open port 4196, not 4197; see `<state>/data/opencode/log/context-panel.log` | | no "Tokens" panel | open port 4196, not 4197; see `<state>/data/opencode/log/context-panel.log` |
| no notifications | install `xdg-dbus-proxy` | | no notifications | install `xdg-dbus-proxy` |
| browser tools fail (`Missing X server`, `Failed to connect to Wayland display`) | no display in the sandbox: run the browser headless (Playwright MCP: `--headless`) | | browser tools fail (`Missing X server`, `Failed to connect to Wayland display`) | no display in the sandbox: set `OCSB_DISPLAY=1` (Wayland session) or run the browser headless (Playwright MCP: `--headless`) |
| can't find a file the agent put in `/tmp` | it is in `~/.local/share/opencode-sandbox/<name>/tmp` | | can't find a file the agent put in `/tmp` | it is in `~/.local/share/opencode-sandbox/<name>/tmp` |
| `docker build` fails pulling images | the proxy reaches registries through squid: check `egress logs` | | `docker build` fails pulling images | the proxy reaches registries through squid: check `egress logs` |
+8 -4
View File
@@ -123,6 +123,7 @@ squid.conf.
| `OCSB_PLUGINS` | skill-compact-resurrect | файлы плагинов из `plugins/` (или абсолютные пути) | | `OCSB_PLUGINS` | skill-compact-resurrect | файлы плагинов из `plugins/` (или абсолютные пути) |
| `OCSB_PANEL` | `1` | панель «Токены» в веб-интерфейсе | | `OCSB_PANEL` | `1` | панель «Токены» в веб-интерфейсе |
| `OCSB_NOTIFICATIONS` | `1` | уведомления через фильтрующий прокси D-Bus | | `OCSB_NOTIFICATIONS` | `1` | уведомления через фильтрующий прокси D-Bus |
| `OCSB_DISPLAY` | `0` | `1` — окна агента (например, браузер Playwright) на вашем экране через сокет Wayland |
| `OCSB_TMP` | `private` | `private` — свой `/tmp` агента в каталоге состояния; `shared` — `/tmp` хоста | | `OCSB_TMP` | `private` | `private` — свой `/tmp` агента в каталоге состояния; `shared` — `/tmp` хоста |
| `OCSB_SANDBOX` | `1` | `0` — только фильтр и netlock, без bubblewrap | | `OCSB_SANDBOX` | `1` | `0` — только фильтр и netlock, без bubblewrap |
| `OCSB_WORKTREES_DIR` | `../<проект>-worktrees` | куда класть worktree | | `OCSB_WORKTREES_DIR` | `../<проект>-worktrees` | куда класть worktree |
@@ -244,9 +245,10 @@ opencode-sandbox run
- **Сессионная шина:** заменена на `xdg-dbus-proxy`, который пропускает только - **Сессионная шина:** заменена на `xdg-dbus-proxy`, который пропускает только
`org.freedesktop.Notifications`; настоящая шина позволила бы агенту запускать процессы вне `org.freedesktop.Notifications`; настоящая шина позволила бы агенту запускать процессы вне
песочницы через `systemd-run --user`. песочницы через `systemd-run --user`.
- **Нет экрана:** `DISPLAY`, `WAYLAND_DISPLAY` и `XAUTHORITY` пустые, cookie X скрыт — графические - **Экран:** по умолчанию нет — `DISPLAY`, `WAYLAND_DISPLAY` и `XAUTHORITY` пустые, cookie X
программы до вашего рабочего стола не доберутся; браузеры (Playwright MCP, Chrome) — только скрыт; браузеры — headless. С `OCSB_DISPLAY=1` агент получает только сокет Wayland (и
headless. `xdg-open` отключён; интерфейс открывает `run` из вашей обычной сессии. `/dev/dri` для видеокарты): его окна появляются на вашем экране, но, в отличие от X11, клиент
Wayland не может читать чужие окна и подделывать в них ввод. X11 не пробрасывается никогда. `xdg-open` отключён; интерфейс открывает `run` из вашей обычной сессии.
- **Без тихого отката:** при `OCSB_SANDBOX=1` и неработающем bubblewrap `run` не запустится. - **Без тихого отката:** при `OCSB_SANDBOX=1` и неработающем bubblewrap `run` не запустится.
**Netlock** (`netlock setup`, sudo) создаёт группу `opencode-sandbox`, добавляет в неё вас и **Netlock** (`netlock setup`, sudo) создаёт группу `opencode-sandbox`, добавляет в неё вас и
@@ -268,6 +270,8 @@ opencode-sandbox run
- **Проект доступен на запись.** Ошибки внутри него настоящие; cc-safety-net блокирует - **Проект доступен на запись.** Ошибки внутри него настоящие; cc-safety-net блокирует
типичные разрушительные команды, git хранит историю — коммитьте чаще. типичные разрушительные команды, git хранит историю — коммитьте чаще.
- С `OCSB_TMP=shared` доступны `/tmp` хоста и его сокеты. - С `OCSB_TMP=shared` доступны `/tmp` хоста и его сокеты.
- С `OCSB_DISPLAY=1` агент может открывать окна на вашем экране и пользоваться буфером обмена,
пока его окно в фокусе.
## Решение проблем ## Решение проблем
@@ -282,7 +286,7 @@ opencode-sandbox run
| `bubblewrap cannot create a sandbox` | отключены непривилегированные user namespaces (`kernel.unprivileged_userns_clone`, ограничения AppArmor) | | `bubblewrap cannot create a sandbox` | отключены непривилегированные user namespaces (`kernel.unprivileged_userns_clone`, ограничения AppArmor) |
| нет панели «Токены» | открывайте порт 4196, а не 4197; журнал — `<состояние>/data/opencode/log/context-panel.log` | | нет панели «Токены» | открывайте порт 4196, а не 4197; журнал — `<состояние>/data/opencode/log/context-panel.log` |
| нет уведомлений | установите `xdg-dbus-proxy` | | нет уведомлений | установите `xdg-dbus-proxy` |
| браузерные инструменты падают (`Missing X server`, `Failed to connect to Wayland display`) | в песочнице нет экрана: запускайте браузер headless (Playwright MCP: `--headless`) | | браузерные инструменты падают (`Missing X server`, `Failed to connect to Wayland display`) | в песочнице нет экрана: включите `OCSB_DISPLAY=1` (сессия Wayland) или запускайте браузер headless (Playwright MCP: `--headless`) |
| не найти файл, который агент положил в `/tmp` | он в `~/.local/share/opencode-sandbox/<имя>/tmp` | | не найти файл, который агент положил в `/tmp` | он в `~/.local/share/opencode-sandbox/<имя>/tmp` |
| `docker build` не скачивает образы | реестры доступны через squid: смотрите `egress logs` | | `docker build` не скачивает образы | реестры доступны через squid: смотрите `egress logs` |
+2 -1
View File
@@ -19,7 +19,7 @@ ocsb_info() { echo "→ $*"; }
OCSB_SCALARS=( OCSB_SCALARS=(
OCSB_NAME OCSB_WEB_PORT OCSB_EGRESS_PORT OCSB_NETLOCK_GROUP OCSB_SANDBOX OCSB_PANEL OCSB_NAME OCSB_WEB_PORT OCSB_EGRESS_PORT OCSB_NETLOCK_GROUP OCSB_SANDBOX OCSB_PANEL
OCSB_NOTIFICATIONS OCSB_TMP OCSB_OPENCODE_BIN OCSB_ENV_FILE OCSB_KUBECONFIG OCSB_PASSWORD_FILE OCSB_NOTIFICATIONS OCSB_TMP OCSB_DISPLAY OCSB_OPENCODE_BIN OCSB_ENV_FILE OCSB_KUBECONFIG OCSB_PASSWORD_FILE
OCSB_WORKTREES_DIR OCSB_WT_PREFIX OCSB_WT_BASE OCSB_WT_SLUG_REGEX OCSB_DOCKER_GW OCSB_WORKTREES_DIR OCSB_WT_PREFIX OCSB_WT_BASE OCSB_WT_SLUG_REGEX OCSB_DOCKER_GW
OCSB_STATE_DIR OCSB_OPENCODE_CONFIG OCSB_EGRESS_BASE OCSB_STATE_DIR OCSB_OPENCODE_CONFIG OCSB_EGRESS_BASE
) )
@@ -34,6 +34,7 @@ ocsb_defaults() {
OCSB_PANEL=1 # "Tokens" panel in the web UI (plugins/context-panel) OCSB_PANEL=1 # "Tokens" panel in the web UI (plugins/context-panel)
OCSB_NOTIFICATIONS=1 # desktop notifications through a filtered D-Bus proxy OCSB_NOTIFICATIONS=1 # desktop notifications through a filtered D-Bus proxy
OCSB_TMP=private # private: own /tmp in the state dir; shared: the host's /tmp OCSB_TMP=private # private: own /tmp in the state dir; shared: the host's /tmp
OCSB_DISPLAY=0 # 1: windows on your desktop through the Wayland socket (no X11)
OCSB_OPENCODE_BIN="" # default: `opencode` from PATH OCSB_OPENCODE_BIN="" # default: `opencode` from PATH
OCSB_ENV_FILE="" # KEY=VALUE file with tokens, exported to OpenCode OCSB_ENV_FILE="" # KEY=VALUE file with tokens, exported to OpenCode
OCSB_KUBECONFIG="" # kubeconfig for the agent (~/.kube/config stays hidden) OCSB_KUBECONFIG="" # kubeconfig for the agent (~/.kube/config stays hidden)
+15 -2
View File
@@ -177,6 +177,11 @@ ocsb_bwrap_args() {
if [[ -n ${OCSB_DBUS_PROXY_PID:-} && -S $(ocsb_dbus_sock) ]]; then if [[ -n ${OCSB_DBUS_PROXY_PID:-} && -S $(ocsb_dbus_sock) ]]; then
OCSB_BWRAP+=(--ro-bind "$(ocsb_dbus_sock)" "/run/user/${uid}/bus") OCSB_BWRAP+=(--ro-bind "$(ocsb_dbus_sock)" "/run/user/${uid}/bus")
fi fi
if [[ ${OCSB_DISPLAY} == 1 && -n ${WAYLAND_DISPLAY:-} && -S ${XDG_RUNTIME_DIR:-/run/user/${uid}}/${WAYLAND_DISPLAY} ]]; then
OCSB_BWRAP+=(--ro-bind "${XDG_RUNTIME_DIR:-/run/user/${uid}}/${WAYLAND_DISPLAY}" "/run/user/${uid}/${WAYLAND_DISPLAY}")
# GPU for rendering; without it browsers fall back to software
[[ -d /dev/dri ]] && OCSB_BWRAP+=(--dev-bind /dev/dri /dev/dri)
fi
OCSB_BWRAP+=(--chdir "${OCSB_PROJECT_DIR}") OCSB_BWRAP+=(--chdir "${OCSB_PROJECT_DIR}")
} }
@@ -255,8 +260,16 @@ ocsb_run() {
# No permission prompts in the sandbox: permission rules from all configs form one # No permission prompts in the sandbox: permission rules from all configs form one
# list, the last match wins, and OPENCODE_CONFIG_CONTENT is the last document. # list, the last match wins, and OPENCODE_CONFIG_CONTENT is the last document.
# Denies are repeated after "*" so they still win. # Denies are repeated after "*" so they still win.
# No display: GUI programs fail fast; browsers must run headless # Display: by default none (GUI programs fail fast, browsers run headless). With
env+=("DISPLAY=" "WAYLAND_DISPLAY=" "XAUTHORITY=") # OCSB_DISPLAY=1 only the Wayland socket: unlike X11, a Wayland client cannot read other
# windows or inject input into them. X11 (and the X cookie) stays out either way.
env+=("DISPLAY=" "XAUTHORITY=")
if [[ ${OCSB_DISPLAY} == 1 && -n ${WAYLAND_DISPLAY:-} && -S ${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/${WAYLAND_DISPLAY} ]]; then
env+=("WAYLAND_DISPLAY=${WAYLAND_DISPLAY}" "XDG_RUNTIME_DIR=/run/user/$(id -u)")
else
[[ ${OCSB_DISPLAY} == 1 ]] && ocsb_warn "OCSB_DISPLAY=1 but no Wayland session — no windows in the sandbox"
env+=("WAYLAND_DISPLAY=")
fi
env+=('OPENCODE_CONFIG_CONTENT={"permission":{"bash":{"*":"allow","systemd-run *":"deny","sudo *":"deny"},"external_directory":{"*":"allow"}}}') env+=('OPENCODE_CONFIG_CONTENT={"permission":{"bash":{"*":"allow","systemd-run *":"deny","sudo *":"deny"},"external_directory":{"*":"allow"}}}')
echo " sandbox: writable ${OCSB_PROJECT_DIR}, ${OCSB_WORKTREES_DIR}${OCSB_RW[*]:+, ${OCSB_RW[*]}}; the rest of the system is read-only" echo " sandbox: writable ${OCSB_PROJECT_DIR}, ${OCSB_WORKTREES_DIR}${OCSB_RW[*]:+, ${OCSB_RW[*]}}; the rest of the system is read-only"
fi fi
+2 -1
View File
@@ -39,7 +39,8 @@ sandbox is on.
## Things you cannot do ## Things you cannot do
- Open a browser (`xdg-open` is disabled): print the link for the user. - Open a browser (`xdg-open` is disabled): print the link for the user.
- Run GUI programs: there is no display. Browsers (Playwright, Chrome) must run headless. - Run GUI programs, unless `WAYLAND_DISPLAY` is set (the project enabled `OCSB_DISPLAY=1`): then
windows open on the user's desktop through Wayland. Without it browsers must run headless.
- `sudo`, `systemd-run`: denied. - `sudo`, `systemd-run`: denied.
- Start processes outside the sandbox through the session bus: only desktop notifications - Start processes outside the sandbox through the session bus: only desktop notifications
are reachable. are reachable.