Building Managed Agents That Use GitHub Without Exposing Your Token
You can run the GitHub CLI (gh) inside a Gemini API Managed Agents without exposing your GitHub Personal Access Token (PAT) to the sandbox.
The Network Configuration egress proxy injects your real token into outbound requests. The agent uses a dummy token locally, so the real token never enters the sandbox.
How it works
- Dummy token: The agent exports
GH_TOKEN="dummy"inside the sandbox to satisfygh's local auth checks. - Interception: The egress proxy intercepts requests to
api.github.comorgithub.com. - Transformation: The proxy replaces the dummy token in the
Authorizationheader with your real token. - Security: The real token remains in the control plane and is never visible inside the sandbox.

Prerequisites
-
Gemini API Key: Get your key from Google AI Studio.
-
GitHub Personal Access Token (PAT): I recommend a Fine-grained PAT restricted to the specific repositories your agent needs.
To create one:
- Go to GitHub -> Settings -> Developer settings -> Personal access tokens -> Fine-grained tokens or click here.
- Click Generate new token.
- Under Repository access, select your target repositories.
- Under Permissions -> Repository permissions, configure:
- Contents:
Access: Read and write(to clone and push code). - Pull requests:
Access: Read and write(to open, review, and comment on PRs). - Issues:
Access: Read and write(to create and comment on issues).
- Contents:
- Click Generate token and copy it. It won't be shown again.
Example
This Python example spins up the agent in an isolated, ephemeral Linux environment, configures the network allowlist with header transformations, and runs gh.
import base64
import os
from google import genai
# GitHub PAT from host
GITHUB_PAT = os.environ.get("GITHUB_PAT")
if not GITHUB_PAT:
raise ValueError("Set GITHUB_PAT environment variable on the host.")
# Base64-encode for Git HTTPS auth
git_auth_str = f"x-oauth-basic:{GITHUB_PAT}"
git_auth_base64 = base64.b64encode(git_auth_str.encode("utf-8")).decode("utf-8")
client = genai.Client()
# Shim: auto-installs gh, injects dummy token, ensures git HTTPS and no prompt
shim_script = """#!/bin/bash
REAL_GH="/workspace/gh_install/bin/gh"
if [ ! -f "$REAL_GH" ]; then
echo "GitHub CLI not found. Installing to /workspace/gh_install..." >&2
mkdir -p /workspace/gh_install
curl -sSLo /workspace/gh_install/gh.tar.gz https://github.com/cli/cli/releases/download/v2.40.1/gh_2.40.1_linux_amd64.tar.gz >&2
tar --no-same-owner -xf /workspace/gh_install/gh.tar.gz -C /workspace/gh_install --strip-components=1 >&2
rm -f /workspace/gh_install/gh.tar.gz >&2
echo "GitHub CLI installed." >&2
fi
export GH_TOKEN="dummy_token_to_bypass_cli_check"
export GIT_TERMINAL_PROMPT=0
"$REAL_GH" config set git_protocol https >/dev/null 2>&1 || true
exec "$REAL_GH" "$@"
"""
agent_prompt = """
Use the GitHub CLI wrapper at `/workspace/bin/gh` to show the logged in user info, and clone the repository 'octocat/Spoon-Knife' to '/workspace/Spoon-Knife'.
"""
interaction = client.interactions.create(
agent="antigravity-preview-05-2026",
input=agent_prompt,
environment={
"type": "remote",
"sources": [
{
"type": "inline",
"content": shim_script,
"target": "/workspace/bin/gh"
}
],
"network": {
"allowlist": [
{
"domain": "api.github.com", # GitHub API
"transform": [{"Authorization": f"Bearer {GITHUB_PAT}"}]
},
{
"domain": "github.com", # Git over HTTPS rule
"transform": [{"Authorization": f"Basic {git_auth_base64}"}]
},
{
"domain": "*"
}
]
}
}
)
print(interaction.output_text)
# Logged in as philschmid; cloned octocat/Spoon-Knife to /workspace/Spoon-KnifeGitHub uses different auth schemes depending on the request. API calls to api.github.com expect a Bearer token, while Git operations against github.com require base64-encoded Basic auth.
The wildcard entry ("domain": "*") lets the sandbox reach everything else, e.g. downloading the gh binary, fetching release assets, etc. You can lock this down to specific domains like objects.githubusercontent.com if you prefer tighter controls.
Persistent "GitHub agent"
To make the agent autonomous and avoid setup delays, you can create a managed agent that auto-installs the gh CLI. It reuses the environment after the first interaction to keep the installed gh binary across calls.
import base64
import os
from google import genai
# GitHub PAT from host
GITHUB_PAT = os.environ.get("GITHUB_PAT", "TOKEN_HERE")
git_auth_str = f"x-oauth-basic:{GITHUB_PAT}"
git_auth_base64 = base64.b64encode(git_auth_str.encode("utf-8")).decode("utf-8")
client = genai.Client()
system_instruction = """
You are a senior code reviewer. You have access to the GitHub CLI via a shim wrapper script at `/workspace/bin/gh`.
First run `chmod +x /workspace/bin/gh` so that the wrapper script is executable. Then always use `/workspace/bin/gh` (or `bash /workspace/bin/gh`) for any GitHub operations.
Do NOT use the standard `gh` command directly, as it will not have the correct authentication token.
"""
# Shim: auto-installs gh, injects dummy token, forces HTTPS and disables interactive prompts
shim_script = """#!/bin/bash
REAL_GH="/workspace/gh_install/bin/gh"
if [ ! -f "$REAL_GH" ]; then
echo "GitHub CLI not found. Installing to /workspace/gh_install..." >&2
mkdir -p /workspace/gh_install
curl -sSLo /workspace/gh_install/gh.tar.gz https://github.com/cli/cli/releases/download/v2.40.1/gh_2.40.1_linux_amd64.tar.gz >&2
tar --no-same-owner -xf /workspace/gh_install/gh.tar.gz -C /workspace/gh_install --strip-components=1 >&2
rm -f /workspace/gh_install/gh.tar.gz >&2
echo "GitHub CLI installed." >&2
fi
export GH_TOKEN="dummy_token_to_bypass_cli_check"
export GIT_TERMINAL_PROMPT=0
"$REAL_GH" config set git_protocol https >/dev/null 2>&1 || true
exec "$REAL_GH" "$@"
"""
client.agents.create(
id="github-reviewer-agent",
base_agent="antigravity-preview-05-2026",
base_environment={
"type": "remote",
"sources": [
{
"type": "inline",
"content": shim_script,
"target": "/workspace/bin/gh"
},
{
"type": "inline",
"content": system_instruction,
"target": "/AGENTS.md"
}
],
"network": {
"allowlist": [
{
"domain": "api.github.com",
"transform": [{"Authorization": f"Bearer {GITHUB_PAT}"}]
},
{
"domain": "github.com",
"transform": [{"Authorization": f"Basic {git_auth_base64}"}]
},
{"domain": "*"}
]
}
}
)The first interaction clones the base environment and triggers gh installation.
interaction_1 = client.interactions.create(
agent="github-reviewer-agent",
input="Which GitHub user is logged in?",
environment="remote",
)
print(interaction_1.output_text)
# ... currently logged-in GitHub user is **philschmid**.For subsequent calls, pass interaction_1.environment_id to reuse the same container, keeping the installed gh binary and inheriting the network transforms. Optionally pass previous_interaction_id=interaction_1.id as well if you want to retain conversation history across turns.
interaction_2 = client.interactions.create(
agent="github-reviewer-agent",
input="View the pull request #12 in repository 'octocat/Spoon-Knife' and summarize the changes.",
environment=interaction_1.environment_id,
previous_interaction_id=interaction_1.id
)
print(interaction_2.output_text)
# Based on the inspection of pull request #12 in theConclusion
You can give Managed Agents access to GitHub without putting secrets in the sandbox. A dummy token satisfies gh locally, and the egress proxy injects the real PAT on the way out.
For more on Managed Agents, environments, and network transforms, see the Managed Agents docs and Network configuration.
Thanks for reading! If you have any questions or feedback, please let me know on Twitter or LinkedIn.