Abort on invalid allowlist lines; refuse launch dirs exposing $HOME or the allowlist
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-23 22:46:24 +11:00
parent edbe3faeb0
commit 71c1999e27
3 changed files with 54 additions and 2 deletions
+2
View File
@@ -15,6 +15,8 @@ agent-sandbox --exec bash # a shell in the sandbox instead of claude
- `$PWD` is mounted read-write at the same path. Files you create there are
owned by your host user. Nothing else from the host is mounted, and SELinux
labels on your files are never changed (`label=disable`).
- It refuses to launch from `$HOME`, any parent of it, or any directory
containing the allowlist.
- Claude's home is the `agent-sandbox-home` podman volume (login, config and
nested container images live here). All sessions share it.
- `podman` works inside the sandbox (nested rootless). The host podman socket is
+24 -2
View File
@@ -61,7 +61,9 @@ router_config() {
local -A ip=()
local hosts tcp="" forwards="0.0.0.0:443:$rip:443 0.0.0.0:80:$rip:80"
hosts=$(allowlist_entries "$allowlist" | awk '{print $1}' | uniq)
local entries
entries=$(allowlist_entries "$allowlist") || return
hosts=$(awk '{print $1}' <<<"$entries" | uniq)
for host in $hosts; do
[[ $host == .* ]] && continue
ip[$host]=127.1.$((i / 250)).$((i % 250 + 1))
@@ -72,7 +74,7 @@ router_config() {
tcp+=" server { listen $rip:$((PORT_BASE + j)); set \$up $host:$port; proxy_pass \$up; }"$'\n'
forwards+=" ${ip[$host]}:$port:$rip:$((PORT_BASE + j))"
j=$((j + 1))
done < <(allowlist_entries "$allowlist")
done <<<"$entries"
{
printf '%s\n' no-resolv no-hosts bind-interfaces "listen-address=$rip" "address=/#/"
@@ -137,6 +139,24 @@ EOF
echo "$forwards"
}
# True if path $1 is $2 or lies under it.
inside() {
[[ $1 == "$2" || $1 == "${2%/}"/* ]]
}
# Refuse launch dirs that would mount $HOME or the allowlist read-write.
check_launch_dir() {
local pwd home dir
pwd=$(realpath "$PWD")
home=$(realpath -m "$HOME")
inside "$home" "$pwd" && die "refusing to mount $pwd: it is or contains \$HOME"
for dir in "$CONFIG_DIR" "$(dirname "$ALLOWLIST")"; do
dir=$(realpath -m "$dir")
inside "$dir" "$pwd" && die "refusing to mount $pwd: it contains $dir"
done
return 0
}
main() {
local cmd=(claude "$@")
if [[ ${1:-} == --exec ]]; then
@@ -144,6 +164,7 @@ main() {
cmd=("$@")
fi
[[ $PWD == *:* ]] && die "cannot mount a directory containing ':'"
check_launch_dir
if [[ ! -f $ALLOWLIST ]]; then
local default
@@ -154,6 +175,7 @@ main() {
cp "$default" "$ALLOWLIST"
echo "agent-sandbox: created $ALLOWLIST from defaults" >&2
fi
allowlist_entries "$ALLOWLIST" >/dev/null || exit 1
local session router tmp subnet rip forwards
session=agent-sandbox-$(date +%s)-$$
+28
View File
@@ -70,4 +70,32 @@ done
check default-allowlist-parses 0 "$( (allowlist_entries allowed-hosts >/dev/null) 2>&1; echo $?)"
f=$(list 'ok.example.com' 'bad host' 'z.example.com')
check router-config-fails-on-bad-line 1 "$( (router_config "$f" "$(mktemp -d)" 10.89.7.2) >/dev/null 2>&1; echo $?)"
# Launch through main with a stub podman that logs its calls.
work=$(mktemp -d)
mkdir -p "$work/bin" "$work/home/proj" "$work/other"
printf '%s\n' '#!/bin/sh' "echo \"\$*\" >>$work/podman.log" \
'case "$*" in "network inspect"*) echo 10.89.7.0/24 ;; esac' >"$work/bin/podman"
chmod +x "$work/bin/podman"
launch() {
local dir=$1
shift
rm -f "$work/podman.log"
(cd "$dir" && env PATH="$work/bin:$PATH" HOME="$work/home" XDG_RUNTIME_DIR="$work" "$@" \
"$OLDPWD/agent-sandbox" --exec true) >/dev/null 2>&1
echo "$? $([[ -s $work/podman.log ]] && echo podman || echo none)"
}
good=$(list 'api.anthropic.com')
check launch-from-home-subdir "0 podman" "$(launch "$work/home/proj" AGENT_SANDBOX_ALLOWLIST="$good")"
check launch-refuses-bad-line "1 none" "$(launch "$work/home/proj" AGENT_SANDBOX_ALLOWLIST="$(list 'ok.example.com' 'bad host')")"
check launch-refuses-home "1 none" "$(launch "$work/home" AGENT_SANDBOX_ALLOWLIST="$good")"
check launch-refuses-home-ancestor "1 none" "$(launch "$work" AGENT_SANDBOX_ALLOWLIST="$good")"
check launch-refuses-root "1 none" "$(launch / AGENT_SANDBOX_ALLOWLIST="$good")"
ln -s "$work/home" "$work/homelink"
check launch-refuses-home-symlink "1 none" "$(launch "$work/homelink" AGENT_SANDBOX_ALLOWLIST="$good")"
check launch-refuses-config-parent "1 none" "$(launch "$work/other" XDG_CONFIG_HOME="$work/other/cfg" AGENT_SANDBOX_ALLOWLIST="$good")"
check launch-refuses-allowlist-dir "1 none" "$(launch "$work/home/proj" AGENT_SANDBOX_ALLOWLIST="$work/home/proj/hosts")"
((fails == 0)) || { echo "$fails failed"; exit 1; }