October 11, 2026
Your Agent Can Execute Code. Now What? Defining Its Authority
An agent that can execute code inherits the reach of its tools and connected services. Its sandbox must keep that reach within the authority assigned to the task.
A cleanup request with wider reach
While running integration tests for a feature, I saw an agent propose Docker cleanup commands for the test environment. A hook caught the proposal, so the commands never ran and no data was lost. Had they run, the cleanup could have reached unused volumes from other projects on my development machine. Docker has no idea which repository inspired the cleanup.
The tests concerned one project, and the proposed cleanup addressed a Docker daemon shared with others. Giving an agent code execution means deciding how far its actions may travel through the tools and services available to it.
The daemon behind the command
The Docker CLI sends requests to a daemon that manages containers and volumes across projects. This becomes especially consequential when an agent runs inside a container with the daemon socket mounted. Docker documents that this connection gives the container full access to create and manipulate the host's Docker daemon. The daemon can launch another container with host paths mounted into it. Running the agent's process in a container leaves that service connection available, along with the authority it grants.
The proposed commands show why the daemon's inventory matters. docker rm -f -v force-removes specified containers and their associated anonymous volumes, leaving named volumes. docker volume prune -a removes unused local volumes, including named ones, across the daemon. Removing a container can make one of its named volumes eligible for the prune if no other container references it. The prune can also reach unused volumes from unrelated projects. A volume attached to another container remains in use. One saved for a later run may be unreferenced and eligible for deletion.
The hook caught this proposal at the command interface. Its coverage depends on what that interface sees and recognizes. An agent with daemon access could ask the same service to act through another client or the API. A command filter is useful on my development machine. It cannot define the reach of a runner connected to a shared daemon. Shared execution needs enforcement at the daemon-facing service for every run, regardless of anyone's local hook configuration.
If integration tests need containers, I would let an application-owned launcher create them. The test process receives the service endpoint and can request cleanup later. The launcher removes resources it created for that task, using IDs it retained when they were created. A mistaken request for someone else's volume is rejected there. The test process has no daemon socket or general prune operation.
The launcher's creation interface needs the same care as cleanup. If it accepts any image, host mount, and network setting the agent supplies, it becomes another way to exercise the full Docker API. A request for a test database can map to an approved image on a network made for that run. The application fixes how that requested service is created, including its mounts and network. Ownership records would do little good if the creation call could first mount another project's files.
This costs some flexibility. A test that needs an unusual image may require a new launcher capability or a separately authorized run. I would pay that cost on a shared daemon. A general Docker client can create its own mounts and networks, expanding what the test can reach after it starts.
A project-looking container name is weak evidence of ownership, especially if the agent can choose names and labels itself. The launcher uses its own record of created IDs and the task identity supplied by the application. Letting the agent claim another task's identity would turn that record into an invitation to remove someone else's resources.
Other routes out of the job
Test code inherits the access given to the agent's shell. A dependency's test script can read a mounted directory or make an HTTP request without announcing either action in the agent's tool call. The Docker cleanup was a visible proposal. Executed code can use the same permissions quietly.
The agent needs to edit source files, so it needs a writable workspace. Docker bind mounts give container processes access to the mounted host path and are writable by default. Mounting a home directory for convenience would expose other checkouts and local credentials. Even a read-only mount would let the process read them. I would supply a copy of the selected checkout and copy back only the intended changes. That copy still needs inspection if the repository itself contains secrets.
The mount and network decisions meet here. A test process with read access to a credential file and permission to make outbound requests can disclose that credential. A read-only flag preserves file integrity while permitting reads. It is tempting to review the filesystem and network settings in separate conversations, especially when different teams own them. The process receives both at once.
Integration tests often need a database. A disposable instance with credentials scoped to that instance gives the test something it can change without touching a developer's existing data. It can be discarded after the run. This also makes a network decision unavoidable. Docker containers can make outgoing connections by default, and a container on the default bridge can reach services beyond the host when the host has network access.
For tests using local fixtures, external networking can be disabled. Docker's --network none leaves only loopback. A test database can sit on a network created for that run. A dedicated Docker bridge separates its containers from those on other networks and permits external access by default, so egress needs a separate rule. The database is one permitted destination. Access to the machine's other internal services requires its own decision.
Network reach becomes more consequential when the process holds a credential. Put a broad cloud token in the test environment and every child process can use it until it expires. A shorter lifetime limits exposure after the run, while the token's permissions govern what happens during it. Test fixtures or a task-specific identity give the work a narrower grant.
Keeping the credential outside the test process changes the risk, provided the service holding it checks each request. Imagine an internal deployment service the agent can call. The agent never sees its token, yet an API accepting an arbitrary deployment target would still let it direct that service's authority. The service needs the authenticated task identity and a rule for the target it may affect.
For a test task, the permitted operation might be to inspect a build result or update a disposable environment. A deployment service that accepts a production target because its own credential allows it has transferred that privilege to the caller. The service has to evaluate the caller's task, not merely whether it can complete the requested operation.
What a refusal should mean
Suppose the agent asks to remove a volume. The cleanup service checks its ID against the resources created for this run. If the volume is absent from that record, it returns a denial naming the target and confirming that it made no Docker call. That clear result lets the agent revise its plan or request a specific exception. A generic shell failure might invite another attempt with different syntax. If the deletion is justified, an authorized person can review that exact target and the launcher can perform it. Approval covers one effect and expires with it. “Allow Docker” would grant far more than the request requires.
A daemon-wide prune has no task-owned target to present for that approval. It stays unavailable even when the agent considers it the fastest way to make tests pass. The application can offer cleanup limited to this job's recorded resources.
There may be a genuine problem with a resource outside that record. An operator can investigate it with the wider context of the shared machine. That investigation is a separate action, with its own identity and tools. Expanding the agent's existing shell would make every later command inherit the exception granted for one cleanup request.
The actor performing the deletion must enforce that restriction. An instruction telling the model to ask first may improve its choices. The launcher's ownership check and approval record decide whether any volume is removed, including when the agent invents a plausible container name. A hook can catch dangerous command strings in a development workflow, as mine did. The execution path needs to remain constrained when the string changes.
An operator should be able to establish what was requested, what the system denied, which approval authorized an exception, and what the privileged service executed. The agent's transcript may explain why it asked. The launcher's record distinguishes a blocked proposal from an action on the host.
Isolation follows the neighbouring work
The remaining risk depends on what shares the host. A container can constrain filesystem and process access while sharing the host kernel. NIST's container security guide identifies the shared kernel as a larger cross-workload attack surface than a hypervisor boundary. Code supplied by unrelated users may warrant VM-based execution or dedicated hosts. For an engineer running their own agent against their own project, a constrained container may be proportionate. The choice carries costs in startup time and capacity, and it follows the sensitivity of the neighbouring work.
A VM changes the isolation between code and host kernel. It does not change the permissions of a privileged service deliberately made reachable from inside it. The Docker and deployment examples would require the same service-level checks in either runtime.
Resource limits fit this decision too. A test that loops or floods its logs can consume shared capacity without reaching another project's files. Docker applies no CPU or memory limits by default, so the job needs an enforced budget when other work depends on the host. A limit can stop a legitimate slow test, and the result should say which limit it reached. The job cannot grant itself a larger budget from inside the runner.
I would review an agent runner by mapping everything it can reach, then comparing that map with the task's assigned authority. When it asks to clear Docker resources, the system should be able to identify the volume this job owns or refuse the request. If the available operation is a daemon-wide prune, there is no project boundary for that request to respect. The agent's scope error has become the host's scope error.