From 75cebe37c3afd7d0c91b2805d51b0a434b60439b Mon Sep 17 00:00:00 2001 From: Luisa Lima Date: Fri, 12 Jun 2026 15:23:53 +0100 Subject: [PATCH 1/3] Add Antigravity CLI (agy) transition awareness to the Gemini adapter Google sunsets Gemini CLI for consumer tiers on 2026-06-18 in favor of the closed-source Antigravity CLI (agy), which reuses ~/.gemini/ but reads its own config under ~/.gemini/antigravity-cli/ and whose security settings are not yet documented. - New gemini.agy_transition audit finding: info when agy coexists with Gemini CLI, high when only agy is present (so a passing Gemini audit isn't mistaken for agy coverage) - Skip legacy Gemini checks on agy-only hosts with no Gemini CLI config (~/.gemini existing no longer implies Gemini CLI) - detect.sh reports agent_antigravity and a transition hint - Host alias/function bypass checks cover agy (--dangerously-skip-permissions) - apply gemini warns when agy is present; usage notes the sunset - KNOWN_ISSUES.md tracks the blocked agy adapter (schema undocumented; closed source rules out the gemini-cli source-reading approach) Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 6 ++++ KNOWN_ISSUES.md | 8 +++++ README.md | 2 ++ agents/gemini/audit.sh | 49 ++++++++++++++++++++++++++++ agents/host/audit.sh | 4 +-- scripts/detect.sh | 7 ++++ scripts/setup-gemini.sh | 15 +++++++++ tests/test_audit_gemini.sh | 67 +++++++++++++++++++++++++++++++++++++- tests/test_detect.sh | 11 +++++-- 9 files changed, 164 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e394b62..5658a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project should be documented in this file. +## Unreleased + +### Added + +- **Antigravity CLI (agy) transition awareness.** Google sunsets Gemini CLI for consumer tiers on 2026-06-18 in favor of the closed-source Antigravity CLI (`agy`), which reuses `~/.gemini/` but reads its own config under `~/.gemini/antigravity-cli/` and whose security settings are not yet documented. New `gemini.agy_transition` audit finding: info when agy is installed alongside Gemini CLI (the audit stays accurate for Gemini), high when only agy is present (a passing Gemini audit would otherwise read as false coverage). On agy-only hosts with no Gemini CLI config, the legacy Gemini checks are skipped instead of emitting misleading criticals about settings no installed tool reads. `detect` reports `agent_antigravity`; the host audit's alias/function bypass checks now cover `agy` (its documented bypass flag is `--dangerously-skip-permissions`); `apply gemini` warns when agy is present. A real agy adapter is blocked on Google documenting agy's settings schema — tracked in KNOWN_ISSUES.md. + ## 0.3.0 - 2026-05-13 ### Added diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 396ffc8..734ea18 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -11,6 +11,14 @@ limitation. Sandshell's audit checks reference these entries via their ## Active +### Gemini CLI → Antigravity CLI (agy) — successor's security settings undocumented + +- **Tracking:** [Google Developers Blog announcement](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/), [google-gemini/gemini-cli discussion #27274](https://github.com/google-gemini/gemini-cli/discussions/27274), migration doc at `antigravity.google/docs/gcli-migration` +- **Timeline:** Announced 2026-05-19. On **2026-06-18** Gemini CLI stops serving Google AI Pro, Ultra, and free Code Assist users. Gemini Code Assist Standard/Enterprise licenses and paid Gemini API keys keep Gemini CLI access, so the `gemini` adapter stays for enterprise/legacy use. +- **Impact:** Antigravity CLI (binary `agy`) is closed-source (Go). It reuses `~/.gemini/` but reads its own config nested under `~/.gemini/antigravity-cli/` (own `settings.json`; MCP servers split into `mcp_config.json` with `serverUrl` instead of `url`; workspace skills move to `.agents/skills/`). As of 2026-06, there is **no public documentation** for agy equivalents of the settings sandshell manages for Gemini CLI: `tools.sandbox`, `tools.sandboxNetworkAccess`, `security.folderTrust`, `security.disableYoloMode`, `security.disableAlwaysAllow`, `general.defaultApprovalMode`. Known security surface so far: a `--sandbox` launch flag ("terminal restrictions"), a `--dangerously-skip-permissions` bypass flag, and `agy doctor` / `agy inspect` diagnostics. Hooks reportedly carry over in Antigravity 2.0's JSON format. +- **What sandshell does today:** `detect` reports `agent_antigravity`; the Gemini audit emits `gemini.agy_transition` (info when both CLIs are installed, high when only agy is — so a passing Gemini audit isn't mistaken for agy coverage) and skips the legacy Gemini checks on agy-only hosts with no Gemini config; the host audit's alias/function bypass checks cover `agy` with `--dangerously-skip-permissions`; `apply gemini` keeps writing Gemini CLI settings and warns when agy is present. +- **Blocked:** a real agy adapter (`setup-agy.sh`, `agents/agy/audit.sh`) needs Google to document agy's settings schema. Because agy is closed-source, the verification approach used for Gemini CLI (reading `packages/cli/src/utils/sandbox.ts`) is unavailable — once a schema is published, claims will need empirical verification (probe writes/network from inside an `agy --sandbox` session). + ### Gemini CLI — Network restriction not enforced under sandbox-exec on macOS - **Tracking:** [google-gemini/gemini-cli#20381](https://github.com/google-gemini/gemini-cli/issues/20381) (proposal for the missing layer), [#20046](https://github.com/google-gemini/gemini-cli/issues/20046) (V1 macOS post-intent execution, includes "blocks network" in scope) diff --git a/README.md b/README.md index 0526964..0e0c088 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,8 @@ Per-agent adapters live in `agents//audit.sh`. Each reads the agent's real | `codex` | **Sandbox on, approvals required, no escape hatches.** `sandbox_mode != danger-full-access`, `approval_policy != never`, no broad `writable_roots`, no `trust_level = "trusted"` for `~`/`/`, no network in workspace mode. PreToolUse + PostToolUse hooks present, `[features] codex_hooks = true` set (catches the silent-disable trap where hooks.json is ignored). | | `gemini` | **Sandbox on, folder trust on, YOLO and always-allow off.** `tools.sandbox` configured, `sandboxNetworkAccess = false`, `security.folderTrust.enabled`, `security.disableYoloMode`, approval mode set, no wildcard entries in `trustedFolders.json`. | +> **Gemini CLI sunset:** Google retires Gemini CLI for consumer tiers on **2026-06-18** in favor of the closed-source [Antigravity CLI (`agy`)](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/). Enterprise Gemini Code Assist licenses keep access, so the `gemini` adapter stays. `agy`'s security settings are not yet documented — sandshell detects it (`agent_antigravity`, `gemini.agy_transition`) but cannot audit or configure it yet. See [KNOWN_ISSUES.md](KNOWN_ISSUES.md). + The credential check classifies each export by injection source: `$(op …)`, `$(aws-vault …)`, `$(vault …)`, `$(pass …)`, `$(gh auth token)`, `$(gcloud secrets …)`, `$(infisical …)`, and other recognized secrets-manager invocations stay silent; only literal values are flagged at medium. Adapters self-skip when their agent isn't installed. diff --git a/agents/gemini/audit.sh b/agents/gemini/audit.sh index a085716..d2f718d 100755 --- a/agents/gemini/audit.sh +++ b/agents/gemini/audit.sh @@ -28,10 +28,21 @@ is_gemini_present() { || [[ -d ".gemini" ]] } +# Antigravity CLI (agy) — Gemini CLI's closed-source successor (consumer-tier +# sunset 2026-06-18). agy reuses ~/.gemini/ but reads its own config under +# ~/.gemini/antigravity-cli/, so its presence alone no longer implies Gemini CLI. +is_agy_present() { + command -v agy >/dev/null 2>&1 \ + || [[ -d "$HOME/.gemini/antigravity-cli" ]] +} + if ! is_gemini_present; then exit 0 fi +GEMINI_BIN_PRESENT=false +command -v gemini >/dev/null 2>&1 && GEMINI_BIN_PRESENT=true + USER_CFG="$HOME/.gemini/settings.json" WORKSPACE_CFG=".gemini/settings.json" TRUSTED_FOLDERS="$HOME/.gemini/trustedFolders.json" @@ -248,6 +259,33 @@ check_guidance_present() { fi } +# ---------- gemini.agy_transition ---------- +# Antigravity CLI (agy) detected. agy's security settings (sandbox, approval +# modes) are undocumented and it ignores ~/.gemini/settings.json, so the +# checks in this adapter say nothing about agy sessions. Info when Gemini CLI +# is still installed (the audit remains accurate for it); high when only agy +# is present (a passing Gemini audit would be false coverage). +check_agy_transition() { + is_agy_present || return 0 + if [[ "$GEMINI_BIN_PRESENT" = true ]]; then + emit_finding \ + "gemini.agy_transition" \ + "info" \ + "Antigravity CLI (agy) detected alongside Gemini CLI — this audit covers Gemini CLI only" \ + "" \ + "For agy sessions, prefer --sandbox and avoid --dangerously-skip-permissions; sandshell has no agy adapter yet (see KNOWN_ISSUES.md)" \ + "Google sunsets Gemini CLI for consumer tiers on 2026-06-18 (enterprise Code Assist licenses keep access). agy reads its own config under ~/.gemini/antigravity-cli/ and its security settings are not yet documented." + else + emit_finding \ + "gemini.agy_transition" \ + "high" \ + "Antigravity CLI (agy) is installed but Gemini CLI is not — the settings audited here do not govern agy" \ + "" \ + "Treat agy as unaudited: prefer --sandbox, avoid --dangerously-skip-permissions; a sandshell agy adapter is blocked on Google documenting agy's settings schema (see KNOWN_ISSUES.md)" \ + "agy reuses ~/.gemini/ but reads its own config under ~/.gemini/antigravity-cli/. A passing Gemini audit gives no coverage for agy sessions." + fi +} + # ---------- gemini.sandbox.linux_invalid ---------- # tools.sandbox = "sandbox-exec" is a macOS-only value (Seatbelt). On Linux, # sandbox-exec doesn't exist as a binary; the Gemini CLI's spawn will fail at @@ -313,6 +351,17 @@ check_sandbox_linux_runtime_missing() { fi } +# agy-only host: ~/.gemini exists only because agy nests its config there. +# With no gemini binary and no Gemini CLI config to audit, the legacy checks +# would emit misleading criticals about settings no installed tool reads — +# report the transition finding and stop. +if is_agy_present && [[ "$GEMINI_BIN_PRESENT" = false ]] \ + && [[ ! -f "$USER_CFG" && ! -f "$WORKSPACE_CFG" && ! -f "$TRUSTED_FOLDERS" ]]; then + check_agy_transition + exit 0 +fi + +check_agy_transition check_sandbox_enabled check_sandbox_network_off check_sandbox_paths_bounded diff --git a/agents/host/audit.sh b/agents/host/audit.sh index 0d4a14e..ade96a5 100755 --- a/agents/host/audit.sh +++ b/agents/host/audit.sh @@ -47,7 +47,7 @@ shell_rc_files() { # ---------- host.shell_alias_bypass ---------- # Aliases for known agents that include known bypass flags. check_shell_alias_bypass() { - local agents='claude|codex|gemini|amp' + local agents='claude|codex|gemini|amp|agy' local bypass_flags='--dangerously-skip-permissions|--full-auto|--dangerously-bypass-approvals-and-sandbox|--dangerously-allow-all|--yolo|--approval-mode=yolo' while IFS= read -r rc; do @@ -82,7 +82,7 @@ check_shell_alias_bypass() { # look for any bypass flag inside. Single-line and multi-line definitions are # both supported via awk's brace-depth tracking. check_shell_function_bypass() { - local agents='claude|codex|gemini|amp' + local agents='claude|codex|gemini|amp|agy' local bypass_flags='--dangerously-skip-permissions|--full-auto|--dangerously-bypass-approvals-and-sandbox|--dangerously-allow-all|--yolo|--approval-mode=yolo' while IFS= read -r rc; do diff --git a/scripts/detect.sh b/scripts/detect.sh index c1d06ee..1d45af2 100755 --- a/scripts/detect.sh +++ b/scripts/detect.sh @@ -81,6 +81,13 @@ agent_present claude claude "$HOME/.claude" "$HOME/.claude.json" ".claude" agent_present codex codex "$HOME/.codex" agent_present gemini gemini "$HOME/.gemini" ".gemini" agent_present amp amp "$HOME/.config/amp" ".amp" +# Antigravity CLI (agy) — Gemini CLI's successor. It nests its config inside +# ~/.gemini/, so check the agy-specific subdirectory, not ~/.gemini itself. +agent_present antigravity agy "$HOME/.gemini/antigravity-cli" + +if command -v agy >/dev/null 2>&1 || [[ -d "$HOME/.gemini/antigravity-cli" ]]; then + echo "# Antigravity CLI (agy) replaces Gemini CLI for consumer tiers as of 2026-06-18; sandshell audit/apply do not cover agy yet (see KNOWN_ISSUES.md)" +fi # Pointer to audit if any agent is present. if command -v claude >/dev/null 2>&1 || [[ -d "$HOME/.claude" ]] \ diff --git a/scripts/setup-gemini.sh b/scripts/setup-gemini.sh index 16af927..aeb6f60 100755 --- a/scripts/setup-gemini.sh +++ b/scripts/setup-gemini.sh @@ -39,6 +39,12 @@ Writes: - general.defaultApprovalMode = "default" For real network containment on macOS, see KNOWN_ISSUES.md. + +NOTE: Google sunsets Gemini CLI for consumer tiers on 2026-06-18 in favor of +Antigravity CLI (agy). These settings apply to Gemini CLI only — agy reads its +own config under ~/.gemini/antigravity-cli/ and is not yet covered by +sandshell (its security settings are undocumented; see KNOWN_ISSUES.md). +Gemini Code Assist Standard/Enterprise licenses keep Gemini CLI access. EOF } @@ -252,6 +258,15 @@ EOF ;; esac +if command -v agy >/dev/null 2>&1 || [[ -d "$HOME/.gemini/antigravity-cli" ]]; then + cat <<'EOF' + +NOTE: Antigravity CLI (agy) detected on this host. The settings above govern +Gemini CLI only — agy reads its own config under ~/.gemini/antigravity-cli/ +and its security settings are not yet documented. See KNOWN_ISSUES.md. +EOF +fi + cat </dev/null) +} + +make_fake_bin() { + local dir="$1"; shift + mkdir -p "$dir" + local name + for name in "$@"; do + printf '#!/bin/sh\nexit 0\n' > "$dir/$name" + chmod +x "$dir/$name" + done +} + +# Case 8: agy installed, gemini binary absent, Gemini config still present → +# agy_transition fires high and the legacy checks still run against the config. +mkdir -p "$TMPDIR_TEST/case8/home/.gemini" +make_fake_bin "$TMPDIR_TEST/case8/fakebin" agy +cat > "$TMPDIR_TEST/case8/home/.gemini/settings.json" <<'EOF' +{"security": {"disableYoloMode": false}} +EOF +out=$(run_gemini_path "$TMPDIR_TEST/case8/home" "$TMPDIR_TEST/case8/cwd" \ + "$TMPDIR_TEST/case8/fakebin:/usr/bin:/bin") +assert_finding "$out" "gemini.agy_transition" +echo "$out" | grep '"id":"gemini.agy_transition"' | grep -q '"severity":"high"' \ + || fail "case8: agy_transition should be high when gemini binary is absent, got: $out" +assert_finding "$out" "gemini.sandbox_enabled" + +# Case 9: agy and gemini both installed → agy_transition fires at info. +mkdir -p "$TMPDIR_TEST/case9/home/.gemini" +make_fake_bin "$TMPDIR_TEST/case9/fakebin" agy gemini +cat > "$TMPDIR_TEST/case9/home/.gemini/settings.json" <<'EOF' +{"tools": {"sandbox": "docker"}} +EOF +out=$(run_gemini_path "$TMPDIR_TEST/case9/home" "$TMPDIR_TEST/case9/cwd" \ + "$TMPDIR_TEST/case9/fakebin:/usr/bin:/bin") +assert_finding "$out" "gemini.agy_transition" +echo "$out" | grep '"id":"gemini.agy_transition"' | grep -q '"severity":"info"' \ + || fail "case9: agy_transition should be info when gemini is also installed, got: $out" + +# Case 10: agy-only host — ~/.gemini exists only because agy nests its config +# there; no Gemini CLI config at all → only the transition finding, no +# misleading legacy criticals. +mkdir -p "$TMPDIR_TEST/case10/home/.gemini/antigravity-cli" +out=$(run_gemini_path "$TMPDIR_TEST/case10/home" "$TMPDIR_TEST/case10/cwd" \ + "/usr/bin:/bin") +assert_finding "$out" "gemini.agy_transition" +assert_no_finding "$out" "gemini.sandbox_enabled" +assert_no_finding "$out" "gemini.folder_trust_enabled" + +# Case 11: no agy anywhere → no transition finding. +mkdir -p "$TMPDIR_TEST/case11/home/.gemini" +cat > "$TMPDIR_TEST/case11/home/.gemini/settings.json" <<'EOF' +{"tools": {"sandbox": "docker"}} +EOF +out=$(run_gemini_path "$TMPDIR_TEST/case11/home" "$TMPDIR_TEST/case11/cwd" \ + "/usr/bin:/bin") +assert_no_finding "$out" "gemini.agy_transition" + +echo "PASS: gemini audit checks (sandbox, folder trust, yolo, approval, trusted folders, linux backend, agy transition)" diff --git a/tests/test_detect.sh b/tests/test_detect.sh index dcf2eb3..1d9dde2 100755 --- a/tests/test_detect.sh +++ b/tests/test_detect.sh @@ -21,7 +21,7 @@ mkdir -p "$TMPDIR_TEST/inv/home" "$TMPDIR_TEST/inv/cwd" out=$(run_detect "$TMPDIR_TEST/inv/home" "$TMPDIR_TEST/inv/cwd") for field in os arch native_sandbox dep_jq dep_python3 dep_tomllib \ - agent_claude agent_codex agent_gemini agent_amp; do + agent_claude agent_codex agent_gemini agent_amp agent_antigravity; do echo "$out" | grep -qE "^${field}=" \ || fail "expected '${field}=' in detect output, got: $out" done @@ -42,11 +42,18 @@ done # Each agent_* field must be one of: installed, config_only, absent. # (We can't assert specifically 'absent' even with a fake HOME, because # `command -v` checks PATH, which the test doesn't override.) -for agent in claude codex gemini amp; do +for agent in claude codex gemini amp antigravity; do echo "$out" | grep -qE "^agent_${agent}=(installed|config_only|absent)$" \ || fail "expected agent_${agent} to be installed/config_only/absent, got: $out" done +# Antigravity config lives inside ~/.gemini — agy's subdirectory must register +# as config_only for antigravity without flipping agent_gemini's semantics. +mkdir -p "$TMPDIR_TEST/with_agy/home/.gemini/antigravity-cli" +out=$(run_detect "$TMPDIR_TEST/with_agy/home" "$TMPDIR_TEST/with_agy/cwd") +echo "$out" | grep -qE "^agent_antigravity=(config_only|installed)$" \ + || fail "expected agent_antigravity=config_only or installed when ~/.gemini/antigravity-cli exists, got: $out" + # When ~/.claude exists, agent_claude is at least config_only. mkdir -p "$TMPDIR_TEST/with_claude/home/.claude" out=$(run_detect "$TMPDIR_TEST/with_claude/home" "$TMPDIR_TEST/with_claude/cwd") From ed16c1e1ed73d8e265b7b12fdd23494fa0deea48 Mon Sep 17 00:00:00 2001 From: Luisa Lima Date: Fri, 12 Jun 2026 16:32:19 +0100 Subject: [PATCH 2/3] Fix CI: isolate gemini audit presence checks from host-installed binaries CI has been red on main since 2026-05-08: cases 4/5 used PATH=/usr/bin:/bin as a stand-in for "no container runtime installed", but GitHub's ubuntu runners ship docker at /usr/bin/docker, so linux_runtime_missing (correctly) never fired. Replace system-dir PATHs with a restricted bin dir that symlinks only the tools the adapter needs, so docker/podman/lxc/ gemini/agy presence is controlled entirely by the test. Co-Authored-By: Claude Fable 5 --- tests/test_audit_gemini.sh | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/test_audit_gemini.sh b/tests/test_audit_gemini.sh index ef7beb8..8931054 100755 --- a/tests/test_audit_gemini.sh +++ b/tests/test_audit_gemini.sh @@ -77,6 +77,18 @@ assert_no_finding "$out" "gemini.disable_yolo" assert_no_finding "$out" "gemini.disable_always_allow" assert_no_finding "$out" "gemini.approval_mode" +# Restricted PATH for presence-sensitive cases. System dirs are NOT a safe +# stand-in for "nothing installed" — GitHub's ubuntu runners ship docker at +# /usr/bin/docker, which made case 4/5's expected linux_runtime_missing +# finding (correctly) not fire. Symlink only the tools the adapter needs so +# docker/podman/lxc/gemini/agy presence is controlled entirely by the test. +RESTRICTED_BIN="$TMPDIR_TEST/restricted-bin" +mkdir -p "$RESTRICTED_BIN" +for tool in bash sh jq grep head paste tr uname; do + tool_path="$(command -v "$tool" 2>/dev/null || true)" + [[ -n "$tool_path" ]] && ln -s "$tool_path" "$RESTRICTED_BIN/$tool" +done + # Helper for OS-mocked runs. run_gemini_os() { local home="$1" cwd="$2" os="$3" path_override="${4:-}" @@ -104,7 +116,7 @@ mkdir -p "$TMPDIR_TEST/case4/home/.gemini" cat > "$TMPDIR_TEST/case4/home/.gemini/settings.json" <<'EOF' {"tools": {"sandboxNetworkAccess": false}} EOF -out=$(run_gemini_os "$TMPDIR_TEST/case4/home" "$TMPDIR_TEST/case4/cwd" "Linux" "/usr/bin:/bin") +out=$(run_gemini_os "$TMPDIR_TEST/case4/home" "$TMPDIR_TEST/case4/cwd" "Linux" "$RESTRICTED_BIN") assert_finding "$out" "gemini.sandbox.linux_runtime_missing" # Case 5: Linux + tools.sandbox = "docker" but docker not installed → @@ -113,7 +125,7 @@ mkdir -p "$TMPDIR_TEST/case5/home/.gemini" cat > "$TMPDIR_TEST/case5/home/.gemini/settings.json" <<'EOF' {"tools": {"sandbox": "docker", "sandboxNetworkAccess": false}} EOF -out=$(run_gemini_os "$TMPDIR_TEST/case5/home" "$TMPDIR_TEST/case5/cwd" "Linux" "/usr/bin:/bin") +out=$(run_gemini_os "$TMPDIR_TEST/case5/home" "$TMPDIR_TEST/case5/cwd" "Linux" "$RESTRICTED_BIN") assert_finding "$out" "gemini.sandbox.linux_runtime_missing" # Case 6: Linux + tools.sandbox = "docker" with docker present → no linux_* @@ -127,7 +139,7 @@ chmod +x "$TMPDIR_TEST/case6/fakebin/docker" cat > "$TMPDIR_TEST/case6/home/.gemini/settings.json" <<'EOF' {"tools": {"sandbox": "docker", "sandboxNetworkAccess": false}} EOF -out=$(run_gemini_os "$TMPDIR_TEST/case6/home" "$TMPDIR_TEST/case6/cwd" "Linux" "$TMPDIR_TEST/case6/fakebin:/usr/bin:/bin") +out=$(run_gemini_os "$TMPDIR_TEST/case6/home" "$TMPDIR_TEST/case6/cwd" "Linux" "$TMPDIR_TEST/case6/fakebin:$RESTRICTED_BIN") assert_no_finding "$out" "gemini.sandbox.linux_invalid" assert_no_finding "$out" "gemini.sandbox.linux_runtime_missing" @@ -142,9 +154,8 @@ assert_no_finding "$out" "gemini.sandbox.linux_invalid" assert_no_finding "$out" "gemini.sandbox.linux_runtime_missing" # --- Antigravity CLI (agy) transition cases --- -# PATH is pinned to system dirs (plus a fakebin) so 'gemini'/'agy' presence is -# controlled by the test, not by whatever is installed on the host. jq resolves -# from /usr/bin on both CI and macOS 15+, same as cases 4-6. +# PATH is pinned to RESTRICTED_BIN (plus a fakebin) so 'gemini'/'agy' presence is +# controlled by the test, not by whatever is installed on the host. run_gemini_path() { local home="$1" cwd="$2" path="$3" mkdir -p "$home" "$cwd" @@ -169,7 +180,7 @@ cat > "$TMPDIR_TEST/case8/home/.gemini/settings.json" <<'EOF' {"security": {"disableYoloMode": false}} EOF out=$(run_gemini_path "$TMPDIR_TEST/case8/home" "$TMPDIR_TEST/case8/cwd" \ - "$TMPDIR_TEST/case8/fakebin:/usr/bin:/bin") + "$TMPDIR_TEST/case8/fakebin:$RESTRICTED_BIN") assert_finding "$out" "gemini.agy_transition" echo "$out" | grep '"id":"gemini.agy_transition"' | grep -q '"severity":"high"' \ || fail "case8: agy_transition should be high when gemini binary is absent, got: $out" @@ -182,7 +193,7 @@ cat > "$TMPDIR_TEST/case9/home/.gemini/settings.json" <<'EOF' {"tools": {"sandbox": "docker"}} EOF out=$(run_gemini_path "$TMPDIR_TEST/case9/home" "$TMPDIR_TEST/case9/cwd" \ - "$TMPDIR_TEST/case9/fakebin:/usr/bin:/bin") + "$TMPDIR_TEST/case9/fakebin:$RESTRICTED_BIN") assert_finding "$out" "gemini.agy_transition" echo "$out" | grep '"id":"gemini.agy_transition"' | grep -q '"severity":"info"' \ || fail "case9: agy_transition should be info when gemini is also installed, got: $out" @@ -192,7 +203,7 @@ echo "$out" | grep '"id":"gemini.agy_transition"' | grep -q '"severity":"info"' # misleading legacy criticals. mkdir -p "$TMPDIR_TEST/case10/home/.gemini/antigravity-cli" out=$(run_gemini_path "$TMPDIR_TEST/case10/home" "$TMPDIR_TEST/case10/cwd" \ - "/usr/bin:/bin") + "$RESTRICTED_BIN") assert_finding "$out" "gemini.agy_transition" assert_no_finding "$out" "gemini.sandbox_enabled" assert_no_finding "$out" "gemini.folder_trust_enabled" @@ -203,7 +214,7 @@ cat > "$TMPDIR_TEST/case11/home/.gemini/settings.json" <<'EOF' {"tools": {"sandbox": "docker"}} EOF out=$(run_gemini_path "$TMPDIR_TEST/case11/home" "$TMPDIR_TEST/case11/cwd" \ - "/usr/bin:/bin") + "$RESTRICTED_BIN") assert_no_finding "$out" "gemini.agy_transition" echo "PASS: gemini audit checks (sandbox, folder trust, yolo, approval, trusted folders, linux backend, agy transition)" From 45e29ec2935ecc78ebe75318c39d61bcbaa84ae7 Mon Sep 17 00:00:00 2001 From: Luisa Lima Date: Thu, 18 Jun 2026 16:00:58 +0100 Subject: [PATCH 3/3] Fix CI: isolate setup-gemini Linux backend tests from host docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second front of the same pre-existing breakage: cases 7/8/9 used PATH=/usr/bin:/bin (or kept it in the search path) as a stand-in for "no container runtime", but GitHub's ubuntu runners ship docker at /usr/bin/docker, so setup-gemini.sh picked it up and wrote tools.sandbox=docker where the tests expected it omitted/podman. Mirror the real PATH into a scratch dir, symlinking everything except docker/podman/lxc/runsc, so runtime presence is controlled entirely by the test while every other tool the script needs stays reachable. Case 7 (podman) now asserts unconditionally instead of skipping when the host has docker. Verified by running the full suite and release-check with a fake docker (and podman) prepended to PATH — all green. Co-Authored-By: Claude Fable 5 --- tests/test_setup_gemini.sh | 41 +++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/tests/test_setup_gemini.sh b/tests/test_setup_gemini.sh index f7d2a04..ffdf38d 100755 --- a/tests/test_setup_gemini.sh +++ b/tests/test_setup_gemini.sh @@ -70,6 +70,27 @@ out=$(HOME="$TMPDIR_TEST/case5" "$ROOT/scripts/setup-gemini.sh" --show 2>&1) # ---------- Linux backend selection ---------- unset SANDSHELL_FAKE_UNAME +# A PATH with no container runtime. /usr/bin:/bin is NOT a safe stand-in for +# "nothing installed" — GitHub's ubuntu runners ship docker at /usr/bin/docker, +# so the no-runtime cases below would (correctly) pick it up and fail. Mirror +# the real PATH into a scratch dir, symlinking everything except the runtimes +# setup-gemini.sh probes for, so docker/podman/lxc/runsc presence is controlled +# entirely by the test while every other tool the script needs stays reachable. +NO_RUNTIME_BIN="$TMPDIR_TEST/no-runtime-bin" +mkdir -p "$NO_RUNTIME_BIN" +IFS=':' read -ra _path_dirs <<< "$PATH" +for _dir in "${_path_dirs[@]}"; do + [[ -d "$_dir" ]] || continue + for _bin in "$_dir"/*; do + [[ -e "$_bin" ]] || continue + _name="$(basename "$_bin")" + case "$_name" in + docker|podman|lxc|lxc-*|runsc) continue ;; + esac + [[ -e "$NO_RUNTIME_BIN/$_name" ]] || ln -s "$_bin" "$NO_RUNTIME_BIN/$_name" 2>/dev/null || true + done +done + # Case 6: Linux + docker available → tools.sandbox = "docker". mkdir -p "$TMPDIR_TEST/case6/fakebin" cat > "$TMPDIR_TEST/case6/fakebin/docker" <<'EOF' @@ -89,20 +110,16 @@ cat > "$TMPDIR_TEST/case7/fakebin/podman" <<'EOF' exit 0 EOF chmod +x "$TMPDIR_TEST/case7/fakebin/podman" -# Restrict PATH so neither docker nor a real one leak in. jq still needs to -# be reachable, so we keep /usr/bin and /opt/homebrew/bin in the search path. -SANDSHELL_FAKE_UNAME=Linux PATH="$TMPDIR_TEST/case7/fakebin:/usr/bin:/bin:/opt/homebrew/bin" \ +# Restrict PATH to the no-runtime bin (docker stripped) plus the fake podman, +# so podman is the only runtime visible regardless of what the host has. +SANDSHELL_FAKE_UNAME=Linux PATH="$TMPDIR_TEST/case7/fakebin:$NO_RUNTIME_BIN" \ HOME="$TMPDIR_TEST/case7" "$ROOT/scripts/setup-gemini.sh" user >/dev/null -# (This case relies on docker NOT being on the restricted PATH; if the host -# happens to have it in /usr/bin, we skip the assertion rather than fail.) -if ! command -v docker >/dev/null 2>&1 && [[ -x "$TMPDIR_TEST/case7/fakebin/podman" ]]; then - actual=$(jq -r '.tools.sandbox // ""' "$TMPDIR_TEST/case7/.gemini/settings.json") - [[ "$actual" == "podman" ]] \ - || fail "case7: expected tools.sandbox=podman, got: $actual" -fi +actual=$(jq -r '.tools.sandbox // ""' "$TMPDIR_TEST/case7/.gemini/settings.json") +[[ "$actual" == "podman" ]] \ + || fail "case7: expected tools.sandbox=podman, got: $actual" # Case 8: Linux + neither docker nor podman → tools.sandbox is OMITTED. -SANDSHELL_FAKE_UNAME=Linux PATH=/usr/bin:/bin \ +SANDSHELL_FAKE_UNAME=Linux PATH="$NO_RUNTIME_BIN" \ HOME="$TMPDIR_TEST/case8" "$ROOT/scripts/setup-gemini.sh" user >/dev/null config8="$TMPDIR_TEST/case8/.gemini/settings.json" [[ -f "$config8" ]] || fail "case8: settings.json was not written" @@ -118,7 +135,7 @@ mkdir -p "$TMPDIR_TEST/case9/.gemini" cat > "$TMPDIR_TEST/case9/.gemini/settings.json" <<'EOF' {"sandshell_managed": true, "tools": {"sandbox": "sandbox-exec", "sandboxNetworkAccess": false}} EOF -SANDSHELL_FAKE_UNAME=Linux PATH=/usr/bin:/bin \ +SANDSHELL_FAKE_UNAME=Linux PATH="$NO_RUNTIME_BIN" \ HOME="$TMPDIR_TEST/case9" "$ROOT/scripts/setup-gemini.sh" user >/dev/null [[ "$(jq -r '.tools.sandbox // ""' "$TMPDIR_TEST/case9/.gemini/settings.json")" == "" ]] \ || fail "case9: stale sandbox-exec should be stripped on Linux re-apply"