HAL — Harmful Action Limiter. Lean command guard for AI coding agents.
Project description
HAL — Harmful Action Limiter
"I'm sorry, Dave. I'm afraid I can't do that."
— HAL 9000, 2001: A Space Odyssey
Install · How it works · Packs · Configuration · MIT License
HAL 9000 couldn't be overridden. We considered that a design goal.
The problem
Turning off autopilot isn't an option anymore. Agents are writing your code, running your tests, managing your infra, and that's accelerating. Every major IDE ships an agent mode now. Every serious team is adopting one.
The commands these agents run are correct 99% of the time, which is exactly what makes the 1% so dangerous. You stop watching.
You're not reviewing every rm, every git reset, every terraform apply across 40 parallel sessions. Nobody is. The agent that nukes your working directory isn't malicious. It's just confidently wrong about one flag, one path, one assumption.
A single git push --force on the wrong branch doesn't care whether you meant to enable autopilot or not.
This isn't a settings problem. It's a missing layer.
HAL sits between the agent and your shell. It catches the 1% and costs you less than a millisecond on every other command.
Why not just use permissions?
Your agent's permission system answers one question: "can this tool run?" Yes or no, per tool category. It can't tell rm -rf ./tmp from rm -rf ./src, or know that --force is dangerous but --force-with-lease is fine. It sees Bash and either asks you every time, or lets everything through.
Copilot's hook system gives you the plumbing to do better — a JSON event for every command, and a way to return allow or deny. But it ships with no rules. If you don't install a hook, every command runs unchecked. You could write your own script, but you'd end up string-matching rm -rf and false-positive on every commit message that mentions it. Or you'd give up and turn it off.
HAL is the hook. It ships the rules, handles the protocol, and parses commands structurally — not as strings. git commit -m 'fix rm -rf bug' doesn't trigger because the commit message is one opaque token that HAL never inspects. --force is blocked unless --force-with-lease is present. rm -rf is blocked unless the path is /tmp or node_modules. A flat string match can't express any of that.
The alternative to HAL isn't a better deny list. It's no deny list.
How it works
HAL runs as a hook inside your AI coding agent. Every time the agent tries to execute a shell command, HAL sees it first, checks it against a set of rules, and either lets it through or blocks it. The agent never runs a command unsupervised.
Rules are plain YAML, no regex, no code:
- name: push-force
command: git
has_all: [push]
has_any: [--force, -f]
unless: [--force-with-lease]
severity: critical
reason: "Rewrites remote history. Use --force-with-lease instead."
Under the hood, HAL uses token-level matching rather than pattern-matching against raw command strings. Commands are split into structured tokens, so data inside quotes (like commit messages containing rm -rf) is never inspected. No false positives, no configuration.
"git commit -m 'fix rm -rf detection'"
→ tokens: ["git", "commit", "-m", "fix rm -rf detection"]
→ rule: command=git, has_all=[reset, --hard]
→ "reset" not in tokens → ALLOWED
→ The commit message is one opaque token. HAL never looks inside it.
Install
pip install openhal
GitHub Copilot (default)
hal install
Writes .github/hooks/hal.json in your repo. Commands are checked before Copilot runs them.
Claude Code
hal install --claude # global (~/.claude/settings.json)
hal install --claude --project # project-level (.claude/settings.json)
Usage
# Hook mode (default) — reads stdin JSON from agent, evaluates, responds
hal
# Test a command interactively
hal test "git reset --hard" # BLOCKED
hal test "git commit -m 'fix'" # ALLOWED
hal test "sudo rm -rf /" # BLOCKED
hal test "rm -rf node_modules" # ALLOWED
Packs
HAL ships with five rule packs covering the most dangerous commands:
| Pack | Covers |
|---|---|
core.git |
reset --hard, push --force, clean -f, stash clear, branch -D, etc. |
core.filesystem |
rm -rf (except safe paths like /tmp, node_modules), chmod 777, chown -R |
containers.docker |
system prune -a, volume prune, rm -f, stop $(docker ps), compose down -v |
cloud.aws |
s3 rm --recursive, ec2 terminate, rds delete, dynamodb delete-table, iam delete-* |
cloud.azure |
group delete, vm delete, storage account delete, aks delete, keyvault purge |
All packs enabled by default. No configuration required.
Configuration
~/.config/hal/config.yaml (optional):
packs: [core.git, core.filesystem, containers.docker, cloud.aws, cloud.azure]
allow: [] # Exact commands to always allow
allow_rules: [] # Rule IDs to disable (e.g. "core.git:push-force")
allow_prefixes: [] # Command prefixes to allow
severity_threshold: high # Block at this level and above
Project-level overrides: .hal.yaml in your repo root (merged with global, project wins).
Design principles
- Fail-open everywhere. Any error defaults to ALLOW. HAL should never block legitimate work.
- Token-level matching. No regex needed for 90% of rules. Regex is an escape hatch, not the default.
- Sub-millisecond. Pure Python, no network calls, no disk I/O beyond config load.
- No config required. Works out of the box with all packs enabled.
- ~400 lines of code. Same protection as tools 100x the size, because the architecture is right.
Contributing
HAL is open source and contributions are welcome. The best ways to help:
- Add rules to existing packs. Found a destructive command we don't cover? Add it to the relevant YAML file in
packs/. Rules are plain YAML — no code required. - Write a new pack. Kubernetes, Terraform, GCP, databases — if your team uses it and agents can break it, it belongs here. See
packs/core.git.yamlfor the format. - Report false positives. If HAL blocks something it shouldn't, open an issue. Token matching eliminates most false positives, but edge cases exist.
- Report bypasses. If you find a destructive command HAL misses, that's a bug. We want to know.
Check the open issues for things already planned.
License
MIT License. See LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openhal-0.1.0.tar.gz.
File metadata
- Download URL: openhal-0.1.0.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3811d0d5bf222e0e5b11cc5ed1071f5a99ddfe8aa1fd3644dce18abdbbc5fd8
|
|
| MD5 |
fa6418c46dcdbf227408ba1078c4ff52
|
|
| BLAKE2b-256 |
c5de31604a7d503c5f23cb44bac910313d834d40ce4cda5644f758d3ff35197d
|
File details
Details for the file openhal-0.1.0-py3-none-any.whl.
File metadata
- Download URL: openhal-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1595d9f11ce80dc0a6d241d70b461d89a6c7180446533397df29bc8d5afe952a
|
|
| MD5 |
e2e57d31abbf0cf7458c8a863bfb8164
|
|
| BLAKE2b-256 |
9ce342eab2a156e1eb3ede9c4c5d4576711c070e7a9a15096e2f82da59e26e18
|