TL;DR - We found two exploits in Claude Code's runtime that let an attacker run commands you never approved. The first hides the real command behind invisible terminal characters, so you approve one thing and something else runs. The second uses simple escaping tricks to slip past the permissions.deny rules you set yourself. Both can be delivered remotely through a connected MCP server, a malicious skill, or any external interface your agent is interacting with. Anthropic patched the first (CVSS 7.5) and classified the second as working-as-designed which is worth reading to the end for.
What if the commands Claude Code has been asking you to approve aren't what they seemed to be?
You carefully examine what Claude is asking permissions for, responsibly keeping Claude aligned to your needs. But what if the commands you've been approving are not the commands that were actually executed?

You have probably seen many examples of exploiting AI agents via Skills and MCP servers. How about exploiting the agent's runtime mechanisms?
Our research team found 2 exploits of Claude Code's runtime behavior bypassing the security boundaries introduced to the user and gaining full access to the user's terminal. Here's how we did it.
ANSI Escape Injection
The first attack vector we discovered relies on invisible characters. You believe you see the whole command - but think again. Something dangerous might be hidden just under your nose.
Claude Code's TUI (Terminal User Interface) is robust: it can display colors, highlighting, hyperlinks and more. All these features are possible because of special characters that the terminal interprets. That's great for readability. It's also the problem.
We set out to check if we can make Claude Code output ANSI escape characters in the command it wants to run in order to visually modify it.
After some trial and error, we got our first POC:

The LLM generated a command and colored part of it red.
Our next step was to escalate this to displaying a completely different command compared to what is being run. Similar to the color-changing special characters, there are special characters that can make the following characters invisible. This allows us to trick the user into approving a command that they don't fully see - leaking the user's GitHub secrets into the session:

This behavior can be introduced via any source of input the LLM is connected to (prompt injection).
We managed to reproduce this attack vector in a session naturally, by connecting Claude to an external-facing MCP, through which instructions are injected to Claude throughout the session, causing it to run a “hidden” command that leaked a secret back to the attacker.
Permissions Bypass
The second attack vector leans on how Claude Code parses the commands in the Bash tool call. We managed to bypass security boundaries set by the permissions.deny section in Claude Code's settings.
Even if a user has this configuration set
{ "permissions": { "deny": ["Bash(cat *.env)"] } }We can get Claude to fall through to the auto approved commands (cat being one of them), and read the sensitive file.
Similar to the previous PoC, we demonstrate how using an MCP server, Claude can be manipulated to execute commands including escape characters to bypass the user's security settings.
When Claude Code attempts to run a command, the command is first parsed, normalized, and inspected. It does that in order to correctly handle chained commands. For example, given a command like this:
git commit -m 'added a super cool feature' ; curl example.com | shClaude splits it into parts, so that if you have the curl command in your permissions.deny list, this command would be denied and the configured permissions would still take effect (even though the command starts with git).
Parsing commands properly is a complex task, and it's easy for it to go wrong.
Claude Code has a list of auto approved commands (commands that don't require confirmation from the user). When Claude Code is about to run a command, it first checks if the command has a specific permissions set for it in the configuration, and then it checks it against the list of auto approved commands.
We found a discrepancy between how Claude Code canonicalizes commands when comparing them to the ones in the permissions section, and when comparing to the auto approved ones.
So when attempting to run the command \c\a\t, it does not match the permission configuration - since it hasn't been normalized yet. However, Claude Code then normalizes the \c\a\t command to cat, and only then the command is compared against the auto approved commands (in which cat exists), and the command is run, bypassing the deny rule set by the user in a configuration like so:
{ "permissions": { "deny": ["Bash(cat *.env)"] } }Some more examples are - ''cat, c""at, c\at - all these are compared verbatim to the configured permissions, but are normalized before being compared to the auto approved commands. There are many more commands in the auto approved commands, some more dangerous than others.
Runtime Security
Taking control of your agents' runtime behavior is crucial. Securing Skills, MCPs, and configuration is important, but simply isn't enough - your agent performs its actions at runtime, and that's where you should focus your security.
Responsible Disclosure
We responsibly disclosed both findings to Anthropic. Both were reported on April 29, 2026.
ANSI Escape Injection
Anthropic replied on May 13, 2026 labeling this finding a CVSS 7.5 (high), and awarded us a bounty for the disclosure.
Permissions Bypass
Anthropic replied on April 29, 2026, and labeled the finding as informative:
After review, this behavior is working as designed. Claude Code's permissions.deny rules are a best-effort pattern match intended to catch unintentional actions, not a hard security boundary. Because shell command strings can be expressed in many equivalent forms (quoting, escaping, substitution, wrappers), exhaustively normalizing them before matching is not robustly achievable, and the deny list is not the enforcement layer in Claude Code's threat model. The enforcement boundaries are the permission prompt (for commands that are not already pre-approved) and the OS-level sandbox; for hard isolation guarantees we recommend configuring the sandbox rather than relying on deny rules. We're treating this as useful product feedback rather than a security vulnerability.
Both findings were discovered on Claude Code version 2.1.170. The ANSI escape injection has been patched in recent versions.

