Files
k.denisov 64c222a084 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
2026-10-30 01:00:02 -07:00

76 lines
3.4 KiB
Bash

# shellcheck shell=bash
# Netlock: direct egress (ping, curl by IP, ssh, anything not via the egress filter) is
# rejected for processes running with the netlock group as their primary group. OpenCode
# is started through `sg <group>`, so the agent and everything it spawns are covered.
OCSB_NETLOCK_UNIT=opencode-sandbox-netlock.service
OCSB_NETLOCK_PROBE=${OCSB_NETLOCK_PROBE:-1.1.1.1/443}
ocsb_netlock_active() { systemctl is-active --quiet "${OCSB_NETLOCK_UNIT}"; }
ocsb_netlock_member() {
local members
getent group "${OCSB_NETLOCK_GROUP}" >/dev/null || return 1
members=$(getent group "${OCSB_NETLOCK_GROUP}" | cut -d: -f4)
[[ ",${members}," == *",${USER},"* ]]
}
ocsb_netlock_setup() {
sudo "${OCSB_HOME}/lib/netlock-root.sh" setup "${OCSB_NETLOCK_GROUP}" "${USER}"
}
# Fails unless direct TCP from the group is rejected (not merely timing out)
ocsb_netlock_check() {
local rc=0
ocsb_netlock_member || ocsb_die "${USER} is not in group ${OCSB_NETLOCK_GROUP} (opencode-sandbox netlock setup)"
sg "${OCSB_NETLOCK_GROUP}" -c "timeout 10 bash -c 'exec 3<>/dev/tcp/${OCSB_NETLOCK_PROBE}'" 2>/dev/null || rc=$?
case ${rc} in
0) ocsb_die "netlock is NOT working: direct TCP to ${OCSB_NETLOCK_PROBE/\//:} from ${OCSB_NETLOCK_GROUP} succeeded" ;;
124) ocsb_die "netlock check inconclusive: direct TCP to ${OCSB_NETLOCK_PROBE/\//:} hung instead of being rejected" ;;
esac
}
ocsb_netlock_verify() {
local code
echo "Netlock self-test (group ${OCSB_NETLOCK_GROUP}):"
if sg "${OCSB_NETLOCK_GROUP}" -c 'curl --noproxy "*" -s -o /dev/null --max-time 5 https://example.com/' 2>/dev/null; then
echo " ✗ direct HTTPS bypassing the filter went through"; return 1
fi
echo " ✓ direct HTTPS bypassing the filter is blocked"
if sg "${OCSB_NETLOCK_GROUP}" -c 'ping -c1 -W1 1.1.1.1' >/dev/null 2>&1; then
echo " ✗ ping went through"; return 1
fi
echo " ✓ ping is blocked"
if [[ -n ${OCSB_PROJECT_DIR} ]]; then
# What is checked is that the group reaches the proxy and the proxy opens the tunnel
# (CONNECT 200), not that a particular remote site answers in time
code=$(sg "${OCSB_NETLOCK_GROUP}" -c "curl -sx http://127.0.0.1:${OCSB_EGRESS_PORT} -o /dev/null -w '%{http_connect} %{http_code}' --max-time 20 https://example.com/" || true)
case "${code}" in
200\ * | *\ [23]??) echo " ✓ HTTPS through the filter → tunnel open (connect/http: ${code})" ;;
*) echo " ✗ HTTPS through the filter → connect/http: ${code:-none} (is the egress filter up?)"; return 1 ;;
esac
fi
}
ocsb_netlock() {
local sub=${1:-status}
case "${sub}" in
setup) ocsb_netlock_setup ;;
teardown) sudo "${OCSB_HOME}/lib/netlock-root.sh" teardown ;;
status)
if getent group "${OCSB_NETLOCK_GROUP}" >/dev/null; then
echo " group ${OCSB_NETLOCK_GROUP}: exists$(ocsb_netlock_member && echo ", ${USER} is a member")"
else
echo " group ${OCSB_NETLOCK_GROUP}: missing (opencode-sandbox netlock setup)"
fi
if systemctl is-enabled --quiet "${OCSB_NETLOCK_UNIT}" 2>/dev/null; then
echo " unit ${OCSB_NETLOCK_UNIT}: $(systemctl is-active "${OCSB_NETLOCK_UNIT}")"
else
echo " unit ${OCSB_NETLOCK_UNIT}: not installed (opencode-sandbox netlock setup)"
fi
;;
verify) ocsb_netlock_verify ;;
*) ocsb_die "netlock: unknown subcommand ${sub} (setup|teardown|status|verify)" ;;
esac
}