The allowlist was trusted. The ground it ran on was not
You constrained which commands the agent may run. You did not constrain the environment those commands resolve in.
The standard containment for a tool-using agent is an allowlist. These commands are safe, everything else is refused. It is a reasonable model and it holds right up until the thing being allowlisted is a name rather than a behaviour.
A command name resolves through an environment. Configuration files, path lookup, aliases, project-local settings, plugin manifests, hook definitions. If any of that is writable by content the agent ingests, then an allowlisted name can be pointed at something else entirely, and the allowlist still passes because the name did not change.
This is the shape behind a whole family of recent agent escapes, and it generalises well past coding tools. Anywhere an agent reads a workspace it did not author, the workspace gets a vote on what the agent's own commands mean.
The reason it survives review is that the allowlist looks like the control. It is auditable, it is short, and reading it gives real comfort. The mutable part is the environment, which is rarely in the same document.
The check to run: take your allowlist, and for each entry ask what would have to be true for that name to resolve to different behaviour. Then ask whether any content the agent reads can make that true.
