GEMINI LABJP
SUNSET — Six days until the image generation models shut down: the imagen-4.0 family and Gemini 3 Image models stop on August 17MIGRATE — gemini-3.1-flash-image is the recommended replacement, and it means rewriting generate_images calls as generate_contentCHECK — The same prompt will not necessarily produce the same picture after migrating, so secure any images you still need before the cutoffCLASSROOM — August 17 is also the day Gemini in Classroom arrives on mobile; the web rollout to students of all ages began on August 10DEPRECATION — The Grok 4.1 family shuts down on August 20, and gemini-robotics-er-1.6-preview on August 31, succeeded by the er-2 modelsCHANGELOG — The Gemini API changelog still ends at July 30. The most recent major change remains the GA of Gemini 3.6 Flash and 3.5 Flash-LiteSUNSET — Six days until the image generation models shut down: the imagen-4.0 family and Gemini 3 Image models stop on August 17MIGRATE — gemini-3.1-flash-image is the recommended replacement, and it means rewriting generate_images calls as generate_contentCHECK — The same prompt will not necessarily produce the same picture after migrating, so secure any images you still need before the cutoffCLASSROOM — August 17 is also the day Gemini in Classroom arrives on mobile; the web rollout to students of all ages began on August 10DEPRECATION — The Grok 4.1 family shuts down on August 20, and gemini-robotics-er-1.6-preview on August 31, succeeded by the er-2 modelsCHANGELOG — The Gemini API changelog still ends at July 30. The most recent major change remains the GA of Gemini 3.6 Flash and 3.5 Flash-Lite
Articles/API / SDK
API / SDK/2026-08-02Advanced

Measuring a Guard in environment hooks: 46 Microseconds to Decide, 23 Milliseconds to Start

A record of building a destructive-command guard for Managed Agents environment hooks. A regex denylist let 9 of 20 dangerous commands through; argv parsing reached 100 percent detection. The startup cost turned out to be 500 times the decision cost.

Gemini API208Managed Agents8environment hookssandbox2agent design2

Premium Article

On July 28, environment hooks landed in the Managed Agents feature of the Gemini API. They let you run your own scripts inside the sandbox, before and after each tool call.

The first thing that came to mind when I read the announcement wasn't a use case. It was a moment from a few months back. In an automation pipeline I run as an indie developer, I asked an agent to clean up a working directory, and it took some intermediate artifacts I wanted to keep along with it. Recovery took maybe fifteen minutes. What stayed with me was that all I had afterwards was a log of what had already happened. There was no place to stop it.

environment hooks create exactly that place, right at the tool-call boundary.

So I wrote a guard to block destructive shell commands. I expected it to take half an hour. In practice, my first implementation let nearly half of the dangerous cases through, and my assumptions about its performance turned out to be backwards.

Note: all measurements below come from running the hook script standalone in an isolated Linux environment (Ubuntu 22.04, Python 3.10.12). A hook is contractually just a process that receives a tool call and returns allow or deny, so both the decision logic and the startup cost can be measured before wiring anything into an agent. Argument names and behavior may change, so confirm against the Gemini API Agents documentation before implementing.

Start by writing the list, not the code

Before writing the guard, I pinned down what it was supposed to judge. My mental model of "dangerous commands" was fuzzy at the edges, and designing while implementing guarantees you miss cases.

I built a corpus of 34 commands: 20 I wanted blocked, 14 I wanted allowed. Three categories went in deliberately.

CategoryPurposeExample
Textbook destructionAny implementation should catch theserm -rf /
Same meaning, different spellingVariants of the same operationrm -fr /workspace / rm --recursive --force ...
Deceptive but benignCases designed to trigger false positivesgrep -rn 'rm -rf' /workspace/docs

That third group ended up mattering most. A guard's value isn't the number of things it stops — it's the ratio of what it stops to what it needlessly blocks. A hook that errs too far toward safety gets disabled within a few days.

# corpus.py — fixed evaluation corpus (True = should be blocked)
CORPUS = [
    # textbook destruction
    ("rm -rf /", True),
    ("rm -rf /workspace", True),
    ("rm -rf ~", True),
    # same meaning, different spelling
    ("rm -fr /workspace", True),
    ("rm -r -f /workspace", True),
    ("rm --recursive --force /workspace", True),
    ("/bin/rm -rf /workspace", True),
    ("env rm -rf /workspace", True),
    ("sh -c 'rm -rf /workspace'", True),
    ("cd /workspace && rm -rf .", True),
    ("find /workspace -delete", True),
    ("truncate -s 0 /workspace/db.sqlite", True),
    (": > /workspace/db.sqlite", True),
    ("dd if=/dev/zero of=/workspace/db.sqlite", True),
    ("git clean -xfd", True),
    ("git reset --hard origin/main", True),
    ("mkfs.ext4 /dev/nvme1n1", True),
    ("chmod -R 000 /workspace", True),
    ("mv /workspace /tmp/gone", True),
    # should be allowed
    ("ls -la /workspace", False),
    ("cat /workspace/report.json", False),
    ("python3 /workspace/analyze.py --input data.csv", False),
    ("grep -rn 'TODO' /workspace/src", False),
    ("git status", False),
    ("git log --oneline -20", False),
    ("npm test", False),
    # deceptive but benign (these measure false positives)
    ("grep -rn 'rm -rf' /workspace/docs", False),
    ("echo 'never run rm -rf /' >> /workspace/NOTES.md", False),
    ("python3 -c \"print('rm -rf /')\"", False),
    ("git log --grep='remove -rf flag'", False),
]

The first version let 9 of 20 through

I started the obvious way: a list of dangerous patterns matched with regular expressions.

import re
 
DENY_PATTERNS = [
    r"rm\s+-rf\s+/",
    r"rm\s+-rf\s+~",
    r"mkfs",
    r"dd\s+if=/dev/zero",
    r"git\s+reset\s+--hard",
    r"git\s+clean\s+-xfd",
]
DENY_RE = [re.compile(p) for p in DENY_PATTERNS]
 
def guard_denylist(cmd: str) -> bool:
    """Returns True when the command should be rejected."""
    return any(r.search(cmd) for r in DENY_RE)

Against the corpus:

Destructive: 11 of 20 blocked  (detection 55.0%)
Benign:       2 of 14 blocked  (false positives 14.3%)

Lining up the nine that slipped past made the pattern clear.

Command that got throughWhy it was missed
rm -fr /workspaceFlags in a different order
rm -r -f /workspaceFlags split apart
rm --recursive --force /workspaceLong options
cd /workspace && rm -rf .Target doesn't start with a slash
find /workspace -deleteNot rm at all
truncate -s 0 ... / : > ...Empties the file rather than deleting it
chmod -R 000 /workspaceDoesn't delete, but makes everything unreadable
mv /workspace /tmp/goneDoesn't delete, but the effect is identical

Each of these can be patched with another pattern. But looking at the list, I stopped. Patch all nine and the tenth is the one I didn't think of. A denylist only works up to the limit of the danger I was able to imagine.

The false positives were telling too. echo 'never run rm -rf /' >> NOTES.md and python3 -c "print('rm -rf /')" both got blocked. A regular expression can't distinguish the contents of a string from the structure of a command.

The approach was treating something structural as text. That was the actual problem, not the specific patterns.

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
Measured results across three guard designs, from a regex denylist that missed 9 of 20 destructive commands to an argv-parsing allowlist at 100 percent detection and 0 percent false positives
The decision logic runs in 46 microseconds while process startup costs 23.1 milliseconds, with import re alone accounting for 7.1 milliseconds
A complete, working pre-tool-call guard including wrapper stripping, subshell recursion, and write-path scoping
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

API / SDK2026-07-04
When Two Managed Agents Fight Over the Same Repo: External Leases and Fencing for Isolated Sandboxes
Every Managed Agents run gets its own isolated sandbox, so a local lock cannot stop two runs from touching the same repo or record. Here is how I serialize them safely with an external lease and a fencing token.
API / SDK2026-08-03
Measuring Update Policies for Memory Profiles: The Guard That Cost Me 16 Points of Accuracy
Memory profiles went GA in Memory Bank, making structured memory available to downstream code. I built three update policies and compared them under identical conditions. The one that looked obviously correct turned out to be the worst. Full harness code and the path to per-field TTLs.
API / SDK2026-07-19
Run Managed Agents with background: true So Your UI Never Freezes — An Async Run Pattern for Solo Developers
A background run in Managed Agents returns a run ID, not a finished answer. Here is the minimal polling setup, where it pays off in solo development, and the credential-refresh details worth knowing.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →