Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ Per-agent adapters live in `agents/<name>/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.
Expand Down
49 changes: 49 additions & 0 deletions agents/gemini/audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions agents/host/audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions scripts/detect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]] \
Expand Down
15 changes: 15 additions & 0 deletions scripts/setup-gemini.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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 <<EOF

VERIFY in any new Gemini session, run:
Expand Down
84 changes: 80 additions & 4 deletions tests/test_audit_gemini.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}"
Expand Down Expand Up @@ -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 →
Expand All @@ -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_*
Expand All @@ -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"

Expand All @@ -141,4 +153,68 @@ out=$(run_gemini_os "$TMPDIR_TEST/case7/home" "$TMPDIR_TEST/case7/cwd" "Darwin")
assert_no_finding "$out" "gemini.sandbox.linux_invalid"
assert_no_finding "$out" "gemini.sandbox.linux_runtime_missing"

echo "PASS: gemini audit checks (sandbox, folder trust, yolo, approval, trusted folders, linux backend)"
# --- Antigravity CLI (agy) transition cases ---
# 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"
(cd "$cwd" && PATH="$path" HOME="$home" "$ADAPTER" 2>/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:$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"
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:$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"

# 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" \
"$RESTRICTED_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" \
"$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)"
11 changes: 9 additions & 2 deletions tests/test_detect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down
Loading
Loading