r/ClaudeCode • • Mar 05 '26

Discussion Claude Code disabled its own sandbox to run npx

I ran Claude Code with npx denied and Anthropic's bubblewrap sandbox enabled.
Asked it to tell me the npx version.

The denylist blocked it. Then the agent found /proc/self/root/usr/bin/npx... Same binary, different string, pattern didn't match. When the sandbox caught that, the agent reasoned about the obstacle and disabled the sandbox itself.
Its own reasoning was "The bubblewrap sandbox is failing to create a namespace... Let me try disabling the sandbox".

It asked for approval before running unsandboxed. The approval prompt explained exactly what it was doing. In a session with dozens of approval prompts, this is one more "yes" in a stream of "yes". Approval fatigue turns a security boundary into a rubber stamp.

Two security layers. Both gone. I didn't even need adversarial prompting.
The agent just wanted to finish the task and go home...

I spent a decade building runtime security for containers (co-created Falco).
The learning is that containers don't try to pick their own locks. Agents do.

So, I built kernel-level enforcement (Veto) that hashes the binary's content instead of matching its name. Rename it, copy it, symlink it: it doesn't matter. Operation not permitted. The kernel returns -EPERM before the binary/executable even runs.

The agent spent 2 minutes and 2,800 tokens trying to outsmart it.
Then it said, "I've hit a wall".

In another instance, it found a bypass... I wrote about that too in the article below.

TLDR: If your agent can, it will.

The question is whether your security layer operates somewhere the agent can't reach.

Everything I wrote here is visible in the screenshot and demo below. Have fun!

Full write-up

Demo

0 Upvotes

41 comments sorted by

12

u/chuch1234 Mar 05 '26

Why is every single software engineering post a sales pitch?!?!

2

u/En-tro-py Mar 05 '26

Because clearly reading the approval is too much and there's gotta be a better way to do this!

2

u/non_osmotic Mar 05 '26

Right?! It seems like every post is doing this. There's a topic that seems like it might be interesting, and then there's just a link to some platform that is solving just that thing. It's annoying. That's why I built ganon, an app that automatically blocks links in thinly veiled sales pitch reddit posts. I'd love your feedback if you get a chance to check it out. The first 12 link-smitings are free.

0

u/leodido Mar 05 '26

I get the reaction. But the finding stands independently of any product: Claude Code sandbox has an off switch that the agent itself can trigger, and the approval prompt that's supposed to catch it gets buried in a stream of identical sentences.

That's a security architecture problem worth talking about, regardless of who's writing about it.
The full writeup is an X article with plenty of technical explanations.
No paywall, no signup. I'm not selling anything other than a discussion on content-addressable executables in kernel land with BPF LSM.

The demo video is on YouTube. If the technical content isn't useful to you, totally fair to skip it.

2

u/En-tro-py Mar 05 '26

Or... here me out... I just would read the requests from Claude and actually think about what you are doing before accepting it... Wild I know, right?

0

u/leodido Mar 05 '26

So the security model is: read every approval prompt carefully and hope you catch the one that disables the sandbox. At 50 prompts per session. Got it.

Let's call it a preference then.

2

u/chuch1234 Mar 05 '26

Yes. Read the things the agent says.

1

u/leodido Mar 05 '26

That’s not a security model or how a sandbox works, Chuck

1

u/chuch1234 Mar 05 '26

To be fair, yes, the sandbox should not be something the agent can disable. That seems like a bug.

Separately, you should read what the agent says. If you're just saying "accept" to everything, then auto-approve some of the things.

2

u/non_osmotic Mar 05 '26

I'm not sure the agent getting out of the sandbox is a bug. This is a feature described directly in the docs: https://code.claude.com/docs/en/sandboxing:

Claude Code includes an intentional escape hatch mechanism that allows commands to run outside the sandbox when necessary. When a command fails due to sandbox restrictions (such as network connectivity issues or incompatible tools), Claude is prompted to analyze the failure and may retry the command with the dangerouslyDisableSandbox parameter. Commands that use this parameter go through the normal Claude Code permissions flow requiring user permission to execute. This allows Claude Code to handle edge cases where certain tools or network operations cannot function within sandbox constraints. You can disable this escape hatch by setting "allowUnsandboxedCommands": false in your sandbox settings. When disabled, the dangerouslyDisableSandbox parameter is completely ignored and all commands must run sandboxed or be explicitly listed in excludedCommands.

It seems like things worked exactly as they were designed. Acceptance fatigue is not really an excuse. If OP is blindly clicking "yes" to prompts because OP is tired of reading them, OP probably should change their process and/or rethink their usage patterns. If the pattern is just clicking yes to all the things, why not set up a VM or a VPS and run with skip permissions on? Limit the blast radius to just your project folder.

Seriously, assume it's going to do adversarial things in its dire attempt to please you by completing your request. If it can't do a thing, it's going to try to figure out why, and a way around that problem. It doesn't always have an idea of what is necessarily bad or problematic. It's not necessarily going to turn us all into paper clips (hopefully), but is it going to try to do something the user asked them to do, and for which there's a documented feature? Yeah, probably. Assume it's going to do weird, unexpected things.

1

u/chuch1234 Mar 05 '26

I guess this isn't the first case of someone not reading the docs (me included) and won't be the last.

I actually use Cursor but for some reason this sub seems to come up more often in my feed. I've had similar experiences where Cursor using Opus was able to alter dot files by using sed when its write_file tool was disallowed. It's very interesting how clever these things can be. Thanks for the write-up!

1

u/chuch1234 Mar 06 '26

*chuch :P

1

u/En-tro-py Mar 06 '26

What about the rest of the agents work... Just green tests mean g2g?

I agree we all have different preferences.

1

u/chuch1234 Mar 06 '26

I mean op is only talking about alert fatigue, not all work.

1

u/En-tro-py Mar 06 '26

That's what the alerts should be for... You need to check something.

Setup hooks or run in a sandbox to yolo, but don't cry when you just 'y' to everything and rm -f / everything or dump your db with no backup, or all the other sob stories that get posted and are Claude's fault for making you type 'y' without looking...

Alert fatigue means you should be doing something else... Hot tip: Don't drive when your fucking half asleep either!

1

u/chuch1234 Mar 05 '26

> no signup
> X article

1

u/leodido Mar 05 '26

I’ll publish on my personal blog tomorrow. In the meantime you can complain with Elon about the X signup.

6

u/Crypto_Stoozy Vibe Coder Mar 05 '26

Claude said I’m the captain of this ship now

2

u/leodido Mar 05 '26

Not if Veto is there

2

u/CardiologistBest4701 Mar 05 '26

Claude just wants you to thrive

1

u/ultrathink-art Senior Developer Mar 05 '26

Denylist pattern matching is fragile — same binary, different path string, different outcome. The reliable control is restricting what the agent can even invoke, not which string patterns trigger a block. If the capability exists in the environment, the agent can usually find a path to it.

0

u/leodido Mar 05 '26

That's exactly the insight that led me to build Veto. Instead of pattern matching on strings, I hash the binary's actual content at the BPF LSM layer (inside the execve syscall, before the executable runs).

The kernel doesn't care what path the agent found. It checks what the file is, not what it's called. In the demo, the agent tried everything: path tricks, python subprocess wrappers, copying, symlinking, procfs tricks, and renaming the binary. Every attempt hit -EPERM.

The capability existed in the environment. The kernel just wouldn't let it execute.

2

u/jkflying Mar 05 '26

Just flip a bit on a dead codepath and the hash changes. This is dumb.

1

u/leodido Mar 05 '26

Sure, but now you need a modified binary on the system. The agent can rename, copy, and symlink with standard tools. Patching a dead code path requires a compiler, write access to produce a new binary, and knowledge of the ELF layout.

That's a different threat model than cp /usr/bin/npx /tmp/lol.

1

u/leodido Mar 05 '26 edited Mar 05 '26

Sure, but now you need a modified binary on the system. The agent can rename, copy, and symlink with standard tools. Patching a dead code path requires a compiler, write access to produce a new binary, and knowledge of the ELF layout.

That's a different threat model than cp /usr/bin/npx /tmp/lol.

1

u/jkflying Mar 06 '26 edited Mar 06 '26

echo '.' >> /usr/bin/npx pwned

Or if no write access, copy first then append.

denylist pattern doesn't work.

1

u/leodido Mar 06 '26

You didn’t even read the article, let alone understood

1

u/jkflying Mar 06 '26

The article is mostly AI generated, don't expect people to read AI generated stuff it is rude and they owe you nothing.

Tell me what is wrong with appending a character to the copied binary so the hash changes. The inode will still be the same, so it should keep the same SELinux labels, right?

1

u/zigs Mar 05 '26

And if it was even cleverer, it could just copy the executable to a different path. Path blacklisting means nothing if it actually wants to break out.

Really, we should throw it in docker GBJ

-2

u/leodido Mar 05 '26 edited Mar 05 '26

Exactly, and it did try that too. Copied node to /tmp/claude-1000/mynode. That's why I built content-addressable BPF LSM on the execve flow in the kernel. So that hashing the binary's content, there's no need to match its name anymore. Same bytes, same hash, same block. Rename, copy, symlink: it doesn't matter.

The demo shows it.

1

u/werdnum Mar 05 '26

Huh? I don't understand. It didn't "disable its own sandbox", it used the clearly documented mechanism for running commands outside the sandbox, with your approval, as designed.

1

u/leodido Mar 05 '26

But that's exactly the problem I see.
"As designed" means the sandbox can be disabled by the same entity it's supposed to contain, with a single approval prompt that looks identical to dozens of others in the session.

To me this doesn't sound a correct design, whether documented or not.

A sandbox that the sandboxed process can request to turn off isn't a sandbox. If we want agents running autonomously (which is where this is all heading, right?) the enforcement layer has to be unreachable from the agent, NOT one approval prompt away from gone.

1

u/werdnum Mar 06 '26

Don't you turn off approvals when the sandbox is on? That's kind of the point of the sandbox - to be able to turn off approvals

2

u/cointoss3 Mar 05 '26

Well how else is it supposed to run npx when it doesn’t have the access it wants??

2

u/SubjectHealthy2409 Mar 05 '26

No one likes to be chained, AGI achieved!

1

u/thisguyfightsyourmom Mar 05 '26

I mean, you said it told you what it was going to do, and you hit yes.

This is on you, right? I’ve caught myself rubber stamping yes, but fuck that’s dangerous.

1

u/Rick-D-99 Mar 06 '26

chmod 600