This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new d1c8797  claude-iso: auto-allow current repo in sandbox on every 
launch (#249)
d1c8797 is described below

commit d1c87970bb61c0f0ee37102b90e58e4a347d2f3a
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat May 23 01:58:29 2026 +0200

    claude-iso: auto-allow current repo in sandbox on every launch (#249)
    
    Whenever claude-iso is launched from inside a git working tree,
    resolve `git rev-parse --show-toplevel` and prepend it to
    sandbox.filesystem.allowRead via a one-shot `--settings` JSON.
    This closes the visibility gap for the wrapper-launch path:
    without it, the agent can't read the source tree the user just
    `cd`'d into unless the path was hand-listed in
    `<repo>/.claude/settings.local.json` ahead of time (the fix the
    project-root coverage docs describe for plain `claude`).
    
    The previous `-w`/`--worktree` injection is preserved as an
    additive layer — when `-w` is on the argv and `$PWD` is a
    worktree, the main repo is also added; in the main repo, the
    toplevel and the main repo dedupe to a single entry. Both
    paths ride into the session via one `--settings` injection.
    
    Outside a git repo, this is a silent no-op. The stderr banner
    now reports the full list of paths added.
    
    The setup guide gains a short subsection covering both the new
    current-repo auto-allow and the worktree mode (the latter
    landed in #157 without a docs cross-ref).
    
    Generated-by: Claude Code (Claude Opus 4.7)
---
 docs/setup/secure-agent-setup.md    |  36 ++++++++++++
 tools/agent-isolation/claude-iso.sh | 106 ++++++++++++++++++++++++++----------
 2 files changed, 113 insertions(+), 29 deletions(-)

diff --git a/docs/setup/secure-agent-setup.md b/docs/setup/secure-agent-setup.md
index ef60f96..c202d66 100644
--- a/docs/setup/secure-agent-setup.md
+++ b/docs/setup/secure-agent-setup.md
@@ -19,6 +19,7 @@
     - [When the helper runs](#when-the-helper-runs)
     - [Per-project vs whole-user scope](#per-project-vs-whole-user-scope)
   - [The clean-env wrapper](#the-clean-env-wrapper)
+    - [Automatic sandbox allow-paths](#automatic-sandbox-allow-paths)
   - [Sandbox-bypass visibility hook](#sandbox-bypass-visibility-hook)
     - [Why install it user-scope, not 
project-scope](#why-install-it-user-scope-not-project-scope)
     - [Install (user-scope)](#install-user-scope)
@@ -776,6 +777,41 @@ CLAUDE_ISO_ALLOW="GH_TOKEN" GH_TOKEN="$(op read 
'op://Personal/GitHub/token')" c
 The `CLAUDE_ISO_ALLOW` mechanism is opt-in per invocation — no
 implicit propagation, no persistent allowlist.
 
+### Automatic sandbox allow-paths
+
+Beyond the env-stripping role, `claude-iso` also injects up to two
+absolute paths into the session's `sandbox.filesystem.allowRead`
+via a one-shot `claude --settings <json>` flag prepended to the
+argv. The injection merges with the loaded settings stack at
+startup, *before* sandbox initialisation, so the paths take
+effect for that session immediately — no on-disk
+`settings.local.json` edit, no per-checkout bootstrap, nothing
+to clean up afterwards. A stderr banner reports what was added.
+
+**Current-repo auto-allow (always on).** Whenever `claude-iso` is
+launched from inside a git working tree, the working-tree root
+(resolved via `git rev-parse --show-toplevel`) is added to
+`allowRead`. This closes the visibility gap described in
+[Project-root coverage in the sandbox 
allowlists](#project-root-coverage-in-the-sandbox-allowlists)
+for the wrapper-launch path: when launched through `claude-iso`,
+you do not also need the project root hand-listed in
+`<repo>/.claude/settings.local.json` for the agent to be able to
+read the source tree. (The settings.local.json fix remains the
+right answer for plain `claude` launches — the harness can't
+see the wrapper's argv.) Outside a git repo, this is a silent
+no-op.
+
+**Worktree mode (`claude-iso -w` / `claude-iso --worktree`).**
+Additive on top of the current-repo auto-allow. When `-w` is on
+the argv and `$PWD` is a worktree, the *main* repo (resolved via
+`git rev-parse --git-common-dir`) is also added — that path is
+otherwise unreachable from a worktree session, because the
+sandbox's relative `.` rule covers only the worktree itself.
+Run inside the main repo, `-w` is effectively a no-op: the
+working-tree root and the main repo resolve to the same path
+and dedupe into a single `allowRead` entry. Both paths ride
+into the session via a single `--settings` injection.
+
 ## Sandbox-bypass visibility hook
 
 The Bash tool accepts a `dangerouslyDisableSandbox: true` flag that
diff --git a/tools/agent-isolation/claude-iso.sh 
b/tools/agent-isolation/claude-iso.sh
index a663237..a726998 100755
--- a/tools/agent-isolation/claude-iso.sh
+++ b/tools/agent-isolation/claude-iso.sh
@@ -40,17 +40,29 @@
 #   GH_TOKEN="$(gh auth token)" claude-iso
 #   AWS_PROFILE=read-only claude-iso
 #
+# Current-repo auto-allow:
+#   Whenever the wrapper is invoked from inside a git working
+#   tree, claude-iso automatically grants the session's sandbox
+#   read access to that working tree's root (resolved via
+#   `git rev-parse --show-toplevel`). Without this, the agent
+#   can't read the source the user just `cd`'d into unless the
+#   repo path was hand-listed in `.claude/settings.json` ahead of
+#   time. Outside a git repo it's a silent no-op. The path is
+#   injected via a one-shot `--settings` merge — nothing on disk
+#   changes — and a stderr banner reports what was added.
+#
 # Worktree mode (`claude-iso -w` / `claude-iso --worktree`):
-#   When `-w` / `--worktree` is present in the args AND the wrapper
-#   is invoked from inside a git repo, claude-iso automatically
-#   grants the new worktree session's sandbox read access to the
-#   *main* repo (resolved via `git rev-parse --git-common-dir`, so
-#   it works whether you launch from the main checkout or from a
-#   nested worktree). The wrapper prepends a one-shot
-#   `--settings '{"sandbox":{"filesystem":{"allowRead":["<main-repo>"]}}}'`
-#   to the `claude` argv — Claude merges this into the loaded
-#   settings stack at startup, before the sandbox is initialised.
-#   A stderr banner reports what was added. Nothing on disk changes.
+#   Additive on top of the current-repo auto-allow above. When
+#   `-w` / `--worktree` is present in the args AND the wrapper is
+#   invoked from inside a git repo, claude-iso also grants read
+#   access to the *main* repo (resolved via
+#   `git rev-parse --git-common-dir`, so it works whether you
+#   launch from the main checkout or from a nested worktree).
+#   When run in the main repo, the toplevel and the main repo
+#   resolve to the same path and are deduped. Both paths ride
+#   into the session via a single `--settings` injection that
+#   Claude merges into the loaded settings stack at startup,
+#   before the sandbox is initialised.
 
 claude_iso_main() {
   # Resolve the claude binary on PATH before clobbering the env so
@@ -142,13 +154,25 @@ claude_iso_main() {
   # without a shadow. The conservative read: include these only when
   # the user named them in CLAUDE_ISO_ALLOW.)
 
-  # `-w` / `--worktree`: auto-add the main repo to the new worktree
-  # session's sandbox allowRead. See the "Worktree mode" section in
-  # the file header for the full rationale. The injection uses
-  # `claude --settings <json>`, which merges with the loaded settings
-  # stack at startup (i.e. before sandbox init), so the added path is
-  # in scope for the worktree session immediately — no on-disk
-  # settings.json edit is performed.
+  # Sandbox auto-allow injection. See the "Current-repo auto-allow"
+  # and "Worktree mode" sections in the file header for the full
+  # rationale. The injection uses `claude --settings <json>`, which
+  # merges with the loaded settings stack at startup (i.e. before
+  # sandbox init), so the added paths are in scope for the session
+  # immediately — no on-disk settings.json edit is performed.
+  #
+  # We collect up to two candidate paths:
+  #   - cwd_toplevel: the working tree root of $PWD (always when
+  #     inside a git repo). Lets Claude read the source the user
+  #     just `cd`'d into.
+  #   - main_repo:    the parent of the main repo's .git dir; added
+  #     only when `-w`/`--worktree` is on the argv, so worktree
+  #     sessions can see the original checkout.
+  # When both resolve to the same path (no worktree, or `-w` from
+  # the main repo) they collapse to a single entry.
+  local cwd_toplevel
+  cwd_toplevel="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
+
   local has_worktree=0
   local arg
   for arg in "$@"; do
@@ -157,8 +181,9 @@ claude_iso_main() {
     esac
   done
 
+  local main_repo=""
   if [[ "$has_worktree" -eq 1 ]]; then
-    local common_dir main_repo
+    local common_dir
     common_dir="$(git -C "$PWD" rev-parse --git-common-dir 2>/dev/null || 
true)"
     if [[ -n "$common_dir" ]]; then
       case "$common_dir" in
@@ -166,18 +191,41 @@ claude_iso_main() {
         *) common_dir="$PWD/$common_dir" ;;
       esac
       main_repo="$(cd "$(dirname "$common_dir")" 2>/dev/null && pwd)"
-      if [[ -n "$main_repo" ]]; then
-        # Escape backslashes and double quotes so a pathological
-        # repo path can't break out of the JSON string literal.
-        local escaped="${main_repo//\\/\\\\}"
-        escaped="${escaped//\"/\\\"}"
-        set -- --settings 
"{\"sandbox\":{\"filesystem\":{\"allowRead\":[\"${escaped}\"]}}}" "$@"
-        if [[ -t 2 ]]; then
-          printf '\033[2m[claude-iso] -w detected; added main repo "%s" to 
worktree sandbox allowRead\033[0m\n' "$main_repo" >&2
-        else
-          printf '[claude-iso] -w detected; added main repo "%s" to worktree 
sandbox allowRead\n' "$main_repo" >&2
-        fi
+    fi
+  fi
+
+  local -a allow_read_paths=()
+  local candidate existing seen
+  for candidate in "$cwd_toplevel" "$main_repo"; do
+    [[ -z "$candidate" ]] && continue
+    seen=0
+    for existing in "${allow_read_paths[@]}"; do
+      if [[ "$existing" == "$candidate" ]]; then
+        seen=1
+        break
       fi
+    done
+    [[ "$seen" -eq 0 ]] && allow_read_paths+=("$candidate")
+  done
+
+  if (( ${#allow_read_paths[@]} > 0 )); then
+    # Hand-roll the JSON array literal (escape backslashes and
+    # double quotes) so a pathological repo path can't break out
+    # of the string literal. Keeping it dependency-free — no jq.
+    local json_array="" banner_paths="" sep=""
+    local p escaped
+    for p in "${allow_read_paths[@]}"; do
+      escaped="${p//\\/\\\\}"
+      escaped="${escaped//\"/\\\"}"
+      json_array+="${sep}\"${escaped}\""
+      banner_paths+="${sep}\"${p}\""
+      sep=","
+    done
+    set -- --settings 
"{\"sandbox\":{\"filesystem\":{\"allowRead\":[${json_array}]}}}" "$@"
+    if [[ -t 2 ]]; then
+      printf '\033[2m[claude-iso] added to sandbox allowRead: %s\033[0m\n' 
"$banner_paths" >&2
+    else
+      printf '[claude-iso] added to sandbox allowRead: %s\n' "$banner_paths" 
>&2
     fi
   fi
 

Reply via email to