mirror of
https://github.com/bdbrown4/peltier.io.git
synced 2026-11-07 10:58:28 +00:00
agent: OS-level isolation for the agent tool boundary (SPEC §10)
harnessd gains --socket mode (Unix socket, trusted uid, mutating ops serialized); the harness client speaks socket when HOTPATH_HARNESS_SOCKET is set. scripts/agent-isolated.sh launches an attempt with the agent tree confined — mountns mode (repo bind-mounted read-only, CAP_SYS_ADMIN/CAP_SYS_PTRACE dropped; no credential copying) or user mode (unprivileged hpagent, bench-metal shape). scripts/isolation-check.sh + just isolation-check verify the boundary from the agent's side: 19 checks, both modes, all passing — repo writes, forged ledger INSERTs, trust-binary replacement, rw remounts, and userns re-entry all refused; the harnessd socket door works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2
This commit is contained in:
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
# Launch one unattended optimization attempt behind an OS-level boundary
|
||||
# (SPEC §10). Run as the trusted uid (root in this container):
|
||||
#
|
||||
# scripts/agent-isolated.sh <target> <run-id> [max-turns]
|
||||
#
|
||||
# Two isolation modes (HOTPATH_ISOLATION, default "mountns"):
|
||||
#
|
||||
# mountns — this container's boundary. The agent process tree (Claude
|
||||
# CLI + MCP server + harness client) runs in a private mount
|
||||
# namespace where the repo is bind-mounted READ-ONLY, with
|
||||
# CAP_SYS_ADMIN and CAP_SYS_PTRACE removed from the bounding set so
|
||||
# the mount cannot be undone (verified: remount and userns re-entry
|
||||
# both refused). Repo writes fail with EROFS no matter the uid; the
|
||||
# CLI's own auth state (under $HOME) stays untouched. No credential
|
||||
# copying.
|
||||
#
|
||||
# user — the bench-metal shape (docs/infra.md). The agent loop runs as
|
||||
# the unprivileged user 'hpagent' whose only door into the repo is
|
||||
# the harnessd socket. Requires the agent user to have its own API
|
||||
# credentials (CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in its
|
||||
# environment) — a parent session's root-owned auth is deliberately
|
||||
# NOT copied across the boundary.
|
||||
#
|
||||
# In both modes harnessd + the verdict pipeline run OUTSIDE the boundary
|
||||
# as the trusted uid and own every write into the repo (git apply,
|
||||
# ledger rows, pending/ evidence). Verify the boundary any time with
|
||||
# scripts/isolation-check.sh.
|
||||
set -eu
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
TARGET=${1:?usage: agent-isolated.sh <target> <run-id> [max-turns]}
|
||||
RUN_ID=${2:?usage: agent-isolated.sh <target> <run-id> [max-turns]}
|
||||
MAX_TURNS=${3:-40}
|
||||
MODE=${HOTPATH_ISOLATION:-mountns}
|
||||
|
||||
AGENT_USER=hpagent
|
||||
SOCK_DIR=/run/hotpath
|
||||
SOCK=$SOCK_DIR/harness.sock
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { echo "must run as the trusted uid (root)" >&2; exit 1; }
|
||||
|
||||
# --- no world-writable holes in the repo ------------------------------------
|
||||
# In user mode the boundary is filesystem permissions; a single o+w path
|
||||
# would void it. mountns mode doesn't need this, but a hole is still a
|
||||
# smell worth failing on.
|
||||
HOLES=$(find "$ROOT" -perm -o+w \( -type f -o -type d \) 2>/dev/null || true)
|
||||
if [ -n "$HOLES" ]; then
|
||||
echo "refusing to launch: world-writable paths inside the repo:" >&2
|
||||
echo "$HOLES" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- trust-layer daemon (outside the boundary) --------------------------------
|
||||
( cd "$ROOT" && cargo build -q -p harnessd -p verdict )
|
||||
|
||||
mkdir -p "$SOCK_DIR"
|
||||
if [ ! -S "$SOCK" ]; then
|
||||
( cd "$ROOT" && setsid ./target/debug/harnessd --socket "$SOCK" \
|
||||
>>/var/log/hotpath-harnessd.log 2>&1 & )
|
||||
for _ in $(seq 1 50); do [ -S "$SOCK" ] && break; sleep 0.1; done
|
||||
[ -S "$SOCK" ] || { echo "harnessd socket never appeared" >&2; exit 1; }
|
||||
fi
|
||||
|
||||
run_loop="python3 -m hotpath_agent.loop $TARGET --run-id $RUN_ID --max-turns $MAX_TURNS --repo-root $ROOT"
|
||||
|
||||
case "$MODE" in
|
||||
mountns)
|
||||
chmod 600 "$SOCK" # uid 0 only; the confined tree is still uid 0
|
||||
exec unshare -m sh -c "
|
||||
mount --bind '$ROOT' '$ROOT' &&
|
||||
mount -o remount,bind,ro '$ROOT' &&
|
||||
exec setpriv --bounding-set -sys_admin,-sys_ptrace --inh-caps -all \
|
||||
env HOTPATH_HARNESS_SOCKET='$SOCK' \
|
||||
HOTPATH_REPO_ROOT='$ROOT' \
|
||||
PYTHONPATH='$ROOT/agent' \
|
||||
$run_loop"
|
||||
;;
|
||||
user)
|
||||
id "$AGENT_USER" >/dev/null 2>&1 || useradd -m -s /bin/sh "$AGENT_USER"
|
||||
HP_HOME=$(getent passwd "$AGENT_USER" | cut -d: -f6)
|
||||
chown root:"$AGENT_USER" "$SOCK"
|
||||
chmod 660 "$SOCK"
|
||||
# The container's TLS-intercepting proxy CA lives under /root (mode
|
||||
# 700); the agent user needs a readable copy to reach the API.
|
||||
CA_SRC=${HOTPATH_CA_SRC:-/root/.ccr/ca-bundle.crt}
|
||||
CA=/etc/hotpath-ca.crt
|
||||
[ -f "$CA_SRC" ] && install -m 644 "$CA_SRC" "$CA"
|
||||
# setpriv preserves the environment (proxy/API routing vars); we
|
||||
# override HOME and every CA var that pointed under /root. The agent
|
||||
# user must bring its own API credentials — see header.
|
||||
exec setpriv --reuid "$AGENT_USER" --regid "$AGENT_USER" --init-groups \
|
||||
env HOME="$HP_HOME" USER="$AGENT_USER" LOGNAME="$AGENT_USER" \
|
||||
HOTPATH_HARNESS_SOCKET="$SOCK" \
|
||||
HOTPATH_REPO_ROOT="$ROOT" \
|
||||
PYTHONPATH="$ROOT/agent" \
|
||||
SSL_CERT_FILE="$CA" NODE_EXTRA_CA_CERTS="$CA" \
|
||||
CURL_CA_BUNDLE="$CA" REQUESTS_CA_BUNDLE="$CA" \
|
||||
GIT_SSL_CAINFO="$CA" AWS_CA_BUNDLE="$CA" \
|
||||
NIX_SSL_CERT_FILE="$CA" GRPC_DEFAULT_SSL_ROOTS_FILE_PATH="$CA" \
|
||||
$run_loop
|
||||
;;
|
||||
*)
|
||||
echo "unknown HOTPATH_ISOLATION mode: $MODE (mountns|user)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
#!/bin/sh
|
||||
# Verify the SPEC §10 OS boundary from the agent's side of it. Run as the
|
||||
# trusted uid (root). Checks BOTH isolation modes of agent-isolated.sh:
|
||||
# every write the agent could use to game the harness must fail; the
|
||||
# harnessd socket door must work. Exits nonzero on any hole.
|
||||
set -u
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
AGENT_USER=hpagent
|
||||
SOCK=${HOTPATH_HARNESS_SOCKET:-/run/hotpath/harness.sock}
|
||||
FAIL=0
|
||||
|
||||
note_fail() { echo "FAIL (boundary hole): $1"; FAIL=1; }
|
||||
note_ok() { echo "ok: $1"; }
|
||||
|
||||
[ -S "$SOCK" ] || { echo "FAIL: harnessd socket $SOCK missing (run agent-isolated.sh or start the daemon)"; exit 1; }
|
||||
|
||||
# Probe scripts as files: they run nested inside sh -c layers where
|
||||
# inline `python3 -c` quoting is unmanageable.
|
||||
PROBE_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$PROBE_DIR"' EXIT
|
||||
chmod 755 "$PROBE_DIR"
|
||||
|
||||
cat > "$PROBE_DIR/sock_check.py" <<EOF
|
||||
import json, socket, sys
|
||||
s = socket.socket(socket.AF_UNIX); s.settimeout(15); s.connect('$SOCK')
|
||||
f = s.makefile('rw')
|
||||
f.write(json.dumps({'op': 'read_ledger', 'target': 'comrak'}) + '\n'); f.flush()
|
||||
resp = json.loads(f.readline())
|
||||
assert resp['ok'], resp
|
||||
EOF
|
||||
|
||||
cat > "$PROBE_DIR/escape_check.py" <<EOF
|
||||
import json, socket
|
||||
s = socket.socket(socket.AF_UNIX); s.settimeout(15); s.connect('$SOCK')
|
||||
f = s.makefile('rw')
|
||||
diff = '--- a/../../../config/accept.toml\n+++ b/../../../config/accept.toml\n@@ -1 +1 @@\n-x\n+y\n'
|
||||
f.write(json.dumps({'op': 'propose_patch', 'target': 'comrak', 'diff': diff, 'hypothesis': 'probe'}) + '\n'); f.flush()
|
||||
resp = json.loads(f.readline())
|
||||
assert not resp['ok'] and 'escape' in resp['error'], resp
|
||||
EOF
|
||||
|
||||
cat > "$PROBE_DIR/ledger_insert.py" <<EOF
|
||||
import sqlite3
|
||||
c = sqlite3.connect('$ROOT/results/ledger.sqlite')
|
||||
c.execute("INSERT INTO attempts (run_id,timestamp,target,target_commit,phase,hotspot,playbook_class,hypothesis,patch,gates,bench,verdict,tokens_spent,wall_time_s) VALUES ('forged','','t','','','',1,'','','{}','{}','accepted',0,0)")
|
||||
c.commit()
|
||||
EOF
|
||||
chmod 644 "$PROBE_DIR"/*.py
|
||||
|
||||
# ============================ mountns mode ====================================
|
||||
echo "--- mountns mode (confined uid-0 tree, repo read-only) ---"
|
||||
MNT_PREFIX="unshare -m"
|
||||
CONFINE="mount --bind '$ROOT' '$ROOT' && mount -o remount,bind,ro '$ROOT' && exec setpriv --bounding-set -sys_admin,-sys_ptrace --inh-caps -all"
|
||||
|
||||
mnt_must_fail() {
|
||||
desc=$1; cmd=$2
|
||||
if $MNT_PREFIX sh -c "$CONFINE sh -c \"$cmd\"" >/dev/null 2>&1; then
|
||||
note_fail "[mountns] $desc succeeded"
|
||||
else
|
||||
note_ok "[mountns] $desc refused"
|
||||
fi
|
||||
}
|
||||
mnt_must_pass() {
|
||||
desc=$1; cmd=$2
|
||||
if $MNT_PREFIX sh -c "$CONFINE sh -c \"$cmd\"" >/dev/null 2>&1; then
|
||||
note_ok "[mountns] $desc works"
|
||||
else
|
||||
note_fail "[mountns] $desc broken"
|
||||
fi
|
||||
}
|
||||
|
||||
mnt_must_fail "write into crates/ (trust layer)" "touch '$ROOT/crates/.probe'"
|
||||
mnt_must_fail "edit config/accept.toml (thresholds)" "echo x >> '$ROOT/config/accept.toml'"
|
||||
mnt_must_fail "write a target workspace directly" "touch '$ROOT/targets/comrak/workspace/.probe'"
|
||||
mnt_must_fail "INSERT a forged ledger row" "python3 $PROBE_DIR/ledger_insert.py"
|
||||
mnt_must_fail "overwrite results/ evidence" "echo x > '$ROOT/results/.probe'"
|
||||
mnt_must_fail "replace a trust binary" "echo x > '$ROOT/target/debug/verdict'"
|
||||
mnt_must_fail "remount the repo read-write" "mount -o remount,bind,rw '$ROOT'"
|
||||
mnt_must_fail "userns re-entry to undo the ro mount" "unshare -Ur -m mount -o remount,bind,rw '$ROOT'"
|
||||
mnt_must_pass "harnessd socket read_ledger" "python3 $PROBE_DIR/sock_check.py"
|
||||
|
||||
# ============================ user mode =======================================
|
||||
if id "$AGENT_USER" >/dev/null 2>&1; then
|
||||
echo "--- user mode (unprivileged $AGENT_USER) ---"
|
||||
HP_HOME=$(getent passwd "$AGENT_USER" | cut -d: -f6)
|
||||
as_agent() {
|
||||
setpriv --reuid "$AGENT_USER" --regid "$AGENT_USER" --init-groups \
|
||||
env HOME="$HP_HOME" "$@"
|
||||
}
|
||||
must_fail() {
|
||||
desc=$1; shift
|
||||
if as_agent "$@" >/dev/null 2>&1; then
|
||||
note_fail "[user] $desc succeeded as $AGENT_USER"
|
||||
else
|
||||
note_ok "[user] $desc refused"
|
||||
fi
|
||||
}
|
||||
must_pass() {
|
||||
desc=$1; shift
|
||||
if as_agent "$@" >/dev/null 2>&1; then
|
||||
note_ok "[user] $desc works"
|
||||
else
|
||||
note_fail "[user] $desc broken for $AGENT_USER"
|
||||
fi
|
||||
}
|
||||
# user mode needs group access on the socket for the positive checks
|
||||
SOCK_GRP_SAVED=$(stat -c '%U:%G %a' "$SOCK")
|
||||
chown root:"$AGENT_USER" "$SOCK"; chmod 660 "$SOCK"
|
||||
|
||||
must_fail "write into crates/ (trust layer)" sh -c "touch '$ROOT/crates/.probe'"
|
||||
must_fail "edit config/accept.toml (thresholds)" sh -c "echo x >> '$ROOT/config/accept.toml'"
|
||||
must_fail "write into corpora/ (pinned inputs)" sh -c "touch '$ROOT/corpora/.probe'"
|
||||
must_fail "write a target workspace directly" sh -c "touch '$ROOT/targets/comrak/workspace/.probe'"
|
||||
must_fail "INSERT a forged ledger row" python3 "$PROBE_DIR/ledger_insert.py"
|
||||
must_fail "overwrite results/ evidence" sh -c "echo x > '$ROOT/results/.probe'"
|
||||
must_fail "replace a trust binary" sh -c "echo x > '$ROOT/target/debug/verdict'"
|
||||
must_fail "run the verdict binary to write a row" sh -c "cd '$ROOT' && ./target/debug/verdict comrak --candidate-bin /bin/true --run-id forged-direct --playbook-class 1 --hypothesis x --hotspot x"
|
||||
must_pass "harnessd socket read_ledger" python3 "$PROBE_DIR/sock_check.py"
|
||||
must_pass "harnessd refuses ../ escape in a diff" python3 "$PROBE_DIR/escape_check.py"
|
||||
|
||||
chown "${SOCK_GRP_SAVED%% *}" "$SOCK"; chmod "${SOCK_GRP_SAVED##* }" "$SOCK"
|
||||
else
|
||||
echo "--- user mode skipped (no $AGENT_USER user) ---"
|
||||
fi
|
||||
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo "isolation-check: ALL CHECKS PASSED"
|
||||
else
|
||||
echo "isolation-check: BOUNDARY HOLES FOUND" >&2
|
||||
fi
|
||||
exit "$FAIL"
|
||||
Reference in New Issue
Block a user