From 31dd6e795239513467280b9a50aee36a1cdb36df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:26:01 +0000 Subject: [PATCH 1/4] Initial plan From ea5887f37007a98381394c1bb7141af75b86da2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:49:48 +0000 Subject: [PATCH 2/4] fix: set placeholder GEMINI_API_KEY and create ~/.gemini/ in AWF container for Gemini CLI v0.65.0+ Agent-Logs-Url: https://github.com/github/gh-aw/sessions/6c2ebe9a-244a-4bbd-8515-2d1982097066 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 4 ++-- pkg/workflow/gemini_engine.go | 17 ++++++++++++++++- pkg/workflow/gemini_engine_test.go | 13 +++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 111cc86ca79..f7970077160 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -841,7 +841,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env GEMINI_API_KEY --exclude-env GH_AW_GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.4 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}" && export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: DEBUG: gemini-cli:* GEMINI_API_BASE_URL: http://host.docker.internal:10003 @@ -1271,7 +1271,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env GEMINI_API_KEY --allow-domains '*.googleapis.com,generativelanguage.googleapis.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.4 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}" && export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: DEBUG: gemini-cli:* GEMINI_API_BASE_URL: http://host.docker.internal:10003 diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 7785fb81515..340c5a5605f 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -248,7 +248,19 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str } npmPathSetup := GetNpmBinPathSetup() - geminiCommandWithPath := fmt.Sprintf("%s && %s", npmPathSetup, geminiCommand) + + // Inside the AWF container, GEMINI_API_KEY is excluded from the environment (via + // --exclude-env) so the agent cannot exfiltrate the real secret via bash tools. + // However, Gemini CLI v0.65.0+ performs a startup auth check and exits with code 41 + // if no auth method is configured when GEMINI_API_BASE_URL (the api-proxy) is set. + // To satisfy this check, set a placeholder value for GEMINI_API_KEY inside the + // container — the real key is held by AWF's api-proxy sidecar which intercepts all + // LLM API calls and handles authentication transparently. + // + // Also create $HOME/.gemini/ so Gemini CLI can save its project registry without + // failing with ENOENT (the directory may not exist in the container filesystem). + awfContainerSetup := `mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}"` + geminiCommandWithPath := fmt.Sprintf("%s && %s && %s", awfContainerSetup, npmPathSetup, geminiCommand) command = BuildAWFCommand(AWFCommandConfig{ EngineName: "gemini", @@ -266,8 +278,11 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str ExcludeEnvVarNames: ComputeAWFExcludeEnvVarNames(workflowData, []string{"GEMINI_API_KEY"}), }) } else { + // Create $HOME/.gemini/ to prevent ENOENT when Gemini CLI saves its project + // registry (the directory may not exist on a fresh runner instance). command = fmt.Sprintf(`set -o pipefail touch %s +mkdir -p "$HOME/.gemini" %s 2>&1 | tee -a %s`, AgentStepSummaryPath, geminiCommand, logFile) } diff --git a/pkg/workflow/gemini_engine_test.go b/pkg/workflow/gemini_engine_test.go index 16c64ce3a99..6f58f52261f 100644 --- a/pkg/workflow/gemini_engine_test.go +++ b/pkg/workflow/gemini_engine_test.go @@ -342,6 +342,13 @@ func TestGeminiEngineFirewallIntegration(t *testing.T) { assert.Contains(t, stepContent, "--allow-domains", "Should include allow-domains flag") assert.Contains(t, stepContent, "--enable-api-proxy", "Should include --enable-api-proxy flag") assert.Contains(t, stepContent, "GEMINI_API_BASE_URL: http://host.docker.internal:10003", "Should set GEMINI_API_BASE_URL to LLM gateway URL") + + // Should create ~/.gemini/ to prevent ENOENT when Gemini CLI saves project registry + assert.Contains(t, stepContent, `mkdir -p "$HOME/.gemini"`, "Should create ~/.gemini/ directory in container to prevent ENOENT") + + // Should set placeholder GEMINI_API_KEY inside container so Gemini CLI passes its + // startup auth check (real key is held by AWF's api-proxy sidecar) + assert.Contains(t, stepContent, `GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}"`, "Should set placeholder GEMINI_API_KEY in container for startup auth check") }) t.Run("firewall disabled", func(t *testing.T) { @@ -363,6 +370,12 @@ func TestGeminiEngineFirewallIntegration(t *testing.T) { assert.Contains(t, stepContent, "set -o pipefail", "Should use simple command with pipefail") assert.NotContains(t, stepContent, "awf", "Should not use AWF when firewall is disabled") assert.NotContains(t, stepContent, "GEMINI_API_BASE_URL", "Should not set GEMINI_API_BASE_URL when firewall is disabled") + + // Should create ~/.gemini/ to prevent ENOENT when Gemini CLI saves project registry + assert.Contains(t, stepContent, `mkdir -p "$HOME/.gemini"`, "Should create ~/.gemini/ directory to prevent ENOENT") + + // Should NOT set placeholder API key when firewall is disabled (real key is in env) + assert.NotContains(t, stepContent, "gemini-api-key-placeholder", "Should not set placeholder API key when firewall is disabled") }) } From a483c87941ef82bf9657be2094cfd1eceb18869d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:56:10 +0000 Subject: [PATCH 3/4] refactor: extract geminiAPIKeyPlaceholder as a named constant Agent-Logs-Url: https://github.com/github/gh-aw/sessions/6c2ebe9a-244a-4bbd-8515-2d1982097066 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/gemini_engine.go | 8 +++++++- pkg/workflow/gemini_engine_test.go | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/gemini_engine.go b/pkg/workflow/gemini_engine.go index 340c5a5605f..a32c554413c 100644 --- a/pkg/workflow/gemini_engine.go +++ b/pkg/workflow/gemini_engine.go @@ -10,6 +10,12 @@ import ( var geminiLog = logger.New("workflow:gemini_engine") +// geminiAPIKeyPlaceholder is the placeholder value set for GEMINI_API_KEY inside the AWF +// container. The real key is excluded from the container (held by AWF's api-proxy sidecar), +// but Gemini CLI v0.65.0+ requires some auth method configured before starting. The sidecar +// intercepts all LLM API calls and handles authentication transparently. +const geminiAPIKeyPlaceholder = "gemini-api-key-placeholder" + // GeminiEngine represents the Google Gemini CLI agentic engine type GeminiEngine struct { BaseEngine @@ -259,7 +265,7 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str // // Also create $HOME/.gemini/ so Gemini CLI can save its project registry without // failing with ENOENT (the directory may not exist in the container filesystem). - awfContainerSetup := `mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}"` + awfContainerSetup := fmt.Sprintf(`mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-%s}"`, geminiAPIKeyPlaceholder) geminiCommandWithPath := fmt.Sprintf("%s && %s && %s", awfContainerSetup, npmPathSetup, geminiCommand) command = BuildAWFCommand(AWFCommandConfig{ diff --git a/pkg/workflow/gemini_engine_test.go b/pkg/workflow/gemini_engine_test.go index 6f58f52261f..ce0c6bb6af0 100644 --- a/pkg/workflow/gemini_engine_test.go +++ b/pkg/workflow/gemini_engine_test.go @@ -3,6 +3,7 @@ package workflow import ( + "fmt" "strings" "testing" @@ -348,7 +349,7 @@ func TestGeminiEngineFirewallIntegration(t *testing.T) { // Should set placeholder GEMINI_API_KEY inside container so Gemini CLI passes its // startup auth check (real key is held by AWF's api-proxy sidecar) - assert.Contains(t, stepContent, `GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}"`, "Should set placeholder GEMINI_API_KEY in container for startup auth check") + assert.Contains(t, stepContent, fmt.Sprintf(`GEMINI_API_KEY="${GEMINI_API_KEY:-%s}"`, geminiAPIKeyPlaceholder), "Should set placeholder GEMINI_API_KEY in container for startup auth check") }) t.Run("firewall disabled", func(t *testing.T) { From d652c5e69db6c31ec935d0478c0e3f63c928c18c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:44:02 +0000 Subject: [PATCH 4/4] Agent-Logs-Url: https://github.com/github/gh-aw/sessions/89750aef-5aa9-4145-8e13-6e14adf2b2d3 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-gemini.lock.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 005c7c35146..c19d3ee5510 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -841,7 +841,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env GEMINI_API_KEY --exclude-env GH_AW_GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.googleapis.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,generativelanguage.googleapis.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}" && export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: DEBUG: gemini-cli:* GEMINI_API_BASE_URL: http://host.docker.internal:10003 @@ -1281,7 +1281,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env GEMINI_API_KEY --allow-domains '*.googleapis.com,generativelanguage.googleapis.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'mkdir -p "$HOME/.gemini" && export GEMINI_API_KEY="${GEMINI_API_KEY:-gemini-api-key-placeholder}" && export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && gemini --yolo --output-format stream-json --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: DEBUG: gemini-cli:* GEMINI_API_BASE_URL: http://host.docker.internal:10003