Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
for installs that do not carry the framework source (see `isolated.py`).
"""

FRAMEWORK_FINGERPRINT = "sha256:198dc3e80e1af151"
FRAMEWORK_FINGERPRINT = "sha256:802fecf15e0df3ce"
34 changes: 34 additions & 0 deletions tools/agent-isolation/container-gateway-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ done

log() { printf '%s: %s\n' "${0##*/}" "$*" >&2; }

# Every git call: never discover a bare repository, which sandboxed code could
# plant as HEAD / config / objects in a project that has no .git of its own.
git() { command git -c safe.bareRepository=explicit "$@"; }

payload="$(cat 2>/dev/null || true)"
cwd="$(printf '%s' "$payload" | jq -r '.cwd // empty' 2>/dev/null || true)"
[[ -n $cwd ]] || cwd="$PWD"
Expand All @@ -132,6 +136,36 @@ if [[ $cwd != "$root" && $cwd != "$root"/* ]]; then
log "git reports $root as the toplevel of $cwd, which it does not contain; not starting the container gateway"
exit 0
fi
# The root as the filesystem shows it, read without any repository config: the
# nearest directory above the cwd holding a .git. git's toplevel must be that
# directory, and core.worktree must be unset: a .git directory planted in a
# sandbox-writable checkout could otherwise name a parent as the toplevel, which
# still contains the cwd but keys the worktree records differently, so an
# existing record would never be consulted.
fs_root=""
probe_dir="$cwd"
while :; do
if [[ -e $probe_dir/.git || -L $probe_dir/.git ]]; then
fs_root="$probe_dir"
break
fi
[[ $probe_dir == / ]] && break
probe_dir="$(dirname -- "$probe_dir")"
done
git_toplevel="$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -z $fs_root && -n $git_toplevel ]]; then
log "git finds a repository for $cwd but there is no .git on the way up; not starting the container gateway"
exit 0
fi
if [[ -n $fs_root && $fs_root != "$root" ]]; then
log "git reports $root as the toplevel of $cwd, but the checkout is $fs_root; not starting the container gateway"
exit 0
fi
# Checked whether or not a .git was found: core.worktree is what moves a toplevel.
if [[ -n "$(git -C "$cwd" config --get core.worktree 2>/dev/null)" ]]; then
log "core.worktree is set for $cwd; not starting the container gateway"
exit 0
fi

# The home directory from the account database (`~user` reads it, not $HOME),
# so a $HOME redirected by the environment cannot hide the real ~/.cache,
Expand Down
39 changes: 39 additions & 0 deletions tools/agent-isolation/tests/test_container_gateway_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,3 +782,42 @@ def test_a_writable_record_directory_refuses_even_a_plain_checkout(tmp_path: Pat
lay.settings({"sandbox": {"filesystem": {"allowWrite": [str(lay.home / ".claude" / "magpie-gateway")]}}})
done = run("start", lay.project, home=lay.home)
assert done.stdout == "" and "is sandbox-writable" in done.stderr



def test_a_planted_core_worktree_cannot_move_the_record_key(tmp_path: Path) -> None:
"""A fake .git naming a parent as the toplevel still contains the cwd, but would
key the worktree records differently and skip the existing one."""
lay = Layout(tmp_path)
worktree = _linked_worktree(lay)
run("start", worktree, home=lay.home)
# A plain .git directory (so the checkout no longer looks linked) whose config
# names the parent as the work tree: git's toplevel then contains the cwd.
(worktree / ".git").unlink()
subprocess.run(["git", "init", "-q", str(worktree)], check=True)
subprocess.run(["git", "-C", str(worktree), "config", "core.worktree", str(lay.base)], check=True)
done = run("stop", worktree, home=lay.home)
assert done.stdout == ""
assert "but the checkout is" in done.stderr


def test_core_worktree_on_a_plain_checkout_refuses(tmp_path: Path) -> None:
lay = Layout(tmp_path)
subprocess.run(["git", "-C", str(lay.project), "config", "core.worktree", str(lay.project)], check=True)
done = run("start", lay.project, home=lay.home)
assert done.stdout == "" and "core.worktree is set" in done.stderr


def test_a_planted_bare_repository_is_not_discovered(tmp_path: Path) -> None:
"""In a project with no .git, HEAD / config / objects planted in the cwd would make
git treat it as a bare repository whose core.worktree names a parent."""
lay = Layout(tmp_path)
plain = lay.base / "plain"
plain.mkdir()
subprocess.run(["git", "init", "-q", "--bare", str(plain)], check=True)
subprocess.run(["git", "--git-dir", str(plain), "config", "core.worktree", str(lay.base)], check=True)
subprocess.run(["git", "--git-dir", str(plain), "config", "core.bare", "false"], check=True)
done = run("start", plain, home=lay.home)
# Either refused, or served for the directory itself: never for the parent.
assert done.stdout == "" or f"--project={plain.resolve()} " in done.stdout
assert f"--project={lay.base} " not in done.stdout
Loading