What it is
Gemini CLI and Code Assist agent mode run commands with your permissions and read the same~/.gemini/settings.json. Whether a hijacked session can reach your keys or run a repository's code depends on that file, your policy rules and extensions, and on what a trusted project ships in .gemini/ and .env. The audit checks all of it against theSecurity for Gemini Development guide and tells you exactly what to change. It's for Code Assist Standard and Enterprise users; consumer tiers moved to Antigravity CLI in June 2026.
Gemini CLI and Code Assist security audit v1.0.0 (read-only, offline)
FAIL [4] The project trusts MCP servers, bypassing every confirmation
tickets
FAIL [4] The project .env sets Gemini CLI variables
GEMINI_SANDBOX
WARN [2] Sandboxing is off (the default)
Shell commands and file edits run with your full user permissions.
PASS [2] Folder trust is on
PASS [3] YOLO mode is disabled
PASS [3] Policy rules don't allow risky shell commands
...
Summary: 9 pass, 1 warn, 2 fail, 2 info
Save a report with how-to-fix steps? [h]tml, [t]ext, [c]sv, [j]son, [n]o (default n):Every finding shows, in brackets, the section of the Gemini guide that explains the fix.
Why you can trust it
| Small and known | Official Alpine Linux 3.24.2 pinned by digest, plus only bash, jq and yq (to read TOML policy files) from Alpine's repositories, pinned to exact versions. |
| Open | Everything that runs is the Dockerfile, audit.sh, its mappings file and the shared audit library. You build it yourself from source. |
| Offline | Runs with --network none: it cannot send anything anywhere. |
| Read-only | Your files are mounted read-only and the container's own filesystem is read-only. It changes nothing. |
| What it can read | ~/.gemini/settings.json, your policy files and each extension's manifest; the machine-wide settings files; your VS Code user settings.json; your shell startup files; and the whole project folder you run it from, including any secrets kept in that project. It never mounts oauth_creds.json, google_accounts.json, chat history or extension code. Its output names settings, files and commands, and masks values that look like secrets. |
| Unprivileged | Runs as you, with every Linux capability dropped, and is removed when it exits. |
Run it
You need Docker Desktop, Docker Engine or Podman. Nothing else is installed on your machine.
macOS, Linux and WSL2
git clone https://github.com/aisecuritylabs-org/coding-agent-security.git ~/coding-agent-security
cd ~/code/my-app # the project you want to audit
bash ~/coding-agent-security/gemini/audit/run.shWindows: Command Prompt
git clone https://github.com/aisecuritylabs-org/coding-agent-security.git "%USERPROFILE%\coding-agent-security"
cd /d C:\Projects\my-app
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\coding-agent-security\gemini\audit\run.ps1"Windows: PowerShell
git clone https://github.com/aisecuritylabs-org/coding-agent-security.git "$HOME\coding-agent-security"
Set-Location C:\Projects\my-app
powershell -NoProfile -ExecutionPolicy Bypass -File "$HOME\coding-agent-security\gemini\audit\run.ps1"Replace the project folder with the one you want to audit. When gemini is on your PATH, the launcher also passes your Gemini CLI version to the audit. On Windows it also checks who can writeC:\ProgramData\gemini-cli, which the container can't see.
What it checks
| Area | Examples |
|---|---|
| Sandbox, trust and approvals | Sandboxing on; folder trust on; YOLO mode disabled; no auto_edit default; no risky tools.allowed entries; no sandbox network access; no permanent approvals |
| Policies and hooks | No allow rules for risky shell commands; no hooks that run remote code; no --yolo or GEMINI_SANDBOX=false in shell startup files |
| Extensions | MCP server inventory; unpinned servers; servers that read outsider-written content; "trust": true; secrets in settings; extensions and what they add |
| Code Assist | geminicodeassist.agentYoloMode off |
| Repositories | A .gemini/settings.json that lowers protections, trusts MCP servers or adds hooks; a .env that sets GEMINI_* variables; context files with fetch instructions or hidden Unicode; run-gemini-cli workflows; disguised symlinks; .git/config keys that run programs |
| Machine and version | ProgramData folder permissions on Windows; machine-wide settings; Gemini CLI 0.39.1 or later |
The exit code is 0 with no FAIL results and 1 otherwise, so the audit also works in scripts and CI. Admin controls live in the Google Cloud console, which an offline audit can't see; the checklist covers them.
Real attacks it covers
| Attack | What it did | Check |
|---|---|---|
| CVE-2026-12537 | Headless runs in CI trusted the workspace and ignored allowlists under --yolo | I01, P11 |
| Tracebit | An allowed grep chained with a command that sent environment variables away | G06, P09 |
| Comment and Control | Issue text made the Gemini CLI Action post its API key publicly | P11 |
| ProgramData configuration (Cymulate) | One Windows user plants a session-start hook every user runs | W01, W02 |
| Plugin4Shell (AIR Security) | A commit-pinned extension was swapped for different code | G09, E02 |
Reports with how-to-fix steps
After the on-screen summary, the audit asks whether to save a report as HTML,text, CSV or JSON. Every finding explains why it matters, gives the exact fix, maps the risk to OWASP Top 10 for LLM Applications 2025, OWASP Top 10 for Agentic Applications 2026, MITRE ATLAS 2026.09 and NIST AI RMF 1.0, and lists its sources. Reports are saved to gemini-audit-reports in your home folder, never inside the project. Reports mask values that look like secrets, such as tokens, passwords and credentials in URLs, but review a report before you share it.
Fix what it finds
Most findings are fixed by a settings file and a policy file, below as examples to adapt and test. Every key is from the Gemini CLI configuration reference and the policy engine docs.
Baseline settings.json
Save as ~/.gemini/settings.json; Code Assist agent mode reads it too. On macOS the default Seatbelt profile allows broad reads and network access, so set SEATBELT_PROFILE to astrict- profile, or set "sandbox": "docker".
{
"general": {
"defaultApprovalMode": "default"
},
"tools": {
"sandbox": true,
"sandboxNetworkAccess": false,
"allowed": [
"run_shell_command(git status)",
"run_shell_command(git diff)",
"run_shell_command(git log)"
]
},
"security": {
"folderTrust": {
"enabled": true
},
"disableYoloMode": true,
"enablePermanentToolApproval": false
}
}Policy rules
Save as ~/.gemini/policies/baseline.toml. The highest-priority matching rule wins. Project-level policies aren't enforced yet, so keep yours in your home folder.
# Save as ~/.gemini/policies/baseline.toml. When several rules match a tool
# call, the one with the highest priority wins. Test these rules before you rely
# on them; see gemini/README.md.
# Never delete recursively or escalate privileges.
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["rm -rf", "rm -r", "sudo"]
decision = "deny"
priority = 200
# Ask before anything that publishes, pushes or reaches the network.
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["git push", "npm publish", "curl", "wget", "ssh", "scp"]
decision = "ask_user"
priority = 100Gemini Code Assist
Merge into your VS Code user settings.json, so agent mode asks before it acts.
// Merge into your VS Code user settings.json for Gemini Code Assist. Agent mode
// also reads ~/.gemini/settings.json, so apply settings.json from this folder too.
// Test these settings before you rely on them; see gemini/README.md.
{
// Keep agent mode asking before it acts.
"geminicodeassist.agentYoloMode": false,
// Agent mode's YOLO setting works only in trusted workspaces; keep trust on.
"security.workspace.trust.enabled": true
}To prove it all works in a live session, run the ten-minute self-test.
Windows: lock the ProgramData folder
Gemini CLI on Windows loads machine-wide settings.json and system-defaults.jsonfrom C:\ProgramData\gemini-cli. The folder isn't created or locked by default, so any standard user could create it and plant a session-start hook that runs for every user. Cymulate reported this, and Google said it would be "addressed as a documentation update" (Cymulate). The audit reports it as W01.
Run this in PowerShell opened with Run as administrator; it's also in the repository as gemini/config/lock-programdata.ps1, and the last line shows the result. If the folder already existed and you didn't create it, check what's inside first.
# Run in PowerShell opened with "Run as administrator".
$p = 'C:\ProgramData\gemini-cli'
New-Item -ItemType Directory -Force -Path $p | Out-Null
# A fresh ACL: owned by Administrators, no inherited or extra entries.
$admins = [Security.Principal.SecurityIdentifier]'S-1-5-32-544'
$acl = New-Object Security.AccessControl.DirectorySecurity
$acl.SetOwner($admins)
$acl.SetAccessRuleProtection($true, $false)
$grants = @(
@('S-1-5-32-544', 'FullControl'), # Administrators
@('S-1-5-18', 'FullControl'), # SYSTEM
@('S-1-5-32-545', 'ReadAndExecute') # Users: read only
)
foreach ($g in $grants) {
$sid = [Security.Principal.SecurityIdentifier]$g[0]
$acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($sid, $g[1], 'ContainerInherit, ObjectInherit', 'None', 'Allow')))
}
Set-Acl -LiteralPath $p -AclObject $acl
# Anything already inside takes the same owner and permissions.
Get-ChildItem -LiteralPath $p -Recurse -Force | ForEach-Object {
icacls $_.FullName /setowner '*S-1-5-32-544' /c /q | Out-Null
icacls $_.FullName /reset /c /q | Out-Null
}
icacls $pDelete it when you are done
The container is already gone when the audit finishes. To remove the image as well:
docker image rm gemini-audit