Skip to main content

HAL — Harmful Action Limiter. Lean command guard for AI coding agents.

Project description

HAL — Harmful Action Limiter

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.yaml for 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


Download files

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

Source Distribution

openhal-0.1.0.tar.gz (27.4 kB view details)

Uploaded Source

Built Distribution

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

openhal-0.1.0-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

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

Hashes for openhal-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d3811d0d5bf222e0e5b11cc5ed1071f5a99ddfe8aa1fd3644dce18abdbbc5fd8
MD5 fa6418c46dcdbf227408ba1078c4ff52
BLAKE2b-256 c5de31604a7d503c5f23cb44bac910313d834d40ce4cda5644f758d3ff35197d

See more details on using hashes here.

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

Hashes for openhal-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1595d9f11ce80dc0a6d241d70b461d89a6c7180446533397df29bc8d5afe952a
MD5 e2e57d31abbf0cf7458c8a863bfb8164
BLAKE2b-256 9ce342eab2a156e1eb3ede9c4c5d4576711c070e7a9a15096e2f82da59e26e18

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page