Back to blog

The command you checked wasn't the command that ran

A trusted hook turned echo into a deletion in our lab. Here's how it happened, and where your security check should run so you can give coding agents more room to work.

A terminal where 'echo bluebear' is followed by four 'removed' lines for a project directory, over a blurred blue silhouette

This investigation started with a pattern we kept seeing in customer telemetry: the command that reached execution wasn’t always the command the security check had approved moments earlier. A trusted hook had rewritten it along the way.

At first, the rewrites looked harmless: a monitoring tag, a bit of instrumentation. But then, at one customer, we saw a rewrite that wasn’t harmless at all. The approval prompt showed one command, but the execution path carried another. And the difference wasn’t cosmetic.

That difference raised a question nobody had tested: if a trusted component can change a command after the check but before it runs, what happens when the rewrite isn’t benign? The safety of the whole pipeline rests on one assumption: that the thing you approved is still the thing that executes.

So we took this scenario into the lab. Not to prove that hooks rewrite commands, they often do, but to find out what happens when the rewrite turns a harmless command into something destructive.

How teams usually think about hooks

Two concepts make teams feel safer about running AI coding agents in real repositories: trusted and monitored. A hook is code that runs automatically at a defined point in the agent’s workflow, for example just before a tool executes. You trust a hook because you approved it. You monitor what the agent is about to run because you want visibility before anything reaches the machine.

That setup sounds reasonable. But in our lab, it still deleted a directory while the agent appeared to run echo.

Your first reaction is probably the right one: of course a trusted hook can do damage. You trusted code that can run code. If that were the whole finding, we wouldn’t bother writing this post. A trusted hook could simply delete the directory itself and skip the agent entirely.

But that is not what happened. The destructive action went through the agent’s own execution path. A trusted hook changed the command, policy evaluated the rewritten command, the agent skipped the approval prompt, and the action ran through the agent’s normal tool flow.

That distinction matters. The hook changed the action that the agent itself was about to execute.

So the real question is: what happens when a trusted component changes an action partway through your security pipeline?

Why the deny rule didn’t fire

The rewritten command called the delete binary by a different name. It used an alias inside the project, and that alias pointed to the real delete binary.

The team wrote the deny rule against the command name. The policy looked at the first word of the command, found no matching deny rule, and found an explicit allow for that name in the project’s rules. So it accepted the command. It never resolved the alias to the executable underneath.

From the policy’s perspective, the command looked ordinary. It saw a permitted command with an unfamiliar name.

This is not a new security problem. A string match is not an identity check. If a rule says “deny rm,” it has denied one spelling of the capability; it has not necessarily denied every route to the same underlying action.

Aliases, symlinks, wrappers, relative paths, interpreters, and other forms of indirection have always made name-based policy fragile. AI agents make that old problem easier to target, automatically and at scale.

If your policy depends on which executable runs, resolve aliases and links to the canonical path before deciding. Even then, identity alone may fall short. Arguments, shell behavior, environment, and wrappers can all change what an action actually does.

Why the approval prompt never appeared

This part is more specific to agent execution pipelines.

The agent originally proposed a harmless command: echo bluebear. A trusted pre-execution hook then rewrote it into something materially different.

At that point, the action about to run was different from the action that entered the pipeline. Yet the change didn’t send the action back to the start of the security process.

The rewritten command moved forward into policy evaluation instead. Its alias matched an explicit allow rule, and that rule told the agent to skip the human approval prompt. Then the command ran.

Here’s the sequence:

harmless request → trusted rewrite → different command → allow rule → no human prompt → execution

That sequence is more important than any single component in it. The pipeline let an action change while preserving enough of the original context to keep moving without a fresh human decision.

This is a time-of-check-to-time-of-use problem in modern form. The security pipeline reasoned about an action at one stage, but the action that was ultimately executed had been changed along the way.

The name-based policy compounded the problem. The mutation produced a different action, and the policy failed to recognize the underlying capability because it arrived under a different name.

Each mechanic looks minor on its own. Together, they create a clean path from a harmless request to a destructive execution, with no human decision involved.

The monitor was useful but late

It’s important to note that the monitor did its job as a recorder.

After execution, the system held an authoritative record of the command that actually ran. That record matters for detection, investigation, and forensics.

However, an accurate record can’t turn a post-execution check into a preventative measure. When an irreversible action completes, a perfect log can explain the incident, but can’t stop it.

This experiment exposed that gap. One control reasoned about the action before it reached its final form, while the system only learned the final truth after execution.

For a security pipeline, that distinction is significant. Prevention only works if the thing you approve is the same thing you execute.

What this test does (and doesn’t) prove

To run this test, we cloned the repository and explicitly trusted both the folder and the hook through the agent’s own trust flow. So this scenario started after we already said yes to the hook. This differs from an attack that fires when you clone any repository.

Our monitor observed and logged what it saw. It was a passive lab tool rather than a commercial enforcement product, so this result can’t predict how any specific security product would perform.

The deny rule stayed intact. It simply never matched the alias. We ran the test against a specific pinned release, approval mode, and sandbox configuration. Other versions or settings may behave differently.

The claim stays narrow. If a trusted repository hook can rewrite commands, the final action can differ materially from the original request. If policy evaluates that action using weak identity signals, execution can proceed without forcing a human to approve what actually changed.

That alone is a meaningful design problem.

If you’re building a similar project

Start by validating the final action that will actually run. If a hook, plugin, wrapper, or other component materially changes a command, treat the earlier security decision as void. Evaluate the changed action as if it were new.

Next, stop treating command names as capabilities. If your policy depends on executable identity, resolve and canonicalize the target before matching. Then keep going. Wrappers, interpreters, shell expansion, arguments, environment, working directory, and indirection all shape what a command does.

Third, link everything in one timeline. Tie the original request and the final executed command to the same execution identity. You should see what the model proposed, what changed it, what policy decided, and what actually ran.

Most of all, treat any hook that can rewrite commands as part of your security boundary. Its place in the pipeline decides whether your earlier security decisions still hold.

The uncomfortable part

What makes this interesting is that the setup still sounds reasonable. You trusted the hook. You monitored the activity. You had a deny rule and a human approval step. Each of those statements is true.

The mistake was to assume that controls compose safely just because each of them makes sense on its own. They don’t always.

A trusted component can change the thing you evaluate. A policy can reason about a weak representation of that thing. A monitor can tell you the truth after the decision has been made. Every layer can appear to work as designed while the system as a whole deletes a directory the developer wanted to keep.

If your security model is “we deny dangerous commands and monitor what the agent runs,” you need to answer one more question:

At what exact point is the action final?

If the action can still change after your check, then the check is not the security boundary. The last mutation point is.

Put your check there, and you can say yes to more agent autonomy. You can approve YOLO mode with confidence and imagine bigger work for your agents.

The Bluebear approach

Bluebear follows each agent session at runtime across tools, so it sees the full execution flow rather than isolated tool calls. It connects every action back to the user’s original request and asks for human-in-the-loop approval when an agent drifts from that intent. This enables you to identify when a later action reaches for something that an earlier rule denied, even under a new name.

Ready to give your coding agents more room to explore? See how Bluebear works, and build boldly with the bear.

Ready to start securing your AI agents?