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.2.0.tar.gz (29.6 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.2.0-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: actenon_blastradius-0.2.0.tar.gz
  • Upload date:
  • Size: 29.6 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.2.0.tar.gz
Algorithm Hash digest
SHA256 15d308023c74eab478f02562e139d3e515d445c189c63e2aa8dc8171b3e11835
MD5 0c4bc44fbaa71d6c7df6fe1211e7e107
BLAKE2b-256 d061922975d6af2c93175bdb8cb958f17884b08a3c5409f14763e697b2c8fcbf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for actenon_blastradius-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ec3fe987a35e021308fc750d6779d603b4a8517aff14c90e9388a31baf4f77f
MD5 2dac044a0ea74a00f780f893c93deb47
BLAKE2b-256 12e174cd0c13416047ddd8a53e00b292b26bfaf68d65ade6abb66e62fd318f08

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.0

2 files

0.3.0

2 files

This release

0.2.0 This release

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