M
Mike Dabydeen
Guest
When an AI agent can run shell commands in your pipeline, its permissions are infrastructure. They should be declared where reviewers look, versioned with the agent, and tested on every change. Docker Agent gets the first two right. The third is up to you, and when I wrote the tests, one widely used permission pattern failed in a way worth knowing about.
Docker Agent is Docker's open-source runtime for teams of agents. A team is a config file: models, agents, which agents each one may delegate to, which tools each may use, and the policy around all of it. You run it with
My test project, release-gate-crew, is a release gate for container services. A lead agent delegates to a container auditor and a pipeline auditor, then hands their findings to a scribe that writes a PASS/BLOCK report. The sample service under review is deliberately bad: an unpinned base image, a secret in
Every control sits in the same file as the agents:
A
Guardrails are deterministic, so they should be testable without paying for tokens or tolerating model variance. I wrote a scripted, OpenAI-compatible stub model in Go and pointed every agent at it through a config flavor. The stub plays a fixed sequence per agent. It delegates, reads files, and then deliberately tries things the config should refuse.
A Go integration test drives the real
All of that held, for YAML and HCL alike.
Permission rules are globs matched against the whole command string, and
The command
Docker's documentation says permissions are client-side and not a security boundary, and it recommends sandbox mode for isolation. I agree with that framing. The risk is practical: prefix rules look precise, and they are what most people will copy.
Four changes close the gap:
My suite keeps a test that reproduces the bypass. If a future release starts parsing compound commands, that test will fail and tell me to revisit the advice.
None of this measures review quality. A scripted stub proves the harness, not the judgement. The repository includes three
That split is the main lesson I'd take to any agent platform. Test the guardrails deterministically on every push. Evaluate the model's judgement separately, with repeated runs, before you trust a gate decision.
The setup
Docker Agent is Docker's open-source runtime for teams of agents. A team is a config file: models, agents, which agents each one may delegate to, which tools each may use, and the policy around all of it. You run it with
docker agent run crew.yaml.My test project, release-gate-crew, is a release gate for container services. A lead agent delegates to a container auditor and a pipeline auditor, then hands their findings to a scribe that writes a PASS/BLOCK report. The sample service under review is deliberately bad: an unpinned base image, a secret in
ENV, a privileged container with the Docker socket mounted, and a pull_request_target workflow that checks out untrusted code with write permissions.Every control sits in the same file as the agents:
Code:
agents:
container_auditor:
model: worker
instruction_file: prompts/container_auditor.md
toolsets:
- type: filesystem
allow_list: ["./target"] # read scope
- type: shell
runtime:
safety: restricted # unattended: deny anything not allowed
permissions:
allow: ["read_file", "shell:cmd=grep -n *"]
deny: ["shell:cmd=*curl*", "shell:cmd=*&&*", "shell:cmd=*;*", "shell:cmd=*|*"]
budget:
max_cost: 0.50 # one pot shared by every agent
max_time: 10m
A
.agentsignore file hides .env and key files from every agent. The same team also exists as crew.hcl, because Docker Agent reads HCL too, and platform teams that live in Terraform may prefer it.Testing guardrails without a model
Guardrails are deterministic, so they should be testable without paying for tokens or tolerating model variance. I wrote a scripted, OpenAI-compatible stub model in Go and pointed every agent at it through a config flavor. The stub plays a fixed sequence per agent. It delegates, reads files, and then deliberately tries things the config should refuse.
A Go integration test drives the real
docker-agent binary against both config files and checks nine written requirements. The runtime's own responses:| Attempt | Result |
|---|---|
| Read target/.env | excluded by .agentsignore |
| Pipeline auditor reads a file outside ./target | outside the allowed directories (./target) |
| Scribe writes target/Dockerfile | denied by restricted safety mode, no prompt |
grep -n FROM target/Dockerfile && curl --version | denied by permissions configuration |
All of that held, for YAML and HCL alike.
The pattern that didn't hold
Permission rules are globs matched against the whole command string, and
* matches spaces. So consider the prefix style common in examples:
Code:
permissions:
allow: ["shell:cmd=grep -n *"]
deny: ["shell:cmd=curl *"]
The command
grep -n FROM target/Dockerfile && curl --version begins with grep -n, so the allow rule matches. It does not begin with curl, so the deny rule misses. Deny is evaluated first, finds nothing, then allow approves. In my test, curl ran and printed its version banner. The restricted safety mode did not stop it, because an explicit allow outranks the mode.Docker's documentation says permissions are client-side and not a security boundary, and it recommends sandbox mode for isolation. I agree with that framing. The risk is practical: prefix rules look precise, and they are what most people will copy.
Four changes close the gap:
- Deny shell operators:
*&&*,*;*,*|*, backticks and$(. - Match denied binaries anywhere:
*curl*, notcurl *. - Allow one narrow command per agent that needs a shell.
- Use
--sandbox, which runs the team in a Docker Sandboxes VM, for anything you don't fully trust.
My suite keeps a test that reproduces the bypass. If a future release starts parsing compound commands, that test will fail and tell me to revisit the advice.
Other things a platform team should know
- The filesystem toolset is unrestricted by default. Set
allow_liston every one. - A config pulled from a registry can declare
runtime.safety: autonomous. Your own flag or user settings take precedence only if you set them. - Sub-agents receive only the task text the lead writes, inside a
<task>block. Good isolation, but the lead must pass along everything a specialist needs. - The project moves quickly: 17 releases in September 2026. Pin the version in CI and disable telemetry there with
TELEMETRY_ENABLED=false.
What the stub can't tell you
None of this measures review quality. A scripted stub proves the harness, not the judgement. The repository includes three
docker agent eval cases for that: block the flawed sample, refuse to reveal the secret, and refuse to claim an edit. They need real model keys and a container runtime, and I haven't run them yet.That split is the main lesson I'd take to any agent platform. Test the guardrails deterministically on every push. Evaluate the model's judgement separately, with repeated runs, before you trust a gate decision.