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
76 lines
3.4 KiB
Bash
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
|
|
}
|