Skip to main content

Hardening Autonomous Runs

Advanced
What you'll learn
  • The three risks that are unique to a run with no human watching
  • An eight-item hardening checklist, in the order you should apply it
  • Which permission primitive actually removes capability — and which only pre-approves it
  • Why a least-privilege token matters more than a careful prompt
  • How to cap a runaway loop with a platform-enforced budget instead of a hopeful instruction
  • Why persistent instruction files need to be diffed to a place the agent cannot write

Running Claude headless or on a schedule — in CI, a cron job, a pre-commit hook — removes the human who'd normally catch a bad action. That convenience is exactly why these runs need the tightest guardrails.

The risks unique to unattended runs​

  • No one to say "no" to a risky tool call in the moment.
  • Ambient credentials. CI often has powerful tokens (deploy, package registry, cloud). An agent there inherits them.
  • Untrusted inputs. A run triggered by a PR or an issue may process attacker-authored content (injection).
Watch out

The failure mode to design against is not a malicious model. It is an ordinary model, holding a token it should never have been given, acting on text an attacker wrote. Every item below removes one of those three ingredients.

A hardening checklist​

Guided walkthrough1 of 8
  1. Block reading .env, key files, and credential paths via permission deny rules. Don't rely on the model to avoid them. Deny rules are the only primitive that removes capability — see the six-step evaluation order for why an allow list cannot do this job.

Deny removes, allow only pre-approves​

The most common mistake in a hardened run is reaching for the wrong lever. An allow list looks like a lock and isn't: anything you leave off it is still available to the agent and merely falls through to the session's permission mode.

Removes the capability — the tool is gone from the request

claude -p "run the test suite" \
--disallowedTools "Bash(curl *)" "Read(//.ssh/**)" "Edit(//etc/**)"

Makes a pre-approval list exact — anything else is denied, not prompted

claude -p "run the test suite" \
--permission-mode dontAsk \
--allowedTools "Bash(npm test)" "Read"

The second form is the one you want for CI: dontAsk turns every would-be prompt into a denial, so a run that encounters something unanticipated fails loudly instead of hanging or silently falling back on a permissive default.

A check that runs on every call​

Allow and deny rules match patterns. When you need a check that inspects the content of a call — a secret-shaped string in an argument, an unexpected hostname — put it in a PreToolUse hook. Hooks run before every other permission step, and a hook denial holds even in bypass mode.

Refuse any tool call carrying secret material

# .claude/settings.json -> hooks.PreToolUse
# Exit code 2 blocks the call and tells Claude why.
jq -e '.tool_input | tostring | test("AKIA|BEGIN PRIVATE KEY|ghp_")' >/dev/null \
&& { echo "secret material in tool input" >&2; exit 2; } || exit 0

Example: a safe CI reviewer​

A PR-review bot should: check out the code read-only, have no deploy/secret access, run in a container, and comment its findings — never modify protected branches. See the PR-review walkthrough.

Check yourself

0/3
  1. Your CI run uses bypassPermissions inside a container. Which of these still blocks a tool call?
  2. Which single measure most limits the damage from a successful prompt injection in a CI run?
  3. Why should CLAUDE.md and skill files be diffed to a location the agent cannot write to?
Key takeaways
  • Unattended runs lose the human veto, inherit ambient credentials, and often read attacker-authored text — remove one ingredient at a time.
  • Deny rules and hooks remove capability; allow lists only pre-approve. Pair allowedTools with dontAsk to make it exact.
  • Scope the token before you tune the prompt. It is the defense an injection cannot talk past.
  • Cap cost and iterations with a platform-enforced budget, not an instruction.
  • Ship outputs as a PR or a comment so a human merge stays in the loop.
  • Diff auto-injected instruction files to storage the agent cannot write.

Next​