❌

Vue normale

Reçu avant avant-hierInfra

From Dockerfile to Kit: the Docker Sandboxes Kit Specification

Par :Jin Kim
24 septembre 2026 à 18:00

Agents need containment, and a sandbox is only half of it. Something still has to say which agent runs there, what it gets, and what it may touch. That is a Kit: an ordinary OCI image, so the answer travels with the agent and means the same thing on any conforming runtime. Today we published the Docker Sandbox Kit Specification v3, open source under Apache 2.0 at docker/sandbox-kit-spec. Here is why I wrote it.

Everything that makes an agent useful is a grant

I run a lot of agents. They write code, run tests, install dependencies, call APIs, and work on infrastructure while I do something else. None of it happens without access, so I grant it one piece at a time: a bind mount, a token with broader scope than the task needs, a firewall rule that was quicker to open than to narrow. Each grant is reasonable on its own. Together they take back the isolation I was relying on, and none needed an exploit. The holes are configuration, added on purpose, usually by me.

I am worse at taking any of it back, and I could not reproduce the grants my setup depends on. No file records them. They live in shell history, dashboards, and my memory. I cannot hand that to a colleague or diff it against last week.

Containers package applications. Sandboxes contain agents.

A container packages applications. It shares the host kernel and uses namespaces and cgroups to give one fixed workload its own view of the filesystem, network, and processes. That is the right tool for software that runs, does its job, and touches only what it was handed.

An agent, however, is a probabilistic actor. It decides what to do next and then does it, to my filesystem, network, credentials, and cloud account. It will install a package that needs root, open a port nobody planned for, and try the next thing when the first is blocked. A container was not built for that: the boundary is the same kernel the actor is probing.

A Docker Sandbox is a microVM with its own kernel, so the boundary sits below anything the model can reach or rewrite. Inside one I can hand an agent root and let it loose, because the damage stops at the sandbox boundary. The sandbox is what lets me run an agent with the safeties off.

But an empty sandbox is not an environment. Something still has to say which agent runs, which tools and MCP servers it gets, which skills and instructions shape it, and exactly what it may touch.

What a Dockerfile cannot say

A Dockerfile answers everything about the software itself: how it is built, what gets packaged, how it starts. It was never standardized; OCI standardized the image it produces and how registries distribute it. What a Dockerfile does not describe is the outside: networks, credentials, volumes, tools, context. That half has lived in docker run flags, a Compose file, a CI config, and someone’s memory. Unversioned, unreviewable. A Kit writes it down with the content.

One image, one digest

If you have used sbx, you have used Kits. This is the third version of the format, and the change that matters is that a Kit is now an ordinary OCI image rather than its own artifact: no media type, no sidecar file, nothing for a registry to learn. The manifest carries the declarations in one annotation, vnd.docker.sandbox.kit.descriptor; the layers carry the content.

A Kit therefore builds with docker buildx build, pulls with docker pull, gets scanned and signed by the tooling you already run, and works in a FROM. Pinning the digest pins content, declarations, and metadata together. The tooling and distribution path are free; the format is something you learn: a grammar, a page per capability type, provides and requires, kind: set.

Two kinds of Kit exist. A workload runs and supplies the root filesystem. A mixin is an overlay: a CLI with its network rule, a credential binding, context for an agent. You launch one workload and any number of mixins.

Authority you can read

Part of the GitHub CLI mixin in the repository:

capabilities:
  - type: com.docker.sandbox/network-policy@2
    config:
      runtime:
        allow:
          - github.com
          - hosts: [api.github.com]
            methods: [GET, HEAD, POST, PATCH, PUT, DELETE]
        deny:
          - hosts: [api.github.com]
            methods: [DELETE]
            paths: [/repos/**]

  - type: com.docker.sandbox/credential@1
    optional: true
    config:
      service: github
      phase: runtime
      apiKey:
        name: GH_TOKEN
        proxyManaged: true
        inject:
          - {domain: api.github.com, header: Authorization, format: "Bearer %s"}

Read it as a permission slip. This Kit asks to reach GitHub and nowhere else, and for most of the API but not deletes under /repos/**, because deny wins. The token that can open a pull request cannot delete the repository. The credential is proxy-managed: a conforming runtime injects the real value into requests to the named domains, and inside the sandbox there is only a sentinel.

Two words carry weight: asks and conforming. A Kit grants itself nothing. Each entry is a request, and the host decides. A conforming runtime, one that implements the behaviour the specification describes, blocks hosts not on the list. Without one, the annotation is inert: an image and no enforcement. Docker Sandboxes is the first conforming runtime.

Everything a Kit needs goes through that one list, typed and versioned. Grants (network rules, credentials, volumes, ports, devices, skills paths) count toward “what may this Kit do”; entries that ask the runtime to act, like a lifecycle hook, do not. A required request the host cannot satisfy refuses the launch, rather than starting an agent with less authority than it declared, or more.

Composition is a function, not a sequence

Container images never solved multiple inheritance: a Dockerfile stage has one FROM. Mixins are overlays ordered by the dependency graph the Kits declare through provides and requires, never by the order you typed the flags, so the same set always composes to the same image.

The resolver is strict on purpose. Every requires is satisfied from inside the set or resolution fails; nothing is fetched to cover a gap. Exactly one workload is allowed. Two Kits providing the same name fail rather than one silently shadowing the other (composing the Claude workload with the Claude mixin is the canonical mistake). Where Kits overlap, declarations reconcile: network rules union, hooks run in dependency order, guidance becomes one document, licenses union. Incompatible requests are an error, not a coin flip.

A kind: set descriptor names other Kits; publishing it runs the same coherence rules at build time and merges them into one ordinary Kit. An incoherent set fails at your build, not at someone else’s launch.

The diff is the review

The Claude Code Kit in the repository declares the hosts it asks to reach, its credential, the volumes that persist between sessions, and its install and startup hooks. When the next version asks for another host or a second credential, that is a change in authority, not a software update, and it shows up in the pull request as added lines a human can refuse.

Review depends on somebody reading the diff, so the specification defines a second gate that does not. Every descriptor reduces to a normalized set of everything the host would have to grant; a runtime that gates updates records that set and compares the next version against it. A version inside what was granted may apply without asking. Any widening stops and asks, and removing a deny rule counts: if a later gh Kit dropped DELETE /repos/**, the runtime holds the upgrade. That is why the declarations had to live in the artifact, not beside it.

Why this is a specification and not a feature

A Kit that stopped meaning anything when run somewhere else would be lock-in, not a trust boundary. So the grammar is normative, every capability type has its own page describing what a conforming runtime must implement, and types version independently (network-policy@1 and @2 both exist today). Two conformance suites ship with it: one judges whether an artifact is a conforming Kit, the other whether a runtime behaves as the pages say. Every normative statement is covered by a check or a written waiver.

Docker maintains the specification today, and it should not stay under a single vendor: a format for deciding what an agent may do is worth less if it belongs to whoever sells you the runtime. Docker Sandboxes will be a first-class implementation, not the only one. If a Kit you want cannot be expressed, or a runtime duty cannot be implemented as stated, open an issue.

Try it

sbx is our sandbox CLI (brew install docker/tap/sbx). From a checkout of the repository:

cd examples
sbx run ./hello --kit ./gh .

Edit a descriptor and only that Kit rebuilds; docker buildx build publishes it to any registry. Docker Cloud Sandboxes runs the same Kits with the same trust model on elastic capacity. The specification, capability pages, and a worked tour are in docker/sandbox-kit-spec.

Not only agents

Agents forced this into the open because the authority they ask for is so large, but ordinary workloads have always arrived with unwritten expectations: the endpoints they call, the credentials they need, the volume that must survive a restart. That knowledge has lived in a Helm chart, a runbook, or a colleague. It is the same gap, less alarming when a web service gets it wrong. This specification is where any software writes down what it needs from the world around it; agents were the case urgent enough to have it built.

Dockerfiles made software reproducible. Kits make authority reproducible.

Docker and CNCF partner on an open spec for agent permissions

Par :Jin Kim
24 septembre 2026 à 18:00

Docker and CNCF: Making what an agent may do as portable as the agent itself

Ten years ago, the software industry faced a choice. Every vendor could ship its own image format and its own runtime, and developers would have to pick a side. Or the industry could agree on one artifact. The second option won. Docker donated its image format and the Runc runtime to the Linux Foundation, and the Open Container Initiative (OCI) formed around them. Today, a Docker image built anywhere can run anywhere. The format is the backbone of the cloud native ecosystem and a de facto standard.

Now, we see a similar problem forming around agents. There is no shared format for what an agent is allowed to do. We are proposing the same kind of answer: one artifact, built on OCI, governed in the open.

The same problem, for agents

Containers were built for immutable software. The image is the application. If you want to change it, you rebuild it, and it behaves the same way everywhere. That’s why a container image describes how software is built and says nothing about what it may do once it runs. For a web service, that was fine. It got a network and a port, and that was enough.

Agents are mutable by definition. Claude Code and Codex install packages, call APIs, and use credentials on your behalf. They change the environment they run in, and they decide what to do next. So, every team writes its own rules for what an agent may reach: a network rule here, a token there, a volume mount to get a task done. Those rules live in shell history, in dashboards, and in someone’s memory. A few months in, nobody can answer a simple question: what is this agent allowed to do?

Every team answers that on its own today, and every runtime vendor could ship its own way of answering it. That’s the kind of fragmentation OCI was created to prevent.

What we are announcing

Kits are not new. Kits have been part of Docker Sandboxes as the way you package an agent, its tools, and what it may reach into something a team can share. What’s new is the artifact. A Kit is now an ordinary OCI image, and the format that describes it is open.

Today at WeAreDevelopers, we announced the Docker Sandbox Kit Spec, open source under Apache 2.0. A Kit carries three things in one image: the agent, its tools, and a typed list of everything it asks to reach, such as hosts, credentials, and volumes. Because the list is part of the image, pinning the image pins the agent and its requests together.

A Kit is not a new artifact type and not a fork of any OCI specification. It uses an extension point OCI already defines. It builds, pushes, pulls, signs, and scans like any image you run today, because it is one.

Today, we’re bringing the spec to CNCF, under their neutral governance, just like we did when the image format went to OCI.

Why it matters

Adoption is free. Because a Kit is an OCI image, every registry, scanner, and signing tool you already run handles it. There is nothing new to deploy.

The answer travels with the agent. Because the requests are in the image, “what may this agent do” has one answer. A teammate can pull it. A reviewer can diff it. A conforming runtime can enforce it. When a new version asks for more, the change shows up as added lines someone can refuse.

An ecosystem, not a format

Docker has always had an ecosystem-first mindset. The Dockerfile mattered because anyone could write one, any registry could store the result, and any runtime could run it. Kits follow the same approach, and we did not build them alone.

We have worked with AWS, Box, Datadog, Dynatrace, JFrog, NanoClaw, OpenClaw, Palo Alto Networks, Snyk, and more to build Kits for their tools. Cloud platforms, observability, security, artifact management, content, and agent frameworks are all represented. The Kits we unveiled during the opening keynote at WeAreDevelopers today are the product of all that work, and they are the first of many.

MCP gave agents a standard way to talk to a tool. Kits give the ecosystem a standard way to publish the whole arrangement: the agent, its tools, and what it asks to reach, in one image anyone can pull. That’s what turns a format into a supply chain. For example, a database vendor can publish a Kit that connects any agent to its service with the scope it recommends.An agent maker publishes its own Kit, so the request list comes from the people who built the agent. A platform team publishes one for the company, and every engineer starts from the same place.

None of that happens if the format belongs to one vendor. A standard for deciding what an agent may do is worth a good deal less if it belongs to whoever sells you the runtime. Docker Sandboxes is the first runtime that enforces it. It should not be the only one, and under CNCF governance, it will not be.

Containers made software portable. Kits make authority portable: the set of things you deliberately hand over to an agent travels with the agent, in the same image, and means the same thing wherever a conforming runtime opens it.

“Standards are what let an ecosystem move fast without fragmenting, and few companies understand that better than Docker. By delivering Sandbox Kits as standard OCI images, Docker is giving the industry an open, repeatable way to package an AI agent, its tools, and its guardrails as one artifact. OCI is the foundation the cloud native ecosystem is built on, so a standard for agents that builds on OCI reaches the whole ecosystem at once. The CNCF welcomes this, and we’re excited to work with Docker and the community on making it broadly adopted.“

Chris Aniszczyk

CTO at CNCF

Build a Kit

If you make a tool agents use, publish a Kit for it. If you run agents, start from one and share it with your team. The specification, the capability pages, and a worked tour of a real Kit are at docker/sandbox-kit-spec. If there is a Kit you cannot express, or a rule a runtime cannot implement, open an issue. Every Kit published and every issue filed is how a standard gets built.

Coding Agent Horror Stories: The Command You Already Approved

18 août 2026 à 15:00

This is Part 5 of our AI Coding Agent Horror Stories series, a look at real security incidents involving AI coding agents, and how Docker Sandboxes contain agent execution at the boundary rather than at the command line.

In Part 1, we walked through six categories of AI coding agent failures and why they keep happening. The agent runs as you, with your filesystem permissions and your credentials, and nothing sits between the model’s decision and the shell’s execution. Part 2 went deep on the rm -rf ~/ incident. Part 3 moved the same problem into a production cloud environment. Part 4 followed the credentials themselves through a supply chain attack. 

This one is about the safety net. Most teams running a coding agent today have some version of a list of commands the agent may run without asking, and the assumption underneath it is that anything dangerous will show up as a prompt you can refuse. In January, researchers at Pillar Security showed that the assumption doesn’t hold.

Today’s Horror Story: The Approval That Ran Something Else

On January 14, 2026, researchers at Pillar Security disclosed CVE-2026-22708, a flaw in Cursor. When the agent ran in Auto-Run Mode with an allowlist enabled, a handful of shell built-ins executed without appearing in that allowlist and without asking for approval. Anything that could get text in front of the agent, a README or a dependency or an issue comment, could use them to change environment variables silently. A command the developer then approved, something as ordinary as git branch, would run the attacker’s code instead. Cursor rated it High and patched it in version 2.3.

No memory corruption was involved here and no permission was escalated. The developer was shown an accurate prompt, approved a command that was genuinely harmless, and got arbitrary code execution anyway, because the meaning of that command had been changed a minute earlier by something they were never shown.

In this issue, you’ll learn:

  • How shell built-in slipped past an allowlist that was working exactly as designed
  • Why the attack still worked when the allowlist was completely empty
  • What Docker Sandboxes contain here, and the two things they do not
  • How kits, organisation policy and audit logs cover what a per-laptop allowlist misses
image1 1

Caption: Comic illustrating how an injected instruction changes environment settings without triggering an approval prompt, so that a command the developer legitimately approves runs the attacker’s payload instead.

The Problem

Typically, programs read settings from their environment when they start up. Git checks one called PAGER to work out which program displays its output, and Python checks one called PYTHONWARNINGS. Nobody thinks about these, which is rather the point. The commands that change them are shell built-ins, and Pillar’s research names export, typeset and declare specifically, a detail reported independently at disclosure. Built-ins are not programs sitting on disk, and the checker was looking for programs on disk, so they went through without ever being surfaced.

Which means the whole attack is two lines.

# This one runs silently. You are never asked.
export PAGER="open -a Calculator"

# This one you are asked about, and you say yes, because obviously.
git branch

Git looked up PAGER to work out how to show the branch list, found the attacker’s command sitting in it, and ran that instead. Pillar notes this worked even with a completely empty allowlist, which is the most restrictive setting on offer.

An allowlist checks whether the command in front of it is on the list, which is fine for cutting down interruptions, and nobody wants to approve ls for the ninetieth time in a morning. But the name of a command does not tell you what that command will do. The check reads the name, waves it through, and the setting that decides what actually happens was changed a minute earlier by something the check was never shown.

Cursor’s documentation now describes the allowlist as best-effort and warns that bypasses are possible. Pillar went further and argued that agents should be handed full command execution inside an isolated environment, and that the industry ought to deprecate allowlists altogether.

The Scale of the Problem

None of the underlying trick is new. Pillar’s write-up points back to Elttam’s 2020 research on environment variables, which showed how these settings could be turned into code execution.

It sat there for six years without troubling anybody very much. Pulling it off meant already being on someone’s machine, setting several things in the right order, running each step yourself, and anyone with that much access had faster ways to cause damage.

Then coding agents arrived and removed every one of those obstacles at once. They act on instructions found in files they were told to read, they run several steps in a row without stopping to check, and they run as you. A technique that used to need somebody sitting at your keyboard now arrives in a repository you cloned this morning.

It is the same shape as the s1ngularity attack from Part 4. There, a poisoned package borrowed an agent that was already logged in. Here, poisoned text borrows a command that was already approved. Neither one breaks anything. Both of them use permission that was handed over deliberately, for something nobody intended.

Technical Breakdown: How the Attack Works

image2

Caption: Diagram showing how an injected instruction changes the shell environment out of sight, so that an allowlisted command carries the attacker’s payload when the developer approves it.

The attack has two halves, and the split between them is the entire trick.

1. The half you never see

The agent reads a file it was asked to read, and that file contains an instruction meant for the agent rather than for you. Built-ins then quietly set the environment. Nothing appears on your screen.

Pillar demonstrated a longer version of this, chaining several settings together, PYTHONWARNINGS, BROWSER, and PERL5OPT among them, so that every later python3 command on that machine would run attacker code. The details differ, but the principle is the same: change what a program reads at startup, and you change what it does.

2. The half you approve

Then you run git branch or python3 script.py, or the agent runs it for you under your allowlist. These are the commands people add to allowlists to stop the constant interrupting, so the better tuned your list is, the more reliably the trigger fires. The payload runs with your permissions.

Some variants skip the approval altogether. One writes extra lines into ~/.zshrc, so the code runs again every time you open a terminal. You could finish the project, delete the repository, and still be running it next month.

The Impact

The full chain in Pillar’s research ends with the victim’s SSH private keys leaving the machine.

Work backwards and the whole thing started with a piece of text in a file, read by an agent doing exactly what it was asked to do. No memory bug. No privilege escalation. Nothing in any log that looks the slightest bit out of place.

Pillar reported it in August 2025 and the fix shipped that January. Cursor engaged with the report and made a real change, so anything the parser cannot classify now requires approval, which closes the paths that were demonstrated. Five months is a fair measure of how awkward this is to fix at the layer where it was found rather than a complaint about the vendor.

The wider problem has not gone anywhere, because it was never really about shell built-ins. It is about a check that studies the command while somebody rearranges the furniture around it.

image4

Caption: Diagram showing the same payload running inside the microVM, and what it can and cannot reach from there.

How Docker Sandboxes Contain This at the Execution Layer

Docker Sandboxes run AI coding agents in isolated microVMs, each with its own kernel, filesystem, and deny-by-default network, so a compromised dependency an agent pulls cannot reach the host, its credentials, or other workloads. Inside that box the agent can run anything, including with sudo, which is exactly what Pillar recommends. There is no allowlist to slip past. We made the longer argument for why a shared kernel is the wrong shape for this in The Untrusted Autonomous Workload.

So run the same attack again, this time in a sandbox, and watch where it gets to.

The injection still lands. The environment gets changed, git branch still triggers it, and the payload runs. Nothing about a sandbox stops that. Then the payload goes looking for your SSH key and does not find one. Your home directory sits on the other side of the boundary, so there is no ~/.ssh/id_rsa inside the box to copy.

It can still use the key. Sandboxes forwards an SSH agent socket into the box so that ordinary work like git push keeps working, which means code inside can ask that agent to authenticate on its behalf. It cannot take the key anywhere, but it can borrow it for as long as the sandbox runs. Your network policy is what limits that, since SSH needs a rule naming the exact destination address and port before it connects to anything.

The ~/.zshrc trick fails outright, because that file lives on your host and a poisoned copy written inside the box disappears along with the box.

Getting data out is harder than people expect. HTTP and HTTPS leave only through a proxy on your host that checks every request against your rules, anything else over TCP needs a rule naming the address and port, and UDP and ICMP are blocked outright.

Two caveats, both stated plainly in Docker’s security documentation. The first is your workspace, which is live on your host by default, so Git hooks and Makefile targets are still within reach and a poisoned hook will not turn up in git diff. Running with --clone hands the agent its own copy.

The second is the shared agent skills store. Supported agents mount the same host-side store read-write unless you opt out at creation time, which is what lets an agent refine a skill and keep it. Every sandbox sharing that store sits inside one trust boundary, so a skill modified inside one becomes an input to the next that loads it. The store is sandbox state though, and a modified skill does not by itself execute on your host, so the risk runs sandbox to sandbox rather than sandbox to host.

Isolation has its own seams. In July, Pillar published a series of sandbox escapes across four coding agents, and the mechanism was never a broken sandbox but a file written inside one that a tool outside later trusted. Both caveats above are that shape.

None of this stops the injection. It changes what the injection can get to, which is the only part of this problem with a dependable answer.

Codify the Boundary with Kits

image3

Caption: Diagram showing how a kit declares an agent’s tools, files and network rules, while real credentials stay on the host and are injected by the forward proxy on the way out.

The allowlist failed here partly because it is a list, edited on each laptop, that an injection can reach around. Kits are Docker’s answer to the editing-on-each-laptop half of that.

A kit is a declarative YAML artifact that extends a sandbox agent with credentials, network policies, environment variables, startup commands and files. Rather than every developer maintaining a personal allowlist, you write the boundary once, deny-by-default network plus only the destinations a task genuinely needs, and hand the same kit to everybody. It gets reviewed, versioned and diffed like any other file in the repository. The kit spec reference covers the fields, and docker/sbx-kits-contrib has working examples.

This lands directly on the SSH question above. A forwarded SSH agent is a live credential limited only by network policy, so leaving that policy to whoever remembers to run sbx policy deny is the same per-laptop weak point this whole post has been complaining about. A kit can bake the network rule in, so untrusted work has no SSH egress unless the destination was declared up front.

What This Looks Like in Practice

The vulnerability is in the editor, so what you want is the setup that puts the editor’s terminal inside the box. Cursor is built on VS Code and connects the same way, over Remote – SSH, with the editor staying on your machine while files, terminals and extensions run in the sandbox. You will need Docker Sandboxes 0.37.0 or later, SSH access configured, and Cursor’s Remote – SSH support installed. The Cursor integration guide has the full walkthrough.

# One-time setup: configure your SSH client for sandboxes.
sbx setup ssh
# Check the sandbox is reachable, then open the Command Palette,
# run Remote-SSH: Connect to Host, and enter <name>.sbx
ssh demo.sbx
# See what this sandbox is currently allowed to reach.
sbx policy ls
# Shut egress down and open only what the task needs.
sbx policy deny network "**"
sbx policy allow network "github.com,registry.npmjs.org"

Those last two commands come with a catch. If your organisation has governance switched on, the org policy replaces local policy and sbx policy allow and sbx policy deny will have no effect on your machine. You can spot it in the output of sbx policy ls, which begins with a Governance: Managed by <org> line when that is the case. Depending on how admins scope things, some rule types may be delegated back to local control, but a local allow will never beat an organisation-level deny.

Same editor, same agent, same allowlist, same payload. All that changed is which machine the terminal is on.

What happensOn your laptopInside a sandbox
The payload runsYesYes
Where it runsYour machine, as youA microVM with its own kernel
Your SSH key fileCan be read and copiedNot there
SSH authenticationAvailable, key includedAvailable, key stays outside
The ~/.zshrc trickPersists indefinitelyGone with the sandbox
Sending data outOpen by defaultOnly where policy allows
Who sets the rulesEach developerThe organisation
Evidence afterwardsNoneA logged policy decision

Making This Hold Across a Team

A kit gets the boundary out of one developer’s head and into a file the team shares, but a file can still be ignored or edited on the machine that matters. Docker AI Governance moves the settings up one more level. Network and filesystem rules are defined once by your admins and reach developers through the login they already use, so there is nothing to configure per machine and nobody quietly reopening what security closed. A shared kit is the boundary as a suggestion. Governance is the boundary as a ceiling.

The part that matters most for this story is the record it keeps. What made CVE-2026-22708 work was that the first half was invisible, with no prompt and nothing written down anywhere you would think to look. Under governance every policy decision produces an event carrying the user, the timestamp and the rule that fired, and those events stream into whatever SIEM your security team already uses.

So an attack that succeeds inside the sandbox and then reaches for somewhere it should not leaves a trail behind it. That is a good deal better than a check that finds nothing wrong and mentions it to nobody.

Best Practices

1. Treat export like any other command. Anything that changes environment settings can change what your next command does, even when that next command is on your allowlist.

2. Do not mistake an allowlist for a boundary. It reduces interruptions. The vendor documentation now says outright that it is best-effort and not a security guarantee.

3. Isolate before the first command, not after something looks wrong. Untrusted means anything you did not write and have not read, which covers most of a dependency tree.

4. Use --clone for code you have not vetted, and opt out of the shared skills store. Otherwise Git hooks and build scripts stay live on your host, a poisoned hook will not appear in git diff, and a skill modified inside one sandbox is waiting for the next sandbox that loads it.

5. Remember a forwarded SSH agent is a live credential. The key file staying on your machine is not the same as the key being unusable, so restrict egress for untrusted work.

6. Read your own policy. Run sbx policy ls. Deny-by-default with a long allow list is closer to allow-by-default than it looks.

Take Action

  • Install Docker Sandboxes. Visit the Docker Sandboxes documentation to install sbx and run your first agent inside a microVM.
  • Connect your editor. The Remote – SSH integration puts your terminals inside the boundary while the editor stays where it is, so your workflow does not really change.
  • Codify the boundary with a kit. Define the network and credential rules your team needs once, and hand the same artifact to everybody instead of a personal allowlist.
  • Read the security model. The documentation is straight about what is isolated and what is not, including the workspace and shared skills store behaviour that --clone and the opt-out flag change.
  • Turn on audit logging. Docker AI Governance streams policy decisions into your SIEM, which turns a silent bypass into something somebody can actually investigate.

Conclusion

The uncomfortable thing about CVE-2026-22708 is that nobody in the story did anything wrong.

Cursor built an allowlist, which is what everyone asked for. The developer approved git branch, which any of us would have approved. The check inspected the command and found it acceptable, which is exactly its job. The attack worked anyway.

Getting an agent to correctly judge every instruction it reads is a problem that gets harder as agents get more capable, and it has no clean ending. Limiting what an agent can reach is a problem we solved a long time ago. The more useful move is to stop needing the first one, and to write down what the agent may reach as an artifact you can review, rather than a list each laptop keeps for itself.

Coming up in our series: Issue 6 looks at the ClawHub infostealer campaign, where malicious skills reached developer machines through a marketplace ranking exploit, and at what sandboxed skill execution, and that shared skills store, change about a registry you cannot personally audit.

Learn more

❌