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:
2026-10-30 01:31:36 -07:00
parent 5a7567849e
commit 01fc1627c1
8 changed files with 143 additions and 8 deletions
+57 -1
View File
@@ -117,6 +117,59 @@ ocsb_start_dbus_proxy() {
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)
ocsb_bwrap_args() {
local p f uid
@@ -174,6 +227,7 @@ ocsb_bwrap_args() {
OCSB_BWRAP+=(--ro-bind /dev/null "${p}")
fi
done
ocsb_protect_args
if [[ -n ${OCSB_DBUS_PROXY_PID:-} && -S $(ocsb_dbus_sock) ]]; then
OCSB_BWRAP+=(--ro-bind "$(ocsb_dbus_sock)" "/run/user/${uid}/bus")
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)"
if ocsb_start_dbus_proxy; then
env+=("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus")
trap 'kill "${OCSB_DBUS_PROXY_PID}" 2>/dev/null || true' EXIT
else
unset OCSB_DBUS_PROXY_PID
env+=("DBUS_SESSION_BUS_ADDRESS=")
fi
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[@]}")
# 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.
@@ -282,6 +337,7 @@ ocsb_run() {
for kv in "${env[@]}"; 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
cd "${OCSB_PROJECT_DIR}" || return
sg "${OCSB_NETLOCK_GROUP}" -c "${inner}" 2>&1 | while IFS= read -r line; do