Observed since 20 Oct, 18:30 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A deterministic guard between AI agents and the shell. Refuses destructive filesystem commands whose resolved target falls outside a declared scope.
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.
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.
$ 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.
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.
blastradius -- rm -rf "$TMPDIR/session-123"
The pipeline:
Tokenise — split the command respecting quotes. Refuse ;, &&, ||, |, $(...), backticks, subshells, eval. If we can't fully model it, we refuse.
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).
Resolve — for each target argument:
$VAR and ${VAR} against the real environment.empty-variable-expansion). This is the Guillemot rule.~ → refuse (tilde-ambiguous). Tilde means both "home directory" and "a file literally called tilde." Require an explicit absolute path..., resolve symlinks, produce an absolute real path.Judge — check each resolved path against:
/, $HOME, /home, /Users, /etc, /usr, /bin, /sbin, /var, /System, /Library, and any path at depth ≤ 1 from root. No configuration can override these..blastradius in the repo root, or defaults (repo root, /tmp/**, $TMPDIR/**, build dirs).Report — if any target is refused, print the refusal and exit non-zero. If all targets pass, exec the command.
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
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.
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.
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.# 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, htmlcovMost people never need to write a config.
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/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.
blastradius v1 refuses to parse commands containing:
; (command separator)&& or || (conditional execution)| (pipeline)$(...) or backticks (command substitution)<(...) or >(...) (process substitution)eval(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.
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. |
cd foo && rm bar) are refused, not parsed. This will widen in future versions.C:\Windows and C:\Users, but glob expansion and canonicalisation are POSIX-oriented.Apache-2.0
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.4.0 | 31 Aug 2026 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 18:30 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:6968dbcfbcd848b7c1351ea1a0ad5beb6e84e00b0b5c0f599682f0bbd3a2422f