#!/usr/bin/env bash
set -euo pipefail

# Sandbox cursor-agent with bubblewrap (namespaces + bind mounts).
# Policy lives in this script — no external YAML.
#
# /usr/bin is not mounted whole. Only names in ALLOWED_BINS are visible
# (missing names on the host are skipped). Add more for one run with
# CURSOR_AGENT_SANDBOX_BINS="python3 make" (space-separated, no paths).
# This is not a strict exec whitelist: /usr/lib (including git helpers),
# /opt/cursor-agent, and the project directory stay reachable.
#
# cursor-agent is started as /opt/cursor-agent/cursor-agent. The host
# symlink /usr/bin/cursor-agent is not mounted, so a PATH lookup fails.
#
# Other environment variables:
#   PROJECT_DIR                  directory bound read-write (default: $PWD)
#   CURSOR_AGENT_REAL_HOME       login home used to seed auth and state
#   CURSOR_AGENT_SANDBOX_STATE   host state dir (default: ~/.local/state/cursor-agent-sandbox)
#   CURSOR_AGENT_SANDBOX_HOME    HOME inside the sandbox (default: /home/agent)
#   CURSOR_AGENT_SANDBOX_BINDS   extra host paths to bind, space-separated

if ! command -v bwrap >/dev/null 2>&1; then
  echo "cursor-agent-sandboxed: bwrap (bubblewrap) not found" >&2
  exit 127
fi

PROJECT_DIR="$(cd "${PROJECT_DIR:-$PWD}" && pwd)"

# Always resolve the real login home — never reuse an already-isolated HOME/XDG_*.
REAL_HOME="${CURSOR_AGENT_REAL_HOME:-$(getent passwd "$(id -un)" | cut -d: -f6)}"
if [[ -z "$REAL_HOME" || ! -d "$REAL_HOME" ]]; then
  REAL_HOME="$(getent passwd "$(id -u)" | cut -d: -f6)"
fi

STATE_DIR="${CURSOR_AGENT_SANDBOX_STATE:-$REAL_HOME/.local/state/cursor-agent-sandbox}"
HOST_HOME="$STATE_DIR/home"
# Path of HOME *inside* the sandbox (real /home/<user> is not mounted).
SB_HOME="${CURSOR_AGENT_SANDBOX_HOME:-/home/agent}"

mkdir -p \
  "$HOST_HOME/.cursor" \
  "$HOST_HOME/.config/cursor" \
  "$HOST_HOME/.cache/cursor-compile-cache" \
  "$HOST_HOME/.local/share/cursor-agent" \
  "$REAL_HOME/.cursor/chats" \
  "$REAL_HOME/.cursor/projects" \
  "$PROJECT_DIR"

# Seed only what cursor-agent needs to authenticate / configure.
if [[ -f "$REAL_HOME/.config/cursor/auth.json" ]]; then
  cp -f "$REAL_HOME/.config/cursor/auth.json" "$HOST_HOME/.config/cursor/auth.json"
fi
if [[ -f "$REAL_HOME/.cursor/cli-config.json" ]]; then
  cp -f "$REAL_HOME/.cursor/cli-config.json" "$HOST_HOME/.cursor/cli-config.json"
fi
if [[ -f "$REAL_HOME/.cursor/agent-cli-state.json" ]]; then
  cp -f "$REAL_HOME/.cursor/agent-cli-state.json" "$HOST_HOME/.cursor/agent-cli-state.json"
fi
if [[ -f "$REAL_HOME/.gitconfig" ]]; then
  cp -f "$REAL_HOME/.gitconfig" "$HOST_HOME/.gitconfig"
fi

# Optional extra bind mounts (host paths, space-separated), e.g. another repo.
EXTRA_BINDS=()
if [[ -n "${CURSOR_AGENT_SANDBOX_BINDS:-}" ]]; then
  # shellcheck disable=SC2206
  for path in ${CURSOR_AGENT_SANDBOX_BINDS}; do
    path="$(cd "$path" && pwd)"
    EXTRA_BINDS+=(--bind "$path" "$path")
  done
fi

# Only these executables from /usr/bin are visible inside the sandbox.
# Extra ones can be added via CURSOR_AGENT_SANDBOX_BINS (space-separated names).
ALLOWED_BINS=(
  bash sh env
  # /opt/cursor-agent/node → /usr/bin/node (symlink); agent also shells out to rg.
  node rg
  git diff patch
  ls cat head tail less
  grep sed awk find xargs sort uniq wc tr cut tee
  mkdir rmdir rm cp mv touch chmod ln readlink realpath dirname basename
  pwd date uname id whoami which test true false sleep
)
if [[ -n "${CURSOR_AGENT_SANDBOX_BINS:-}" ]]; then
  # shellcheck disable=SC2206
  ALLOWED_BINS+=(${CURSOR_AGENT_SANDBOX_BINS})
fi
BIN_BINDS=(--dir /usr/bin)
for bin in "${ALLOWED_BINS[@]}"; do
  if [[ -e "/usr/bin/$bin" ]]; then
    BIN_BINDS+=(--ro-bind "/usr/bin/$bin" "/usr/bin/$bin")
  fi
done

# Always continue the last session unless the caller already chose a session,
# runs a subcommand, or uses a non-interactive / meta option.
# --resume takes an optional chat id, so it would swallow a following prompt.
AGENT_ARGS=("$@")
add_continue=1
case "${1:-}" in
  install-shell-integration|uninstall-shell-integration|login|logout|mcp|plugin|\
  worker|status|whoami|models|bedrock|about|update|create-chat|generate-rule|rule|\
  ls|resume|help)
    add_continue=0 ;;
esac
for arg in "$@"; do
  case "$arg" in
    --resume|--resume=*|--continue|-p|--print|-w|--worktree|--worktree=*|\
    --version|-v|--help|-h|--list-models)
      add_continue=0; break ;;
  esac
done
if [[ "$add_continue" -eq 1 ]]; then
  AGENT_ARGS=(--continue "$@")
fi

exec bwrap \
  --die-with-parent \
  --unshare-pid \
  --unshare-ipc \
  --unshare-uts \
  --proc /proc \
  --dev /dev \
  --tmpfs /tmp \
  --tmpfs /home \
  --dir "$SB_HOME" \
  --bind "$HOST_HOME" "$SB_HOME" \
  --bind "$PROJECT_DIR" "$PROJECT_DIR" \
  --bind "$REAL_HOME/.cursor/chats" "$SB_HOME/.cursor/chats" \
  --bind "$REAL_HOME/.cursor/projects" "$SB_HOME/.cursor/projects" \
  "${EXTRA_BINDS[@]}" \
  "${BIN_BINDS[@]}" \
  --ro-bind /usr/lib /usr/lib \
  --ro-bind-try /usr/lib64 /usr/lib64 \
  --ro-bind-try /usr/lib/locale /usr/lib/locale \
  --ro-bind-try /usr/share/locale /usr/share/locale \
  --ro-bind-try /usr/share/ca-certificates /usr/share/ca-certificates \
  --ro-bind /opt/cursor-agent /opt/cursor-agent \
  --symlink usr/bin /bin \
  --symlink usr/lib /lib \
  --symlink usr/lib /lib64 \
  --ro-bind-try /etc/resolv.conf /etc/resolv.conf \
  --ro-bind-try /etc/hosts /etc/hosts \
  --ro-bind-try /etc/host.conf /etc/host.conf \
  --ro-bind-try /etc/nsswitch.conf /etc/nsswitch.conf \
  --ro-bind-try /etc/gai.conf /etc/gai.conf \
  --ro-bind-try /etc/ld.so.cache /etc/ld.so.cache \
  --ro-bind-try /etc/localtime /etc/localtime \
  --ro-bind-try /etc/ssl /etc/ssl \
  --ro-bind-try /etc/ca-certificates /etc/ca-certificates \
  --ro-bind-try /etc/passwd /etc/passwd \
  --ro-bind-try /etc/group /etc/group \
  --setenv HOME "$SB_HOME" \
  --setenv XDG_CONFIG_HOME "$SB_HOME/.config" \
  --setenv XDG_CACHE_HOME "$SB_HOME/.cache" \
  --setenv XDG_STATE_HOME "$SB_HOME/.local/state" \
  --setenv XDG_DATA_HOME "$SB_HOME/.local/share" \
  --setenv NODE_COMPILE_CACHE "$SB_HOME/.cache/cursor-compile-cache" \
  --chdir "$PROJECT_DIR" \
  -- /opt/cursor-agent/cursor-agent "${AGENT_ARGS[@]}"
