Skip to content
Merged
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
26 changes: 21 additions & 5 deletions internal/cli/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func runGemini(
}
defer os.RemoveAll(settingsDirectory)
settingsPath := filepath.Join(settingsDirectory, "settings.json")
if err := os.WriteFile(settingsPath, []byte(`{"security":{"auth":{"selectedType":"gateway"}}}`), 0o600); err != nil {
if err := os.WriteFile(settingsPath, []byte(`{"security":{"auth":{"selectedType":"gateway","useExternal":true}}}`), 0o600); err != nil {
return errors.New("could not create Gemini CLI gateway settings")
}

Expand All @@ -113,7 +113,7 @@ func runGemini(
command.Stdin = stdin
command.Stdout = stdout
command.Stderr = stderr
command.Env = geminiEnvironment(os.Environ(), bridge.url, bridge.headerName+": "+bridge.headerValue, settingsPath)
command.Env = geminiEnvironment(os.Environ(), bridge.url, bridge.headerName+": "+bridge.headerValue, settingsPath, isAutomatedGeminiPrompt(args))
if err := command.Run(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
Expand Down Expand Up @@ -217,8 +217,17 @@ func (bridge *geminiBridge) close() {
_ = bridge.server.Shutdown(ctx)
}

func geminiEnvironment(environment []string, baseURL string, customHeaders string, settingsPath string) []string {
filtered := make([]string, 0, len(environment)+3)
func isAutomatedGeminiPrompt(args []string) bool {
for _, argument := range args {
if argument == "-p" || argument == "--prompt" || strings.HasPrefix(argument, "--prompt=") {
return true
}
}
return false
}
Comment on lines +220 to +227

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The detector scans arguments after the -- separator, even though parseClaudeOptions preserves -- and all following values as passthrough arguments. A prompt or positional value after -- equal to --prompt, -p, or beginning with --prompt= will incorrectly enable GEMINI_CLI_TRUST_WORKSPACE=true, granting workspace trust when the user did not request automated prompt mode. Stop scanning once -- is encountered. [incorrect condition logic]

Severity Level: Critical 🚨
- ❌ Separator-delimited arguments can enable workspace trust.
- ⚠️ Interactive Gemini execution may skip workspace confirmation.
- ⚠️ Trust boundary depends on raw argument text.

Use CodeAnt Skill

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** internal/cli/gemini.go
**Line:** 220:227
**Comment:**
	*Incorrect Condition Logic: The detector scans arguments after the `--` separator, even though `parseClaudeOptions` preserves `--` and all following values as passthrough arguments. A prompt or positional value after `--` equal to `--prompt`, `-p`, or beginning with `--prompt=` will incorrectly enable `GEMINI_CLI_TRUST_WORKSPACE=true`, granting workspace trust when the user did not request automated prompt mode. Stop scanning once `--` is encountered.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


func geminiEnvironment(environment []string, baseURL string, customHeaders string, settingsPath string, trustWorkspace bool) []string {
filtered := make([]string, 0, len(environment)+4)
for _, entry := range environment {
name, _, _ := strings.Cut(entry, "=")
switch strings.ToUpper(name) {
Expand All @@ -227,13 +236,20 @@ func geminiEnvironment(environment []string, baseURL string, customHeaders strin
"GEMINI_CLI_SYSTEM_SETTINGS_PATH":
continue
}
if trustWorkspace && strings.EqualFold(name, "GEMINI_CLI_TRUST_WORKSPACE") {
continue
}
Comment on lines +239 to +241

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Interactive invocations retain any inherited GEMINI_CLI_TRUST_WORKSPACE value because the variable is filtered only when trustWorkspace is true. A user or parent process with this variable set to true can therefore enable workspace trust without an explicit non-interactive prompt, violating the intended security boundary. Always remove the inherited variable and append it only for automated prompts. [security]

Severity Level: Critical 🚨
- ❌ Interactive Gemini sessions can inherit workspace trust.
- ⚠️ Workspace security prompts may be bypassed through process environment.
- ⚠️ Interactive tool execution receives broader workspace permissions.

Use CodeAnt Skill

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** internal/cli/gemini.go
**Line:** 239:241
**Comment:**
	*Security: Interactive invocations retain any inherited `GEMINI_CLI_TRUST_WORKSPACE` value because the variable is filtered only when `trustWorkspace` is true. A user or parent process with this variable set to `true` can therefore enable workspace trust without an explicit non-interactive prompt, violating the intended security boundary. Always remove the inherited variable and append it only for automated prompts.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

filtered = append(filtered, entry)
}
return append(filtered,
filtered = append(filtered,
"GOOGLE_GEMINI_BASE_URL="+baseURL,
"GEMINI_CLI_CUSTOM_HEADERS="+customHeaders,
"GEMINI_CLI_SYSTEM_SETTINGS_PATH="+settingsPath,
)
if trustWorkspace {
filtered = append(filtered, "GEMINI_CLI_TRUST_WORKSPACE=true")
}
return filtered
}

func printGeminiHelp(output io.Writer) {
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/gemini_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestRunGeminiUsesOfficialCLIWithGatewayEnvironment(t *testing.T) {
defer func() { findGeminiCLIExecutable = original }()
directory := t.TempDir()
executable := filepath.Join(directory, "gemini")
if err := os.WriteFile(executable, []byte("#!/bin/sh\nprintf '%s\\n' \"$@\"\nprintf 'base=%s\\nheaders=%s\\nsettings=%s\\n' \"$GOOGLE_GEMINI_BASE_URL\" \"$GEMINI_CLI_CUSTOM_HEADERS\" \"$GEMINI_CLI_SYSTEM_SETTINGS_PATH\"\n"), 0o755); err != nil {
if err := os.WriteFile(executable, []byte("#!/bin/sh\nprintf '%s\\n' \"$@\"\nprintf 'base=%s\\nheaders=%s\\nsettings=%s\\ntrust=%s\\n' \"$GOOGLE_GEMINI_BASE_URL\" \"$GEMINI_CLI_CUSTOM_HEADERS\" \"$GEMINI_CLI_SYSTEM_SETTINGS_PATH\" \"$GEMINI_CLI_TRUST_WORKSPACE\"\ncat \"$GEMINI_CLI_SYSTEM_SETTINGS_PATH\"\n"), 0o755); err != nil {
t.Fatal(err)
}
findGeminiCLIExecutable = func() (geminiCLIExecutable, error) { return geminiCLIExecutable{path: executable}, nil }
Expand All @@ -101,7 +101,7 @@ func TestRunGeminiUsesOfficialCLIWithGatewayEnvironment(t *testing.T) {
if err := runGemini(context.Background(), upstream.URL, "circles-secret", "person@example.com", withDefaultGeminiModel([]string{"-p", "hello"}), strings.NewReader(""), &output, io.Discard); err != nil {
t.Fatal(err)
}
for _, value := range []string{"--model\ngemini-3.7-flash\n-p\nhello", "base=http://127.0.0.1:", "headers=X-Prism-Gemini-Bridge:", "settings=/"} {
for _, value := range []string{"--model\ngemini-3.7-flash\n-p\nhello", "base=http://127.0.0.1:", "headers=X-Prism-Gemini-Bridge:", "settings=/", "trust=true", `"selectedType":"gateway"`, `"useExternal":true`} {
if !strings.Contains(output.String(), value) {
t.Fatalf("output omitted %q: %s", value, output.String())
}
Expand Down
Loading