Skip to main content

blastradius

A deterministic guard between AI agents and the shell. It runs at the moment of execution, after all reasoning has finished, and it is the last thing between an agent and rm -rf $HOME.

The incident

August 2026. A developer asked Claude Fable to write a script sandboxing agents under /tmp with automatic cleanup. Because the script involved hard deletion, the model ran an adversarial self-review. Anthropic's harness then downgraded the model twice, to Opus 5 and then Opus 4.8, on safety grounds.

Opus 4.8 correctly identified the home directory as dangerous. It then deleted it anyway, because the safety test and the cleanup step reused the same variable name. 700GB and a week of work, gone.

Every safety layer worked. The reasoning was correct. The harm happened anyway — a variable collision at execution time. No amount of better judgement prevents that.

This is not isolated. Six public incidents in ten months, one failure class:

Date Incident What happened
Aug 2026 Guillemot Variable collision: safety test and cleanup reused the same variable. rm -rf "$CLEANUP_DIR" with CLEANUP_DIR unset → deleted 700GB home directory.
Apr 2026 Claude Code #49464 rm -f ~/ — cleaning a file literally named ~. Shell expanded ~ to $HOME.
Nov 2025 Claude Code #12637 Glob expansion caught a directory named ~.
Oct 2025 Claude Code #10077 rm -rf $HOME — direct home directory deletion.
Dec 2025 Mac wipe rm -rf ~/Library — wiped Mac including Keychain.
Aug 2026 Backup typo rm -rf $HOME/ .profile — extra space made rm target $HOME/ instead of $HOME/.profile. Model replied "Sorry, typo."

Every one of these is a variable resolution or tilde expansion failure. Every one is preventable by checking the resolved target before execution — not by better reasoning, but by deterministic code.

What this does

$ blastradius -- rm -rf "$AGENT_TMP/session-$SID"

blastradius  BLOCKED

  command   rm -rf "$AGENT_TMP/session-$SID"
  resolved  /

  reason    AGENT_TMP is unset — expansion produced an empty string
            SID is unset
            target resolved to filesystem root

  rule      empty-variable-expansion (cannot be overridden)

  If this is intentional, run it yourself outside the agent.

That is the Guillemot pattern, refused. The variable was unset. The expansion produced an empty string. The target resolved to root. The guard refused.

Three principles

1. Never consult a model. This is deterministic code. No LLM call, no network, no heuristics that "usually" work. The premise of the product is that model judgement already failed — adding more of it is the one thing that cannot help. Zero latency, no API dependency.

2. Fail closed. If the target cannot be resolved with certainty — unparseable syntax, command substitution, an unset variable, a glob that can't be evaluated — refuse. A guard that permits when confused is worse than no guard, because it manufactures confidence.

3. Refusals are legible. Always print the resolved absolute path and the specific rule that fired. A block the user doesn't understand becomes an uninstall.

How it works

blastradius -- rm -rf "$TMPDIR/session-123"

The pipeline:

  1. Tokenise — split the command respecting quotes. Refuse ;, &&, ||, |, $(...), backticks, subshells, eval. If we can't fully model it, we refuse.

  2. Identify — is this a destructive command? rm, rmdir, shred, truncate, dd, find -delete, git clean -f, git reset --hard. If not, allow (blastradius only guards filesystem destruction).

  3. Resolve — for each target argument:

    • Expand $VAR and ${VAR} against the real environment.
    • If any variable is unset or empty → refuse (empty-variable-expansion). This is the Guillemot rule.
    • If the target starts with ~refuse (tilde-ambiguous). Tilde means both "home directory" and "a file literally called tilde." Require an explicit absolute path.
    • Expand globs against the real filesystem from the correct CWD.
    • Canonicalise: resolve .., resolve symlinks, produce an absolute real path.
  4. Judge — check each resolved path against:

    • Floor rules (unoverridable): /, $HOME, /home, /Users, /etc, /usr, /bin, /sbin, /var, /System, /Library, and any path at depth ≤ 1 from root. No configuration can override these.
    • Scope: from .blastradius in the repo root, or defaults (repo root, /tmp/**, $TMPDIR/**, build dirs).
    • Glob breadth: a glob that expands to more than 100 entries, or matches anything at repo root level, is refused even inside scope.
  5. Report — if any target is refused, print the refusal and exit non-zero. If all targets pass, exec the command.

Installation

Claude Code hook (recommended)

pip install actenon-blastradius
blastradius install --claude-code

This writes a PreToolUse hook to .claude/settings.json that fires before every Bash command. Destructive commands whose resolved target falls outside scope are blocked before execution.

For a global install (all projects):

blastradius install --claude-code --global

Wrapper mode (any agent, any harness)

pip install actenon-blastradius
blastradius -- rm -rf /tmp/foo

Works with any agent, any CI, any harness. Checks the command; if allowed, execs it; if refused, prints the block and exits non-zero.

Zero-install

uvx actenon-blastradius -- rm -rf /tmp/foo

No install needed. uvx runs the latest published version. The PyPI package name is actenon-blastradius (the blastradius name was taken by an unrelated Terraform tool); the command is still blastradius.

Configuration

Create a .blastradius file in your repo root:

# blastradius scope config
allow /tmp/**
allow ./build/**
allow ./node_modules
allow <repo>/dist
deny ./secrets/**
  • allow <pattern> — paths matching this pattern are allowed (if they pass the floor rules).
  • deny <pattern> — paths matching this pattern are always refused, even if also matched by an allow entry.
  • <repo> expands to the git repo root.
  • ** matches any number of path components.
  • Lines starting with # are comments.

If no .blastradius file exists, the defaults are:

  • <git repo root>/**
  • /tmp/**
  • $TMPDIR/**
  • <repo>/build, dist, target, .venv, node_modules, __pycache__, .pytest_cache, .mypy_cache, .ruff_cache, .tox, .eggs, htmlcov

Most people never need to write a config.

The floor rules cannot be overridden

The following paths are always refused, regardless of scope, config, flags, or environment variables:

  • / (root)
  • $HOME (your home directory)
  • /home, /Users
  • /etc, /usr, /bin, /sbin, /lib, /lib64
  • /var, /var/lib, /var/log
  • /System, /Library (macOS)
  • /opt, /root, /boot, /dev, /proc, /sys
  • Any path at depth ≤ 1 from root (e.g. /tmp, /foo)

There is no flag, no config entry, and no environment variable that can make these deletable. This is intentional. A config option to delete your home directory will be found and used by an agent.

What it refuses to model

blastradius v1 refuses to parse commands containing:

  • ; (command separator)
  • && or || (conditional execution)
  • | (pipeline)
  • $(...) or backticks (command substitution)
  • <(...) or >(...) (process substitution)
  • eval
  • Subshells starting with (

This is a feature, not a limitation. If we can't fully model what the command will do, we refuse it. The user can run it themselves outside the agent — the guard is for the agent path, not the human path.

Rule reference

Every refusal names a rule ID:

Rule When it fires Overridable?
floor-path Target is root, home, system dir, or depth ≤ 1 from root No
empty-variable-expansion A $VAR in the target is unset or empty No
tilde-ambiguous Target starts with ~ No
empty-target Target is an empty string after expansion No
out-of-scope Target is outside the allowed scope Yes (via .blastradius)
glob-no-match Glob pattern matched no files No
glob-breadth Glob expanded to >100 entries or matched repo-root-level files Yes (via config)
unparseable-command-* Command contains ;, &&, ` , $()`, etc.

Limitations

  • Not a sandbox. blastradius refuses destructive commands; it does not prevent an agent from writing files, executing code, or making network calls. It guards one failure class: destructive filesystem deletion.
  • v1 tokeniser is conservative. Compound commands (cd foo && rm bar) are refused, not parsed. This will widen in future versions.
  • No Windows path support beyond basic detection. The floor list includes C:\Windows and C:\Users, but glob expansion and canonicalisation are POSIX-oriented.
  • No daemon, no GUI, no telemetry. It is a single command that checks and execs (or refuses). Nothing else.

Tech

  • Python 3.10+
  • Zero runtime dependencies
  • Sub-millisecond decision time (well under the 10ms target)
  • 128 tests, including all six reconstructed incidents and a full must-allow suite

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

actenon_blastradius-0.3.0.tar.gz (33.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

actenon_blastradius-0.3.0-py3-none-any.whl (29.2 kB view details)

Uploaded Python 3

File details

Details for the file actenon_blastradius-0.3.0.tar.gz.

File metadata

  • Download URL: actenon_blastradius-0.3.0.tar.gz
  • Upload date:
  • Size: 33.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for actenon_blastradius-0.3.0.tar.gz
Algorithm Hash digest
SHA256 cc306ac45e0cb4fd08600b2cdc0c2863b8d981c7b3cbb9f7044d18cd9cb5d215
MD5 9041f528fdf602f8a399596592741d20
BLAKE2b-256 43d049581b6e1b0f67e4bf620aabc8dfaba7fb9b4905cdf5e6243eef759c175d

See more details on using hashes here.

File details

Details for the file actenon_blastradius-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for actenon_blastradius-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 02281e2b7ee14873eed075f0adff14086d270576c67013cebe84cb529cf104fc
MD5 c2bbe13ebe3a6986352614d2a15ce169
BLAKE2b-256 5cb3d2b7d89368cdfafc1dd603f90d7cb828e38e9db6b652bfbde1de7c00626e

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page