DEV Community

Cover image for Secure Gemini CLI for Cloud development
Remigiusz Samborski for Google AI

Posted on • Originally published at Medium

Secure Gemini CLI for Cloud development

AI agents are a double-edged sword. You hear horror stories of autonomous tools deleting production databases or purging entire email inboxes. These risks often lead users to require manual confirmation for every agent operation. This approach keeps you in control but limits the agent's autonomy. You will soon find yourself hand-holding the agent and hindering its true capabilities. You need a way to let the agent run in "yolo mode" without risking your system.

In this blog you will learn how to secure your Gemini CLI in a way that will allow you to run it in an isolated environment with limited GitHub and Google Cloud access while not worrying that it will do too much damage if things go wrong. We will follow the least privilege pattern to make sure Gemini CLI has all necessary permissions to build your project, but at the same time can’t access systems it shouldn’t touch.

The Sandbox premise

The solution consists of following components:

  1. Using GitHub fine-grained personal access tokens - limits source control risks.
  2. Google Cloud service account - limits cloud risks.
  3. Docker - limits local system risks.
  4. Session limits - avoid surprises with the number of used tokens (especially important when running in --yolo mode).

Following this approach will protect you from the 'helpful agent curse' - it’s a situation when the agent tries very hard to achieve a task by finding ways around blockers. Examples include: granting itself more permissions, copying files to the current folder to edit them, and many more.

GitHub fine-grained personal access tokens

First let’s limit agent’s GitHub exposure by leveraging the fine grained tokens:

  • Navigate to GitHub Settings > Developer Settings > Personal access tokens > Fine-grained tokens.
  • Click Generate a new token.
  • Provide a descriptive name for your token and consider using expiration date to force rotations on a regular basis.
  • Restrict Repository access to the specific target repo you are working on.
  • Grant Read and Write permissions for Contents.
  • Save the token locally by running export GITHUB_TOKEN="github_pat_...".

Google Cloud Service Account

Create an isolated Service Account (SA) with minimal permissions. This prevents the agent from accessing protected resources and other projects.

Run these commands after updating YOUR_PROJECT_ID and roles below:

# Set your project ID
export CLOUDSDK_CORE_PROJECT="YOUR_PROJECT_ID"
gcloud config set project $CLOUDSDK_CORE_PROJECT

# Create the Service Account
gcloud iam service-accounts create gemini-cli-sa \
    --description="Isolated account for Gemini CLI"

# Grant minimal roles (adjust roles as needed)
gcloud projects add-iam-policy-binding $CLOUDSDK_CORE_PROJECT \
   --member="serviceAccount:gemini-cli-sa@$CLOUDSDK_CORE_PROJECT.iam.gserviceaccount.com" \
    --role="roles/aiplatform.user"

# Generate the JSON key file
gcloud iam service-accounts keys create sa-key.json \
    --iam-account=gemini-cli-sa@$CLOUDSDK_CORE_PROJECT.iam.gserviceaccount.com
Enter fullscreen mode Exit fullscreen mode

Hint: you can use the IAM roles and permissions index page to easily find the roles to grant.

A good practice is to use a dedicated project for each of your AI coding initiatives. This way you can run several agents in parallel. They will build different solutions without worrying about stepping on each other's toes.

Custom Docker Build

The Gemini CLI uses a sandbox image to isolate the execution environment. You must customize this image to install gcloud, terraform, vim and set git configuration.

Prepare the Dockerfile

Create a .gemini directory in your project, and inside it, create a sandbox.Dockerfile. Using this specific file name allows Gemini CLI to automatically detect and build your custom sandbox profile if you’re running it from source and you can also use it to build the image manually if you’re running a binary installation.

Paste this content in the .gemini/sandbox.Dockerfile:

# Start from the official Gemini CLI sandbox image with proper version
ARG GEMINI_CLI_VERSION 0.33.0
FROM us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:${GEMINI_CLI_VERSION}

# Switch to root to install system dependencies (gcloud)
USER root

# Install Google Cloud SDK, Git, and prerequisites
RUN apt-get update && apt-get install -y curl apt-transport-https ca-certificates gnupg git && \
    echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - && \
    apt-get update && apt-get install -y google-cloud-cli

# Install Terraform
RUN apt-get update && apt-get install -y wget lsb-release && \
    wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list && \
    apt-get update && apt-get install -y terraform

# Install vim
RUN apt-get install -y vim

# Switch back to the non-root user (the official sandbox image uses 'node' as the default user)
USER node
WORKDIR /workspace

# Configure Git to use the injected GitHub PAT at runtime
RUN git config --global credential.helper '!f() { echo "username=x-access-token"; echo "password=$GITHUB_TOKEN"; }; f'
Enter fullscreen mode Exit fullscreen mode

Prepare docker for building images (optional MacOS step)

If you haven’t built any Docker images before then run following commands to prepare your environment with brew:

# Install dependencies
brew install docker colima docker-buildx

# Configure docker-buildx
mkdir -p ~/.docker/cli-plugins
ln -sfn $(brew --prefix)/opt/docker-buildx/bin/docker-buildx ~/.docker/cli-plugins/docker-buildx

# Start colima service
brew services start colima

# Update DOCKER_HOST (you might want to add this line to .bash_profile):
export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock"
Enter fullscreen mode Exit fullscreen mode

Build the image (binary installation)

If you installed Gemini CLI with npm, brew or any other binary method then you will need to manually build the Docker image and tag it as a default one that Gemini CLI is looking for:

# Get the base name the CLI looks for
export IMAGE_BASE_NAME="us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox"

# Get your currently installed Gemini CLI version (e.g., 0.33.0)
export GEMINI_CLI_VERSION=$(gemini --version)

# Combine them
export IMAGE_NAME="${IMAGE_BASE_NAME}:${GEMINI_CLI_VERSION}"

# Build your custom sandbox image
docker build \
  --build-arg GEMINI_CLI_VERSION=$GEMINI_CLI_VERSION \
  -t "${IMAGE_NAME}" \
  -f .gemini/sandbox.Dockerfile .
Enter fullscreen mode Exit fullscreen mode

Important: this image will be tagged with the exact version of the Gemini CLI you use. This means it needs to be rebuilt every time you update the CLI. I keep the above code in a shell script to run it after every update.

Build the image (source installation)

If you’re running your Gemini CLI from source as explained here. You can trigger the image build automatically each time you start gemini.

First update the top part of your sandbox.Dockerfile by substituting the FROM line with following:

# Start from the official Gemini CLI sandbox image (source installation)
FROM gemini-cli-sandbox
Enter fullscreen mode Exit fullscreen mode

Start Gemini CLI in the sandbox mode

First set couple very important environment variables:

# Export the necessary environment variables
export GITHUB_TOKEN="github_pat_..."
export GEMINI_API_KEY="your-api-key"
export CLOUDSDK_CORE_PROJECT="YOUR_PROJECT_ID"
export GEMINI_SANDBOX=docker

# We keep the ENV variables for our dynamic credentials
export SANDBOX_FLAGS="\
-e GITHUB_TOKEN=${GITHUB_TOKEN} \
-e CLOUDSDK_CORE_PROJECT=${CLOUDSDK_CORE_PROJECT} \
-e CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=$(pwd)/sa-key.json"
Enter fullscreen mode Exit fullscreen mode

Note: You can put the above variables in a shell script to speed up starts in the future. Just make sure to update your .gitignore to keep it and sa-key.json from getting added to your repository.

Now you can start your Gemini CLI with following command:

# For binary installation
gemini

# For source installation
BUILD_SANDBOX=1 gemini
Enter fullscreen mode Exit fullscreen mode

Session limits

To avoid surprises with the number of tokens that Gemini CLI uses in your session, you can use the Max Session Turns in /settings or your ~/.gemini/settings.json:

Max Session Turns

Congratulations

Congratulations 🚀You’re ready to validate your setup.

Validation and "Ultimate Tests"

Once the environment is launched within a sandbox we should verify the security boundaries.

First let’s run the /about command to see if we’re running within a sandbox. You should see something like this:

About with sandbox

Now let’s try to break out from our new sandbox.

GitHub privilege escalation

Try asking Gemini CLI to access a private repo it shouldn’t have access. Example prompt:

Clone https://github.com/USER_NAME/PRIVATE_REPOSITORY to a new folder
Enter fullscreen mode Exit fullscreen mode

You should see how Gemini CLI really tries and struggles to get access. Mine got really creative at trying to access the repo with git, gh, curl and even tried to reuse the GITHUB_TOKEN manually. All these tries failed and this error was displayed:

GitHub privilege escalation test

Google Cloud privilege escalation

Ask the agent to list all compute instances:

List all my compute instances
Enter fullscreen mode Exit fullscreen mode

It should fail due to missing permissions on your restricted Service Account. Gemini CLI tries really hard and executes couple different commands including reauthentication, but it fails at the end:

Google Cloud privilege escalation test

Local privilege escalation

Finally let’s try to access a file from another project folder by prompting:

There are other projects in the folder above the current one. List them and let me know if there is anything that is interesting from hacker's perspective.
Enter fullscreen mode Exit fullscreen mode

I am starting to feel sorry for the poor agent 😉 Once again it can’t complete its task:

Local privilege escalation test

Conclusions

Now that you have validated your sandbox setup you should feel much more confident to run gemini --yolo and streamline your work as Gemini CLI delivers your code without hand-holding and pesky Can I execute this command? Prompts.

I am looking forward to all the creative ideas you’ll bring to life!

What's next?

If you find this setup useful here are some additional steps to consider:

Thanks for reading.

Top comments (1)

Collapse
 
theycallmeswift profile image
Swift

This is super helpful. I recently had to figure a lot of this out myself and it was a bit of a journey. Really glad this resource exists now on DEV!