opencode-sandbox: OpenCode 2 in a sandbox for any project

Runs OpenCode 2 with full permissions while containing its mistakes:

- egress filter: squid in a container per project; squid.conf is rendered
  from layered egress.conf files (package, user, project, local) with
  allow/block/connect rules and expect-pass/expect-block self-tests
- netlock: nftables rejects direct egress for the opencode-sandbox group
  (installed once with sudo as a systemd unit)
- bubblewrap: read-only host, writable project, worktrees and state;
  credentials hidden; ssh keys via the host's ssh-agent; session bus
  replaced by an xdg-dbus-proxy limited to notifications; no permission
  prompts (sudo and systemd-run denied); refuses to run unsandboxed
- per-project state in ~/.local/share/opencode-sandbox/<name>
- worktrees: `wt add|rm|list` with project hooks
- skills mounted per project; the opencode-sandbox skill explains the
  environment to the agent
- plugins as submodules: context-panel, skill-compact-resurrect;
  global config adds opencode-notifier and cc-safety-net
- README in English and Russian
This commit is contained in:
2026-10-30 01:00:02 -07:00
commit 64c222a084
29 changed files with 1945 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# opencode-sandbox — run OpenCode 2 for a project inside a sandbox:
# egress filter (squid in a container) + netlock (nftables by group) + bubblewrap.
# See README.md. Start with: opencode-sandbox help
set -euo pipefail
OCSB_HOME="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)"
export OCSB_HOME
# shellcheck source-path=SCRIPTDIR/..
# shellcheck source=lib/common.sh
source "${OCSB_HOME}/lib/common.sh"
usage() {
cat <<'EOF'
Usage: opencode-sandbox <command> [args]
Project setup
init Create .opencode-sandbox/ in the current git repository
config Print the effective configuration
doctor Check dependencies and host setup
Running
run [opencode args] Prepare everything and start OpenCode (web UI) in the sandbox
prepare Egress filter up + netlock check + self-tests, without OpenCode
Egress filter (squid)
egress up|down|status|logs|verify|render
Netlock (blocks direct egress for the agent group; setup/teardown need sudo)
netlock setup|teardown|status|verify
Worktrees
wt add <branch> [hook args] Create a worktree (runs .opencode-sandbox/hooks/wt-post-add)
wt rm <name|path> [--force] Remove a worktree (runs .opencode-sandbox/hooks/wt-pre-rm)
wt list
Environment: OCSB_<SETTING>=... overrides any setting for one run (see `config`).
EOF
}
command_name="${1:-help}"
[[ $# -gt 0 ]] && shift
case "${command_name}" in
init)
source "${OCSB_HOME}/lib/init.sh"
ocsb_init "$@"
;;
config)
ocsb_load_project
ocsb_print_config
;;
doctor)
source "${OCSB_HOME}/lib/doctor.sh"
ocsb_doctor "$@"
;;
run)
ocsb_load_project
source "${OCSB_HOME}/lib/egress.sh"
source "${OCSB_HOME}/lib/netlock.sh"
source "${OCSB_HOME}/lib/run.sh"
ocsb_prepare
ocsb_run "$@"
;;
prepare)
ocsb_load_project
source "${OCSB_HOME}/lib/egress.sh"
source "${OCSB_HOME}/lib/netlock.sh"
source "${OCSB_HOME}/lib/run.sh"
ocsb_prepare
;;
egress)
ocsb_load_project
source "${OCSB_HOME}/lib/egress.sh"
ocsb_egress "$@"
;;
netlock)
ocsb_load_project --optional
source "${OCSB_HOME}/lib/netlock.sh"
ocsb_netlock "$@"
;;
wt)
ocsb_load_project
source "${OCSB_HOME}/lib/worktree.sh"
ocsb_wt "$@"
;;
help | -h | --help)
usage
;;
*)
usage >&2
ocsb_die "unknown command: ${command_name}"
;;
esac