All posts
Sep 12, 2026·3 min read

I fenced my AI to one folder. It ran shell commands anyway

#ai-agents#automation#claude-code#security

I built a small AI clerk whose only job is filing notes into one folder. No shell access. Its permission list named five tools: read, write, edit, find files, search text. On its first real job it ran find, mkdir -p and mv.

None of those five tools runs commands.

What I thought the flag did

The clerk is a headless Claude Code run launched from a script, roughly:

claude -p "file today's notes" --allowedTools "Read,Write,Edit,Glob,Grep"

I read that the way most people would: these tools are allowed, so everything else is blocked.

That's not what --allowedTools does. It lists tools that run without asking. It doesn't take anything away, and the CLI reference says so directly: to restrict which tools are available, use --tools.

Where the shell access came from

Whatever you pass on the command line gets combined with the permissions already saved on the machine, and the saved ones live in ~/.claude/settings.local.json. That file fills up quietly. Every time an interactive session asks "allow this command?" and you pick the option that remembers your answer, a rule gets added.

I opened mine: 172 allow rules, 160 of them shell patterns, collected over weeks of unrelated work. A find here, a git there, each approved once for some other job.

So the clerk wasn't breaking out of anything. Its real limits were my five tools plus every "yes" I'd ever clicked on that account, and I couldn't have told you what most of those were.

Nobody made a bad call here. Every one of those approvals was reasonable when I clicked it. The problem is where they end up: in a shared pile that every future run inherits, including the runs you built specifically to have less power.

What actually locked it down

The fix was to stop the run from reading those saved approvals at all. Current Claude Code has a --restricted mode that loads only managed settings and anything passed with --settings, drops the command-running tools unless you name them in --tools, and keeps file tools inside the working directory. Add an explicit tool list, and set permission prompts to deny since nobody's there to answer them:

claude -p "file today's notes" \
  --restricted \
  --tools "Read,Write,Edit,Glob,Grep" \
  --permission-prompts none

Version note: --restricted needs Claude Code 2.1.248 or later, and --permission-prompts needs 2.1.259 or later. On an older build those flags will error out, so update first.

Then I tried to break it. I asked the clerk to read /etc/passwd, and it refused because the file is outside its folder. That refusal was the first time I believed the restriction was real.

I also added a test that fails if those flags ever disappear from the launch command. If somebody "simplifies" the script later, the test catches it.

Checklist for your own headless runs

  • Treat any allow-list flag as additive until you've confirmed otherwise, and find out what else gets merged in.
  • Open settings.local.json, plus its user and project siblings, and count the rules. Every automated run on that account has been working with that number.
  • Launch automated runs in restricted or isolated mode, name the tools explicitly, and deny anything that would prompt.
  • Test the restriction by asking the agent to do something it shouldn't, like reading a file outside its folder, and watch it refuse.
  • Then check your other scripts. If one headless run on that machine was inheriting your old clicks, all of them are.