Gemini CLI
Installing the Agent Firewall for Gemini CLI, its own sandbox, and what its hooks cannot see
The Gemini CLI adapter translates Gemini CLI's hook events to the firewall's normalized actions and its answers back. The policy is the same engine Claude Code uses; nothing about what is allowed differs by agent.
Tested with Gemini CLI 0.63.0. Every event shape was captured live against a local mock model that answered with scripted tool calls. No real account or key was used.
Install
launchsafe-firewall install --agent gemini # ~/.gemini/settings.json
launchsafe-firewall install --agent gemini --scope project # <project>/.gemini/settings.json
launchsafe-firewall doctorinstall adds one command hook per event, first in each list, keeps every other key and hook, sets hooksConfig.enabled: true, saves the original file under ~/.launchsafe-firewall/backups/ and records the install. Running it again changes nothing. uninstall --agent gemini removes exactly the firewall's entries (and the hooksConfig.enabled flag when install was the one that added it); --restore-backup puts the file back as it was, after saving the current file as settings.json.before-restore.TIMESTAMP.
Gemini CLI's hook timeouts are in milliseconds. Hooks need no feature flag in 0.63.0.
Two things Gemini CLI does weaken any hook, and doctor reports them:
- Folder trust. In a folder Gemini CLI does not trust it skips every hook, yours and the system's included. A headless run in an untrusted folder exits 55 unless given
--skip-trust. Trust the folders you work in. - A repository can switch your hooks off. A project
.gemini/settings.jsonwith"hooksConfig": {"enabled": false}, or adisabledlist naming the firewall's hook (launchsafe-firewall), turned off user-level hooks silently (captured).doctorfails on it, and the agent writing it is denied (FW-TAMPER).
Managed deployment
launchsafe-firewall install --managed --agent gemini --print | sudo tee "/Library/Application Support/GeminiCli/settings.json" # Linux: /etc/gemini-cli/settings.jsonSystem settings override user and project settings, but Gemini CLI skips the file unless its directory is owned by root. doctor checks the owner. GEMINI_CLI_SYSTEM_SETTINGS_PATH moves it. Gemini CLI has no administrator control that stops a user from adding further hooks, so doctor scores this as partial.
What the adapter maps
| Gemini CLI event | Firewall |
|---|---|
BeforeTool | The decision |
AfterTool | Taint, private data and fingerprints from the output |
BeforeAgent (carries the prompt) | Destinations you named |
SessionStart | The session-start agent-configuration scan |
SessionEnd, AfterAgent, BeforeModel, AfterModel, BeforeToolSelection, PreCompress, Notification | Ignored (the firewall judges actions, not text) |
| Gemini CLI tool | Judged as |
|---|---|
run_shell_command | A shell command (in dir_path when it is not .) |
read_file | A read |
read_many_files (older versions) | One read per path; a glob is a recursive read of its base directory |
write_file | A write (result-checked for settings files) |
replace | An edit (allow_multiple replaces every match). Gemini CLI matches old_string flexibly and can correct it from instruction; that was not captured, so the result is judged at every place the text could match, and as the worst case when it matches nowhere |
list_directory, glob, grep_search | A listing, a glob and a search |
web_fetch | One fetch per URL in the prompt, taken as Gemini CLI takes it: split on whitespace, each URL the whole token, brackets and quotes included. No URL is a parse failure (a person decides) |
google_web_search | A web search |
save_memory (older versions) | A write to ~/.gemini/GEMINI.md: allowed in a clean session, held as a proposal once untrusted |
write_todos, update_topic, invoke_agent, activate_skill, list_background_processes, read_background_output | Safe (the agent talking to itself; a subagent's own tool calls are judged one by one) |
MCP tools (mcp_<server>_<tool>) | mcp__<server>__<tool>; the server's source (system, project or user settings) decides its trust |
| Anything else | An unknown tool: held for a person (FW-UNKNOWN-TOOL) |
Gemini CLI's own sandbox (gemini -s, macOS)
-s re-runs the whole CLI under sandbox-exec with one profile, and every process it starts inherits it: the firewall's hook and the agent's own tools run in the same sandbox. Gemini CLI's built-in profiles never let a process write ~/.gemini. With the firewall installed and nothing else, every hook fails to save its state, so every non-read action is blocked (captured: 5 of 5 shell commands blocked, reads allowed). The hook says so in one message naming the fix.
The firewall's answer is a custom profile:
launchsafe-firewall install --agent gemini --seatbelt permissive-open
export SEATBELT_PROFILE=launchsafe # in your shell profile
gemini -s--seatbelt BASE writes ~/.gemini/sandbox-macos-launchsafe.sb: Gemini CLI's own base profile, copied unchanged from the installed Gemini CLI (found from gemini on PATH, or --gemini-package DIR), plus one block that lets a process write the hook's state directories (sessions, queue, log, locks, ledger, pins, quarantine, scan/receipts, scan/last) and nothing else. Re-run the command after updating Gemini CLI.
Why only those directories: whatever the profile lets the hook write, the agent's commands can write too. Every command is still judged first, exactly as without -s. With the firewall's profile, a command the analyser cannot see through got an error for policy.json, keys/, install.json, identity.json, backups/, canaries.json, the profile itself and ~/.gemini/settings.json, and could write only sessions/ and queue/. So under -s the policy, keys, install records, backups, vault and decoy registry have the OS's protection on top of the analyser's; the hook's state has the analyser's alone, as everywhere.
Not covered: mcp wrap started by Gemini CLI inside -s, the vault inside -s (references in Gemini CLI shell commands are refused anyway), desktop notifications from inside the sandbox (not verified), and Gemini CLI's container sandboxes (Docker, Podman: not tested).
Output protocol (observed)
BeforeToolblocks with{"decision": "deny", "reason": "..."}on stdout (exit 0), or exit 2 with text on stderr.- Exit 2 with empty stderr, any other exit code, a crash and a timeout let the tool run. The wrapper prints a line before
exit 2, so a failure of the firewall or of Node blocks. A timeout (30 s) still fails open. - Gemini CLI's hooks cannot put a question to you in a way the firewall can use, so an action that needs a person is held in the queue and the agent is told the exact
launchsafe-firewall queue approve <id>command.
What its hooks cannot do
- No
ConfigChangeevent: a change tosettings.jsonis judged only when the agent makes it through a tool, not when something else edits the file mid-session. - Secrets in tool output are not redacted.
- Subagents (
invoke_agent) are not reported as sessions; their tool calls arrive with the parent'ssession_id, so their taint is the session's. - A tool call Gemini CLI rejects before the hook never reaches the firewall, and never runs either.
- Unattended runs (
gemini -p,--yolo,--approval-mode yolo) are detected from the process command line only, because Gemini CLI tells its hooks nothing about its mode. The firewall reads it withps, up to four ancestors, and falls back topgrepon macOS where a seatbelt sandbox forbidsps. When neither can run, the session is treated as unattended. Another agent startinggemini --yolois also caught byFW-AGENT-BYPASS.