◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/Advanced
⬡ Advanced/2026-07-01Advanced

Getting Artifacts Out of a Managed Agents Sandbox Safely — Scoped Credentials and Egress Design

Gemini API Managed Agents run in a Google-hosted isolated sandbox. Here is the short-lived, least-privilege credential and egress-boundary design I use to return generated artifacts to my own repository safely.

Gemini API249Managed Agents8Security4Agents8Indie Development16

✦ Premium Article

The first thing I tried when Managed Agents hit public preview was whether I could move part of my usual article-generation pipeline into that sandbox. Having an agent plan, run code, and touch files entirely inside a Google-hosted isolated Linux environment is genuinely appealing when you are otherwise running your own containers.

But the moment I ran it, I hit the least glamorous and most dangerous problem right away: how do you get the artifacts you produced inside the sandbox back into something you control — a GitHub repository or object storage? Build this carelessly and you end up placing a key that can touch your entire infrastructure inside a sandbox that Google hosts. The whole point of isolation flips on its head.

This article focuses on that single concern — egress — and shares the design I actually adopted for the Dolice Labs automation. It also ties in neatly with the 6/19 change that started rejecting requests from unrestricted API keys.

Why design "getting artifacts out" as its own problem

A Managed Agents sandbox is best treated as a stateless environment that spins up per run and disappears when done. Planning, reasoning, and code execution all complete inside it. That is convenient, but it also means your artifacts — generated MDX, built JSON, images — vanish along with the sandbox. So you have to push them somewhere.

The naive path looks like this: you ask the agent to "push to GitHub" and hand it a full GITHUB_TOKEN as an environment variable. It works. But at that instant, the code running inside the sandbox — including code the model wrote, which you do not fully control — can reach every repository that token can touch. In my case, running several sites under one account as an indie developer, one over-broad token leaking puts all four sites in range.

There is one principle here. Give the sandbox nothing more than the ability to write this run's artifact to one predetermined place. No read, no reach into other repositories, no delete. Narrow egress to this granularity and even if the sandbox code goes rogue, the damage stops at "this run's output location gets dirty."

The bad way and the good way to hand over credentials

First, the shape to avoid.

# ❌ Bad: pass a long-lived, broad token straight into the sandbox
agent = client.agents.create(
    model="gemini-flash-latest",
    environment={
        "env_vars": {
            # This PAT can read/write every repo, and effectively never expires
            "GITHUB_TOKEN": "ghp_LONG_LIVED_BROAD_SCOPE_TOKEN",
            # And it even carries the deploy key along
            "CF_API_TOKEN": "cloudflare_account_wide_token",
        }
    },
)

The problem is that scope, lifetime, and reach are all "wide." The token is long-lived, can write to every repo, and touches deploys on top. Since you cannot verify the code running inside the sandbox line by line, you should trim the key down to exactly what that code needs to do.

The good shape is to not hand over a general-purpose token at all. On the caller side (your own controlled server), you issue a short-lived signed upload URL valid for exactly one object this run, and hand only that to the sandbox.

# ✅ Good: caller issues a write-only URL scoped to this run's single artifact
import datetime
from google.cloud import storage
 
def issue_upload_url(site: str, run_id: str) -> str:
    bucket = storage.Client().bucket("dolice-agent-artifacts")
    # Pin the target to a per-run prefix (cannot touch other runs' space)
    blob = bucket.blob(f"incoming/{site}/{run_id}/output.tar.gz")
    return blob.generate_signed_url(
        version="v4",
        expiration=datetime.timedelta(minutes=15),  # expires in 15 minutes
        method="PUT",                               # PUT only. no GET, no DELETE
        content_type="application/gzip",
    )
 
signed_url = issue_upload_url("gemilab", run_id="20260701-01")
 
agent = client.agents.create(
    model="gemini-flash-latest",
    environment={
        # All the sandbox receives is "write this one object, for 15 minutes"
        "env_vars": {"ARTIFACT_UPLOAD_URL": signed_url},
    },
)

The difference is stark. In the bad example the key's reach was "the whole account." In the good example it is "one object at incoming/gemilab/20260701-01/output.tar.gz, via PUT, for 15 minutes." Even if this signed URL ends up in a log, all a thief can do is overwrite this run's output — and it becomes invalid 15 minutes later.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦An egress implementation that hands the sandbox only a 15-minute signed URL — never a long-lived GitHub PAT
✦Locking the egress target to one bucket prefix with a write-only role so blast radius stays contained
✦Turning the 6/19 unrestricted-key rejection into a discipline for scoping the agent's Gemini API key
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ Advanced2026-09-24
I Stopped Asking Gemini 'Is This Right?' and Started Hiding My Answer Instead
Ask Gemini to confirm a label you already chose and it tends to agree. Here is why I compared three ways of asking, chose blind re-classification, how my answer still leaked in, and the routing code that sends only disagreements to a human.
⬡ Advanced2026-09-14
When BLOCK_NONE Changes Nothing: Telling Adjustable Gemini Blocks from the Ones You Can't Move
Some safety blocks go away when you loosen a threshold. Others never will. Here is how to read the response and decide which kind you are looking at before you touch a single setting.
⬡ Advanced2026-09-05
Two Kinds of Video Questions: Why I Send "Find It" and "Prove It Isn't There" Down Separate Paths
Agentic video understanding lets the model decide which parts of a video to watch. That works beautifully for finding things, and it quietly breaks when you need to prove something never appears. Here is how I split my questions, and the coverage check I now run first.