Protect host-run files from the agent; trust registry; git audit
- read-only in the sandbox: .opencode-sandbox/, .cc-safety-net/, .git/hooks, .git/config, an in-project core.hooksPath, the egress rules and the safety-net home; .git is pinned by a bind onto itself so it cannot be swapped - only trusted projects are loaded (init / trust): a .opencode-sandbox/ in a worktree or a subdirectory is no longer sourced on the host - run (before and after the session) and doctor report planted .git entries, redirected worktrees and per-worktree overrides - hide Firefox, Claude, VS Code, Cursor and Codex configs
This commit is contained in:
@@ -70,7 +70,11 @@ browser opens at `http://127.0.0.1:4196/`; log in as `opencode` with the passwor
|
|||||||
`~/.local/share/opencode-sandbox/<project>/password` (printed on the first start).
|
`~/.local/share/opencode-sandbox/<project>/password` (printed on the first start).
|
||||||
|
|
||||||
Commit `.opencode-sandbox/` (except `local/`) and `.cc-safety-net/` — everyone who clones the
|
Commit `.opencode-sandbox/` (except `local/`) and `.cc-safety-net/` — everyone who clones the
|
||||||
project gets the same sandbox.
|
project gets the same sandbox. `sandbox.conf` and the hooks are bash that runs on your host, so
|
||||||
|
only trusted projects are loaded: `init` trusts the project it creates; in a clone, read
|
||||||
|
`.opencode-sandbox/` and run `opencode-sandbox trust` (the list is
|
||||||
|
`~/.config/opencode-sandbox/projects`). A `.opencode-sandbox/` in a worktree or a
|
||||||
|
subdirectory is ignored.
|
||||||
|
|
||||||
## Project files
|
## Project files
|
||||||
|
|
||||||
@@ -233,6 +237,14 @@ Inside the sandbox:
|
|||||||
browsers' singleton sockets, other tools), so the agent gets its own, stored in
|
browsers' singleton sockets, other tools), so the agent gets its own, stored in
|
||||||
`<state>/tmp` (files older than 7 days are removed on start). Look there for its screenshots
|
`<state>/tmp` (files older than 7 days are removed on start). Look there for its screenshots
|
||||||
and logs. Docker mounts of `/tmp` paths would point at the host's `/tmp` — mount project paths.
|
and logs. Docker mounts of `/tmp` paths would point at the host's `/tmp` — mount project paths.
|
||||||
|
- **Read-only inside the project:** what the host runs outside the sandbox — `.opencode-sandbox/`,
|
||||||
|
`.cc-safety-net/`, `.git/hooks`, `.git/config` (`core.fsmonitor` there runs on any
|
||||||
|
`git status`, a shell prompt is enough), `core.hooksPath` if it is in the project — and the
|
||||||
|
egress rules and safety-net home in the state directory. `.git` itself is a mount point, so
|
||||||
|
it cannot be renamed and replaced. Git still works; only `git config` writes fail (e.g.
|
||||||
|
`push -u` cannot record the upstream). After each session `run` reports any other `.git` in
|
||||||
|
the project and worktrees, worktrees whose `.git` points elsewhere and per-worktree
|
||||||
|
overrides; `doctor` does the same.
|
||||||
- **Hidden:** `OCSB_HIDE_HOME`, `OCSB_HIDE`, private keys in `~/.ssh` (`config`, `known_hosts`
|
- **Hidden:** `OCSB_HIDE_HOME`, `OCSB_HIDE`, private keys in `~/.ssh` (`config`, `known_hosts`
|
||||||
and `*.pub` stay visible; the host's ssh-agent signs).
|
and `*.pub` stay visible; the host's ssh-agent signs).
|
||||||
- **Network:** `HTTP(S)_PROXY` point to the filter. `GIT_SSH_COMMAND` sends ssh through it with
|
- **Network:** `HTTP(S)_PROXY` point to the filter. `GIT_SSH_COMMAND` sends ssh through it with
|
||||||
@@ -269,6 +281,12 @@ place, requests to production — not to hold a determined attacker.
|
|||||||
- **Allowed hosts are a channel.** Data can leave through any host the rules allow.
|
- **Allowed hosts are a channel.** Data can leave through any host the rules allow.
|
||||||
- **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.
|
||||||
|
- **Files you run yourself.** Hooks, configs and git metadata are protected (see above), but
|
||||||
|
the agent edits code: a changed `Makefile`, `package.json` script or `bin/` file runs with
|
||||||
|
your rights when you run it. Look at `git diff` before running project commands on the host.
|
||||||
|
- **Planted repositories are reported, not prevented.** A `.git` the agent creates in a new
|
||||||
|
directory or a new worktree is found by the check after the session; don't run git there
|
||||||
|
until you have looked.
|
||||||
- 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
|
- With `OCSB_DISPLAY=1` the agent can open windows on your desktop and use the clipboard while
|
||||||
its window is focused.
|
its window is focused.
|
||||||
@@ -304,6 +322,7 @@ docker rmi opencode-sandbox-egress
|
|||||||
|
|
||||||
```
|
```
|
||||||
opencode-sandbox init add .opencode-sandbox/ to the current repository
|
opencode-sandbox init add .opencode-sandbox/ to the current repository
|
||||||
|
opencode-sandbox trust [dir] trust an existing .opencode-sandbox/ (a clone)
|
||||||
opencode-sandbox config effective settings
|
opencode-sandbox config effective settings
|
||||||
opencode-sandbox doctor dependencies and host setup
|
opencode-sandbox doctor dependencies and host setup
|
||||||
opencode-sandbox run [opencode args] everything, then OpenCode
|
opencode-sandbox run [opencode args] everything, then OpenCode
|
||||||
|
|||||||
+21
-1
@@ -69,7 +69,11 @@ opencode-sandbox run
|
|||||||
`~/.local/share/opencode-sandbox/<проект>/password` (выводится при первом запуске).
|
`~/.local/share/opencode-sandbox/<проект>/password` (выводится при первом запуске).
|
||||||
|
|
||||||
Закоммитьте `.opencode-sandbox/` (кроме `local/`) и `.cc-safety-net/` — каждый, кто
|
Закоммитьте `.opencode-sandbox/` (кроме `local/`) и `.cc-safety-net/` — каждый, кто
|
||||||
склонирует проект, получит ту же песочницу.
|
склонирует проект, получит ту же песочницу. `sandbox.conf` и хуки — это bash, который
|
||||||
|
выполняется на вашем хосте, поэтому загружаются только доверенные проекты: `init` доверяет
|
||||||
|
созданному проекту, а в клоне прочитайте `.opencode-sandbox/` и выполните
|
||||||
|
`opencode-sandbox trust` (список — `~/.config/opencode-sandbox/projects`).
|
||||||
|
`.opencode-sandbox/` в worktree или подкаталоге игнорируется.
|
||||||
|
|
||||||
## Файлы проекта
|
## Файлы проекта
|
||||||
|
|
||||||
@@ -233,6 +237,14 @@ opencode-sandbox run
|
|||||||
(tmux, X11, сокеты браузеров, других программ), поэтому у агента свой `/tmp` — в
|
(tmux, X11, сокеты браузеров, других программ), поэтому у агента свой `/tmp` — в
|
||||||
`<состояние>/tmp` (файлы старше 7 дней удаляются при запуске). Скриншоты и логи агента ищите
|
`<состояние>/tmp` (файлы старше 7 дней удаляются при запуске). Скриншоты и логи агента ищите
|
||||||
там. Docker смонтирует путь из `/tmp` из `/tmp` хоста — монтируйте пути проекта.
|
там. Docker смонтирует путь из `/tmp` из `/tmp` хоста — монтируйте пути проекта.
|
||||||
|
- **Только чтение внутри проекта:** то, что хост выполняет вне песочницы, — `.opencode-sandbox/`,
|
||||||
|
`.cc-safety-net/`, `.git/hooks`, `.git/config` (`core.fsmonitor` оттуда срабатывает при любом
|
||||||
|
`git status`, хватит prompt'а оболочки), `core.hooksPath`, если он внутри проекта, — а также
|
||||||
|
правила egress и домашний каталог safety-net в каталоге состояния. Сам `.git` — точка
|
||||||
|
монтирования, его нельзя переименовать и подменить. git работает; не проходит только запись
|
||||||
|
`git config` (например, `push -u` не запишет upstream). После каждой сессии `run` сообщает
|
||||||
|
о других `.git` в проекте и worktree, о worktree, чей `.git` указывает в другое место, и о
|
||||||
|
переопределениях для отдельных worktree; `doctor` проверяет то же.
|
||||||
- **Скрыто:** `OCSB_HIDE_HOME`, `OCSB_HIDE`, приватные ключи в `~/.ssh` (`config`,
|
- **Скрыто:** `OCSB_HIDE_HOME`, `OCSB_HIDE`, приватные ключи в `~/.ssh` (`config`,
|
||||||
`known_hosts` и `*.pub` видны; подписывает ssh-agent хоста).
|
`known_hosts` и `*.pub` видны; подписывает ssh-agent хоста).
|
||||||
- **Сеть:** `HTTP(S)_PROXY` указывают на фильтр. `GIT_SSH_COMMAND` пускает ssh через него
|
- **Сеть:** `HTTP(S)_PROXY` указывают на фильтр. `GIT_SSH_COMMAND` пускает ssh через него
|
||||||
@@ -269,6 +281,13 @@ opencode-sandbox run
|
|||||||
правила.
|
правила.
|
||||||
- **Проект доступен на запись.** Ошибки внутри него настоящие; cc-safety-net блокирует
|
- **Проект доступен на запись.** Ошибки внутри него настоящие; cc-safety-net блокирует
|
||||||
типичные разрушительные команды, git хранит историю — коммитьте чаще.
|
типичные разрушительные команды, git хранит историю — коммитьте чаще.
|
||||||
|
- **Файлы, которые вы запускаете сами.** Хуки, конфиги и метаданные git защищены (см. выше),
|
||||||
|
но код агент правит: изменённый `Makefile`, скрипт в `package.json` или файл в `bin/`
|
||||||
|
выполнится с вашими правами, когда вы его запустите. Смотрите `git diff`, прежде чем
|
||||||
|
запускать команды проекта на хосте.
|
||||||
|
- **Подложенные репозитории обнаруживаются, а не предотвращаются.** `.git`, созданный агентом
|
||||||
|
в новом каталоге или новом worktree, найдёт проверка после сессии; не запускайте там git,
|
||||||
|
пока не посмотрели.
|
||||||
- С `OCSB_TMP=shared` доступны `/tmp` хоста и его сокеты.
|
- С `OCSB_TMP=shared` доступны `/tmp` хоста и его сокеты.
|
||||||
- С `OCSB_DISPLAY=1` агент может открывать окна на вашем экране и пользоваться буфером обмена,
|
- С `OCSB_DISPLAY=1` агент может открывать окна на вашем экране и пользоваться буфером обмена,
|
||||||
пока его окно в фокусе.
|
пока его окно в фокусе.
|
||||||
@@ -304,6 +323,7 @@ docker rmi opencode-sandbox-egress
|
|||||||
|
|
||||||
```
|
```
|
||||||
opencode-sandbox init добавить .opencode-sandbox/ в текущий репозиторий
|
opencode-sandbox init добавить .opencode-sandbox/ в текущий репозиторий
|
||||||
|
opencode-sandbox trust [каталог] доверять существующему .opencode-sandbox/ (клон)
|
||||||
opencode-sandbox config итоговые настройки
|
opencode-sandbox config итоговые настройки
|
||||||
opencode-sandbox doctor зависимости и настройки хоста
|
opencode-sandbox doctor зависимости и настройки хоста
|
||||||
opencode-sandbox run [аргументы opencode] всё сразу, затем OpenCode
|
opencode-sandbox run [аргументы opencode] всё сразу, затем OpenCode
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Usage: opencode-sandbox <command> [args]
|
|||||||
|
|
||||||
Project setup
|
Project setup
|
||||||
init Create .opencode-sandbox/ in the current git repository
|
init Create .opencode-sandbox/ in the current git repository
|
||||||
|
trust [dir] Trust an existing .opencode-sandbox/ (a clone): its config runs on the host
|
||||||
config Print the effective configuration
|
config Print the effective configuration
|
||||||
doctor Check dependencies and host setup
|
doctor Check dependencies and host setup
|
||||||
|
|
||||||
@@ -46,6 +47,9 @@ case "${command_name}" in
|
|||||||
source "${OCSB_HOME}/lib/init.sh"
|
source "${OCSB_HOME}/lib/init.sh"
|
||||||
ocsb_init "$@"
|
ocsb_init "$@"
|
||||||
;;
|
;;
|
||||||
|
trust)
|
||||||
|
ocsb_trust "$@"
|
||||||
|
;;
|
||||||
config)
|
config)
|
||||||
ocsb_load_project
|
ocsb_load_project
|
||||||
ocsb_print_config
|
ocsb_print_config
|
||||||
|
|||||||
+31
-5
@@ -55,19 +55,44 @@ ocsb_defaults() {
|
|||||||
OCSB_HIDE_HOME=(.gnupg .aws .azure .config/gcloud .kube/config .docker/config.json .netrc
|
OCSB_HIDE_HOME=(.gnupg .aws .azure .config/gcloud .kube/config .docker/config.json .netrc
|
||||||
.git-credentials .config/gh/hosts.yml .password-store .local/share/keyrings .mozilla
|
.git-credentials .config/gh/hosts.yml .password-store .local/share/keyrings .mozilla
|
||||||
.config/google-chrome .config/chromium .config/BraveSoftware .claude .codex
|
.config/google-chrome .config/chromium .config/BraveSoftware .claude .codex
|
||||||
.config/opencode .local/share/opencode .Xauthority)
|
.config/opencode .local/share/opencode .Xauthority .config/mozilla .config/Claude .config/Code
|
||||||
|
.config/Cursor .config/Codex)
|
||||||
OCSB_SKILLS=(opencode-sandbox) # skills mounted for the agent (names of skill directories)
|
OCSB_SKILLS=(opencode-sandbox) # skills mounted for the agent (names of skill directories)
|
||||||
OCSB_SKILL_DIRS=() # extra directories to look for skills in
|
OCSB_SKILL_DIRS=() # extra directories to look for skills in
|
||||||
OCSB_PLUGINS=(skill-compact-resurrect/skill-compact-resurrect.v2.ts) # files in plugins/
|
OCSB_PLUGINS=(skill-compact-resurrect/skill-compact-resurrect.v2.ts) # files in plugins/
|
||||||
OCSB_ENV=() # extra KEY=VALUE for OpenCode
|
OCSB_ENV=() # extra KEY=VALUE for OpenCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Trusted projects (init / trust), one physical path per line. Only these are loaded:
|
||||||
|
# sandbox.conf is bash run on the host, and the agent can write anywhere in the project
|
||||||
|
# tree, so a .opencode-sandbox/ found in a worktree or a subdirectory proves nothing.
|
||||||
|
ocsb_trust_file() { echo "${XDG_CONFIG_HOME:-${HOME}/.config}/opencode-sandbox/projects"; }
|
||||||
|
ocsb_trusted() { [[ -f $(ocsb_trust_file) ]] && grep -Fxq -- "$1" "$(ocsb_trust_file)"; }
|
||||||
|
ocsb_trust() {
|
||||||
|
local dir f
|
||||||
|
dir=$(cd "${1:-.}" && pwd -P) || ocsb_die "no such directory: ${1}"
|
||||||
|
[[ -f ${dir}/.opencode-sandbox/sandbox.conf ]] || ocsb_die "no ${dir}/.opencode-sandbox/sandbox.conf"
|
||||||
|
[[ -z ${OCSB_IN_SANDBOX:-} ]] || ocsb_die "trust is a host command"
|
||||||
|
ocsb_trusted "${dir}" && { echo "already trusted: ${dir}"; return 0; }
|
||||||
|
f=$(ocsb_trust_file)
|
||||||
|
mkdir -p "$(dirname "${f}")"
|
||||||
|
echo "${dir}" >>"${f}"
|
||||||
|
echo "✓ trusted: ${dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Nearest trusted project at or above the directory
|
||||||
ocsb_find_project() {
|
ocsb_find_project() {
|
||||||
local dir=${1:-$PWD}
|
local dir untrusted=""
|
||||||
|
dir=$(cd "${1:-.}" && pwd -P)
|
||||||
while [[ ${dir} != / ]]; do
|
while [[ ${dir} != / ]]; do
|
||||||
[[ -f ${dir}/.opencode-sandbox/sandbox.conf ]] && { echo "${dir}"; return 0; }
|
if [[ -f ${dir}/.opencode-sandbox/sandbox.conf ]]; then
|
||||||
|
ocsb_trusted "${dir}" && { echo "${dir}"; return 0; }
|
||||||
|
untrusted=${untrusted:-${dir}}
|
||||||
|
fi
|
||||||
dir=$(dirname "${dir}")
|
dir=$(dirname "${dir}")
|
||||||
done
|
done
|
||||||
|
[[ -z ${untrusted} ]] ||
|
||||||
|
ocsb_warn "${untrusted} is not a trusted project: read its .opencode-sandbox/ (sandbox.conf and hooks run on the host), then: opencode-sandbox trust ${untrusted}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +107,13 @@ ocsb_load_project() {
|
|||||||
|
|
||||||
OCSB_PROJECT_DIR=""
|
OCSB_PROJECT_DIR=""
|
||||||
if [[ -n ${OCSB_PROJECT:-} ]]; then
|
if [[ -n ${OCSB_PROJECT:-} ]]; then
|
||||||
OCSB_PROJECT_DIR=$(cd "${OCSB_PROJECT}" && pwd)
|
OCSB_PROJECT_DIR=$(cd "${OCSB_PROJECT}" && pwd -P)
|
||||||
|
ocsb_trusted "${OCSB_PROJECT_DIR}" || ocsb_die "OCSB_PROJECT=${OCSB_PROJECT} is not a trusted project (opencode-sandbox trust)"
|
||||||
else
|
else
|
||||||
OCSB_PROJECT_DIR=$(ocsb_find_project) || true
|
OCSB_PROJECT_DIR=$(ocsb_find_project) || true
|
||||||
fi
|
fi
|
||||||
if [[ -z ${OCSB_PROJECT_DIR} || ! -f ${OCSB_PROJECT_DIR}/.opencode-sandbox/sandbox.conf ]]; then
|
if [[ -z ${OCSB_PROJECT_DIR} || ! -f ${OCSB_PROJECT_DIR}/.opencode-sandbox/sandbox.conf ]]; then
|
||||||
[[ ${optional} == 1 ]] || ocsb_die "no .opencode-sandbox/sandbox.conf here or above (run: opencode-sandbox init)"
|
[[ ${optional} == 1 ]] || ocsb_die "no trusted .opencode-sandbox/ here or above (new project: opencode-sandbox init)"
|
||||||
fi
|
fi
|
||||||
export OCSB_PROJECT_DIR
|
export OCSB_PROJECT_DIR
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ ocsb_doctor() {
|
|||||||
echo "Project: ${OCSB_PROJECT_DIR} (name ${OCSB_NAME}, state ${OCSB_STATE_DIR})"
|
echo "Project: ${OCSB_PROJECT_DIR} (name ${OCSB_NAME}, state ${OCSB_STATE_DIR})"
|
||||||
source "${OCSB_HOME}/lib/egress.sh"
|
source "${OCSB_HOME}/lib/egress.sh"
|
||||||
ocsb_egress status
|
ocsb_egress status
|
||||||
|
source "${OCSB_HOME}/lib/run.sh"
|
||||||
|
if ocsb_git_audit; then echo " ✓ no planted git metadata in the project and worktrees"; else fail=1; fi
|
||||||
else
|
else
|
||||||
echo "Project: none here (opencode-sandbox init)"
|
echo "Project: none here (opencode-sandbox init)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ ocsb_init() {
|
|||||||
mkdir -p "${root}/.cc-safety-net"
|
mkdir -p "${root}/.cc-safety-net"
|
||||||
cp "${t}/cc-safety-net-policy.json" "${root}/.cc-safety-net/policy.json"
|
cp "${t}/cc-safety-net-policy.json" "${root}/.cc-safety-net/policy.json"
|
||||||
fi
|
fi
|
||||||
|
ocsb_trust "${root}" >/dev/null
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
✓ created ${root}/.opencode-sandbox/
|
✓ created ${root}/.opencode-sandbox/
|
||||||
sandbox.conf settings (ports, writable paths, skills, worktrees) — commit it
|
sandbox.conf settings (ports, writable paths, skills, worktrees) — commit it
|
||||||
egress.conf domains for this project — commit it
|
egress.conf domains for this project — commit it
|
||||||
hooks/ worktree hooks: drop .example and chmod +x to enable
|
hooks/ worktree hooks: drop .example and chmod +x to enable
|
||||||
local/ personal overrides (sandbox.conf, egress.conf), git-ignored
|
local/ personal overrides (sandbox.conf, egress.conf), git-ignored
|
||||||
|
The project is trusted on this machine; a clone elsewhere needs: opencode-sandbox trust
|
||||||
|
|
||||||
Next:
|
Next:
|
||||||
opencode-sandbox doctor check the host
|
opencode-sandbox doctor check the host
|
||||||
|
|||||||
+57
-1
@@ -117,6 +117,59 @@ ocsb_start_dbus_proxy() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Files the host trusts and runs outside the sandbox stay read-only for the agent: the
|
||||||
|
# sandbox config and hooks, the safety-net policies, the egress rules, git hooks and config
|
||||||
|
# (core.fsmonitor runs on any git status — a shell prompt is enough). .git is first bound
|
||||||
|
# onto itself: a mount point cannot be renamed, so it cannot be swapped for a planted one.
|
||||||
|
ocsb_protect_args() {
|
||||||
|
local d=${OCSB_PROJECT_DIR} p hooks
|
||||||
|
mkdir -p "${d}/.cc-safety-net" "${OCSB_STATE_DIR}/cc-safety-net"
|
||||||
|
for p in "${d}/.opencode-sandbox" "${d}/.cc-safety-net" "${OCSB_STATE_DIR}/cc-safety-net" "${OCSB_STATE_DIR}/egress"; do
|
||||||
|
OCSB_BWRAP+=(--ro-bind "${p}" "${p}")
|
||||||
|
done
|
||||||
|
if [[ -d ${d}/.git ]]; then
|
||||||
|
mkdir -p "${d}/.git/hooks"
|
||||||
|
OCSB_BWRAP+=(--bind "${d}/.git" "${d}/.git")
|
||||||
|
for p in "${d}"/.git/{hooks,config} "${d}"/.git/modules/*/{hooks,config}; do
|
||||||
|
[[ -e ${p} ]] && OCSB_BWRAP+=(--ro-bind "${p}" "${p}")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
# core.hooksPath inside the project (husky and the like)
|
||||||
|
hooks=$(git -C "${d}" config --get core.hooksPath 2>/dev/null) || true
|
||||||
|
if [[ -n ${hooks} ]]; then
|
||||||
|
hooks=$(cd "${d}" && realpath -m -- "${hooks/#\~/${HOME}}")
|
||||||
|
[[ ${hooks} == "${d}"/* && -e ${hooks} ]] && OCSB_BWRAP+=(--ro-bind "${hooks}" "${hooks}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Git repositories planted for the host's git. The main .git is read-only for the agent, but
|
||||||
|
# it can still create a .git anywhere it writes, point a worktree's .git elsewhere, or add
|
||||||
|
# per-worktree overrides — and a repository's config can run commands. Report all of that.
|
||||||
|
ocsb_git_audit() {
|
||||||
|
local main=${OCSB_PROJECT_DIR}/.git f gitdir found=() roots=()
|
||||||
|
[[ -d ${main} ]] || return 0
|
||||||
|
roots=("${OCSB_PROJECT_DIR}")
|
||||||
|
[[ -d ${OCSB_WORKTREES_DIR} ]] && roots+=("$(realpath -- "${OCSB_WORKTREES_DIR}")")
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
[[ ${f} == "${main}" ]] && continue
|
||||||
|
if [[ -f ${f} && ! -L ${f} ]]; then
|
||||||
|
gitdir=$(sed -n '1s/^gitdir: //p' "${f}")
|
||||||
|
[[ ${gitdir} == "${main}/worktrees/"* && ${gitdir} != */..* && $(cat "${gitdir}/gitdir" 2>/dev/null) == "${f}" ]] && continue
|
||||||
|
fi
|
||||||
|
found+=("${f}")
|
||||||
|
done < <(find "${roots[@]}" -name .git -prune -print0 2>/dev/null)
|
||||||
|
for f in "${main}"/worktrees/*/; do
|
||||||
|
[[ -d ${f} ]] || continue
|
||||||
|
[[ $(cat "${f}commondir" 2>/dev/null) == ../.. ]] || found+=("${f}commondir")
|
||||||
|
[[ -e ${f}config.worktree ]] && found+=("${f}config.worktree")
|
||||||
|
done
|
||||||
|
[[ -e ${main}/commondir ]] && found+=("${main}/commondir")
|
||||||
|
((${#found[@]} == 0)) && return 0
|
||||||
|
ocsb_warn "unexpected git metadata — check it before running git there (a repository config can run commands):"
|
||||||
|
printf ' %s\n' "${found[@]}" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# bubblewrap arguments for the sandbox (variant: read-only host, writable project)
|
# bubblewrap arguments for the sandbox (variant: read-only host, writable project)
|
||||||
ocsb_bwrap_args() {
|
ocsb_bwrap_args() {
|
||||||
local p f uid
|
local p f uid
|
||||||
@@ -174,6 +227,7 @@ ocsb_bwrap_args() {
|
|||||||
OCSB_BWRAP+=(--ro-bind /dev/null "${p}")
|
OCSB_BWRAP+=(--ro-bind /dev/null "${p}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
ocsb_protect_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
|
||||||
@@ -250,12 +304,13 @@ ocsb_run() {
|
|||||||
ssh-add -l >/dev/null 2>&1 || ocsb_warn "ssh-agent has no keys — git over SSH will fail in the sandbox (ssh-add)"
|
ssh-add -l >/dev/null 2>&1 || ocsb_warn "ssh-agent has no keys — git over SSH will fail in the sandbox (ssh-add)"
|
||||||
if ocsb_start_dbus_proxy; then
|
if ocsb_start_dbus_proxy; then
|
||||||
env+=("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus")
|
env+=("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus")
|
||||||
trap 'kill "${OCSB_DBUS_PROXY_PID}" 2>/dev/null || true' EXIT
|
|
||||||
else
|
else
|
||||||
unset OCSB_DBUS_PROXY_PID
|
unset OCSB_DBUS_PROXY_PID
|
||||||
env+=("DBUS_SESSION_BUS_ADDRESS=")
|
env+=("DBUS_SESSION_BUS_ADDRESS=")
|
||||||
fi
|
fi
|
||||||
ocsb_bwrap_args
|
ocsb_bwrap_args
|
||||||
|
# On any exit (Ctrl-C too): stop the bus proxy, look for what the session planted
|
||||||
|
trap 'kill "${OCSB_DBUS_PROXY_PID:-}" 2>/dev/null; ocsb_git_audit || true' EXIT
|
||||||
cmd=(bwrap "${OCSB_BWRAP[@]}" -- "${cmd[@]}")
|
cmd=(bwrap "${OCSB_BWRAP[@]}" -- "${cmd[@]}")
|
||||||
# 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.
|
||||||
@@ -282,6 +337,7 @@ ocsb_run() {
|
|||||||
for kv in "${env[@]}"; do inner+=" $(printf '%q' "${kv}")"; done
|
for kv in "${env[@]}"; do inner+=" $(printf '%q' "${kv}")"; done
|
||||||
for kv in "${cmd[@]}"; do inner+=" $(printf '%q' "${kv}")"; done
|
for kv in "${cmd[@]}"; do inner+=" $(printf '%q' "${kv}")"; done
|
||||||
|
|
||||||
|
[[ ${OCSB_SANDBOX} == 1 ]] && { ocsb_git_audit || true; }
|
||||||
local opened=0 line
|
local opened=0 line
|
||||||
cd "${OCSB_PROJECT_DIR}" || return
|
cd "${OCSB_PROJECT_DIR}" || return
|
||||||
sg "${OCSB_NETLOCK_GROUP}" -c "${inner}" 2>&1 | while IFS= read -r line; do
|
sg "${OCSB_NETLOCK_GROUP}" -c "${inner}" 2>&1 | while IFS= read -r line; do
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ sandbox is on.
|
|||||||
| Toolchain caches in `~` (`.cache`, `.npm`, `.asdf`, `.bundle`, `.gem`, …) | read-write |
|
| Toolchain caches in `~` (`.cache`, `.npm`, `.asdf`, `.bundle`, `.gem`, …) | read-write |
|
||||||
| `/tmp` | read-write, **private** to the sandbox (on the host it is `<state>/tmp`; files older than 7 days are cleaned) |
|
| `/tmp` | read-write, **private** to the sandbox (on the host it is `<state>/tmp`; files older than 7 days are cleaned) |
|
||||||
| Everything else | read-only |
|
| Everything else | read-only |
|
||||||
|
| `.opencode-sandbox/`, `.cc-safety-net/`, `.git/hooks`, `.git/config` | read-only (the host runs them) |
|
||||||
| Private SSH keys, `~/.gnupg`, cloud CLI credentials, `~/.kube/config`, browser profiles, other agents' configs, `OCSB_HIDE` paths | hidden (empty) |
|
| Private SSH keys, `~/.gnupg`, cloud CLI credentials, `~/.kube/config`, browser profiles, other agents' configs, `OCSB_HIDE` paths | hidden (empty) |
|
||||||
|
|
||||||
- `Read-only file system` / `EROFS` means the path is outside the writable set. Do not retry,
|
- `Read-only file system` / `EROFS` means the path is outside the writable set. Do not retry,
|
||||||
do not look for another way to write there. If the task really needs it, tell the user
|
do not look for another way to write there. If the task really needs it, tell the user
|
||||||
which path and why — they can add it to `OCSB_RW`.
|
which path and why — they can add it to `OCSB_RW`.
|
||||||
- A hidden credential is not a bug either; ask the user instead of searching for it.
|
- A hidden credential is not a bug either; ask the user instead of searching for it.
|
||||||
|
- `git config` writes fail (`could not lock config file`): commits, branches, worktrees, fetch
|
||||||
|
and push work; `push -u` just cannot record the upstream — use `git push origin <branch>`.
|
||||||
|
Need a sandbox, egress or safety-net rule changed? Tell the user what and why.
|
||||||
|
- Do not create git repositories (`git init`, `git clone`) inside the project or the worktrees
|
||||||
|
directory unless asked: the user is warned about every unexpected `.git` there.
|
||||||
|
|
||||||
## Network
|
## Network
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user