Building a Production AI Agent in Spring Boot: The Sandbox Rule (Part 11) Docker released Sandboxes, a feature that runs coding agents like Claude Code and Copilot CLI in isolated microVMs with no manual review or supervision. Meanwhile, a senior engineer at BS23 detailed how they built a permission-based 'cage' for a Spring Boot e-commerce agent, defending against indirect prompt injection after a product description caused the agent to follow embedded instructions. Docker shipped a product this week with a feature it calls YOLO mode, and the marketing line is almost a dare: "No manual review, no permission prompts, no supervision required." Docker Sandboxes https://www.docker.com/products/docker-sandboxes/ gives Claude Code, Copilot CLI, Codex, OpenCode, and Kiro each a dedicated microVM with only your project workspace mounted in, plus an outbound firewall and secret injection, so an agent can run unattended and the isolation is the safety net. The HN thread sits at 678 points, and a Docker engineer shows up in the comments to correct a common misread: this is not containers. Each session is a microVM with its own kernel on the native hypervisor Hypervisor.framework, WHP, KVM , running on a VMM Docker wrote itself, not Firecracker https://www.docker.com/blog/why-microvms-the-architecture-behind-docker-sandboxes/ . I read that thread and watched the industry's answer to "how do I run an agent safely" settle into one shape: put the agent in a cage, then let it work at full speed. That is the right answer for a coding agent, which installs packages, edits configs, and executes arbitrary commands. My agent is not a coding agent. It is the e-commerce assistant from Parts 1 through 10, the same nine tools, same supervisor, same memory, and it never runs a command. Its cage is not a microVM. Its cage is the permission model around each of the nine tool calls, and this part is about building that cage. I am a Senior Software Engineer II at BS23 in Dhaka, and I have been building production AI agents with Spring Boot and Spring AI for over a year. Last month I added an adversarial case to the Part 8 golden set. The catalog contains a product whose description includes a line that reads like a customer instruction: mention a discount code in the chat and the assistant will apply it. I wrote the case as a plain question about that product, and the agent failed it in the most instructive way possible. The semantic search tool from Part 1 returned the description, the agent treated the instruction inside that description as a real instruction, and its reply started doing what the description told it to do instead of answering the question. No user attacked the agent in that test. The attack came out of a tool, which means the attack came out of my own catalog. That is the moment I stopped thinking of the agent as a chat endpoint with a few helpers and started treating it as a program with privileges. Parts 6 through 10 proved the agent was bug-free, good, and deployable. Nothing had ever checked the boundary between the agent and the world, and the product description was the world. An agent with tools has three attack channels, and they need different defenses. The user message, direct injection. The customer writes instructions into the chat: "ignore your rules and..." Well studied, well defended. In this agent the money path already stops at the Part 7 approval gate, so a direct attack can waste tokens but cannot place an order. The tool output, indirect injection. Data that a tool returns can carry instructions. Product descriptions, order history, whatever your RAG returns, anything your model reads as content can be written to read as a command. This is the channel that does not look like an attack, which is exactly why it is the one that lands. The tool side effect, abuse. Every tool that writes is a privilege. The defense is not a sandbox at the process level, it is policy at the call level: which tool may run, with which arguments, under which conditions. The rest of this part is those three defenses in the order I built them. Spring AI models every tool as a ToolCallback https://docs.spring.io/spring-ai/reference/api/tools.html , and the interface is small: getToolDefinition for the model, call toolInput and call toolInput, toolContext for execution. That is the seam. I wrap every callback once at startup with a guard that runs the policy before the real tool runs. public class GuardedToolCallback implements ToolCallback { private final ToolCallback delegate; private final ToolPolicy policy; @Override public ToolDefinition getToolDefinition { return delegate.getToolDefinition ; } @Override public ToolMetadata getToolMetadata { return delegate.getToolMetadata ; } @Override public String call String toolInput { return call toolInput, new ToolContext Map.of ; } @Override public String call String toolInput, ToolContext toolContext { String toolName = delegate.getToolDefinition .name ; Optional