HaltDashboard

Security

How Halt is built, what it protects against, what it can't do, and what happens when it's down. The enforcement claims here come with tests you can run yourself against this deployment.

Architecture · Outages · Stop, pause, resume · Threat model · Keys · Data · Tests

What runs where

Every decision is made by the hosted API. Nothing on your machine evaluates rules, so a change applies on the next tool call. The local pieces only enforce Halt's answer and choose what to do when there is no answer.

ComponentRunsDecides locallyNeeds the API
Claude Code hookInside Claude Code on your machine. Configuration only, nothing installed.Nothing. Sends each tool call to Halt and applies the answer: deny, or no objection.Yes, once per tool call (10s timeout)
OpenClaw pluginInside the OpenClaw gateway (one short file you can read).Nothing. Same as the hook, through before_tool_call.Yes, once per tool call (5s timeout)
Supervisor (halt.mjs)On your machine, as the parent of the agent process. Uses its own supervisor token, which it removes from the agent's environment.Enforces the agent's state with OS signals. Decides locally only what to do when Halt is unreachable, and which processes belong to the agent.Heartbeat every 2s; silence is an alarm
watch mode (halt.mjs watch)On your machine, next to containers or services.Runs the commands you configured (e.g. docker pause) when the state changes. Halt only picks which.Polls every 2s
Check APIYour own code calls it before each side effect.Your code obeys the decision.Yes, per action
Halt (hosted)Vercel + Postgres.Everything else: STOP ALL, each agent's state, tripwire matching, rule matching and breaker counts, cooldown auto-resume, rate limits, the silent-supervisor alarm, alerts.n/a
Alertsntfy (phone push) and your webhooks.Nothing. Notification buttons call Halt with the alert key, which can stop. It can resume only if you allow that.Buttons need Halt

When Halt is unreachable

While Halt is down, nothing new can happen: no pause, stop, breaker trip or tripwire, because those decisions need the API. What was already decided stays decided. Each integration chooses between letting work continue (the default) or blocking it:

IntegrationDefault (fail-open)Fail-closedEnable with
Claude Code hookThe tool call goes ahead. Claude Code treats a connection error, a timeout or a non-2xx answer as a non-blocking hook error.The tool call is blocked."onFailure": "block" on the hook (Claude Code 2.1.295+)
OpenClaw pluginAllowed after the timeout, with a warning in the gateway log.Blocked: "[Halt] unreachable; failing closed"."failClosed": true
Supervisor, agent runningKeeps running. One warning.Killed after the grace period (default 60s).--fail-closed [--grace <seconds>]
Supervisor, agent pausedStays frozen until Halt is reachable again and says running.Same.Always
Supervisor, startingStarts with a warning.Refuses to start (exit 69).--fail-closed
watch modeKeeps the current state.Runs --on-stop after the grace period.--fail-closed [--grace <seconds>]
halt.mjs check / your codeExit 0 (allow) on error.Exit 2 (deny) on error.HALT_FAIL_CLOSED=1, or your own handling

Stop, pause, resume: exactly

Halt changes states. Hooks and plugins enforce them on the next tool call, and the supervisor enforces them on the process. Halt never starts a process.

StateTool callsSupervised processEnds whenWhat resume does
runningChecked against STOP ALL, tripwires and rules.Runs.n/an/a
pausedDenied, with "wait; do not retry".The whole process tree is frozen with SIGSTOP, mid-instruction.Owner or overseer sets it to running, or a breaker's cooldown elapses.The next tool call is allowed. The supervisor sends SIGCONT and the process continues exactly where it was. Nothing restarts, because nothing was killed.
stoppedDenied, with "do not retry".SIGTERM to the whole tree, SIGKILL 5s later. The supervisor exits (code 137).Only the owner or overseer setting it to running. Tripwires and stop-effect breakers never auto-resume.Changes the state only. A hook-based agent whose process is still alive (a Claude Code session, the OpenClaw gateway) can call tools again on its next call. A killed process stays dead: Halt never starts processes. Relaunch it yourself; halt.mjs run refuses to start (exit 75) until the agent is running. Only watch --on-start restarts something, and only if you configured it.
STOP ALLEvery agent denied. This is an account-wide flag, separate from each agent's state.Every supervisor treats it as stopped: every supervised process is killed.RESUME ALL.Lifts the flag only. Each agent goes back to its own state: running agents can call tools again, agents paused or stopped individually stay that way, and processes killed by STOP ALL are not restarted.

Threat model

What Halt protects against

  • An agent that ignores "stop". Enforcement happens in the harness hook and at the OS level, before the tool runs. The model is never asked.
  • Runaway loops and bulk damage. Per-agent circuit breakers pause the agent at the threshold (default: deletion #21 within 5 minutes).
  • Work already in flight. The supervisor freezes or kills the process tree mid-command, including grandchildren, detached daemons and processes left behind by a parent that exited.
  • Prompt injection after planted bait. A canary secret appearing anywhere in a tool call's input, or a fetch of a canary link, stops the agent and alerts you.
  • An agent switching Halt off through its tools. A default block rule denies edits to Claude Code settings, the OpenClaw plugin config, and attempts to kill the supervisor or read its token. For a hard guarantee, see the managed-settings and --as rows under "What Halt can't do".
  • A hijacked agent freeing itself. Agent tokens can only ask for decisions and read their own state. They cannot change any state, rule or other agent. That takes the owner key, which agents never get.
  • A killed or crashed supervisor. The supervisor proves it is alive every 2 seconds with a credential the agent never sees. If it goes quiet (killed, crashed, or its network to Halt cut) for longer than the agent's limit (60s by default) without saying goodbye, you get a "supervisor went silent" alarm.
  • A leaked agent token. Each agent token is limited to 10 calls a second (bursts of 40), far above what a model-driven agent does. A flood is denied without filling the activity log, and you get one alarm an hour telling you to rotate the token. The token can't change any state or fake the supervisor.
  • Undoing a safety stop. Stopping is easy, resuming takes more. Alert buttons and the alert page can only stop unless you allow resume, and operator keys for overseer agents can read and stop but never resume, change rules or delete.
  • Halt outages, for high-stakes agents. Every integration has a fail-closed mode (table above). Halt's own errors are non-2xx, never an empty 200, so fail-closed hooks block on them.
  • A leaked notification topic. Alert buttons carry the alert key, which can stop agents but, by default, not resume them. Rotate it in settings.
  • Guessing credentials. Tokens and keys are 192-bit random secrets, stored hashed. Requests with wrong credentials or connect codes are limited per network (30 a minute), and connect codes work once, for 15 minutes.

What Halt can't do

  • Damage before a breaker trips. The first 20 deletions still happen. Set tighter thresholds for data you can't lose.
  • What happens inside one allowed call. A shell command or script is checked once, as text. A loop of 500 deletions inside it counts as one action. The supervisor can still freeze or kill it mid-run, and per-syscall control needs an OS sandbox.
  • Obfuscated commands. Keyword rules are substring matches: a speed bump, not a sandbox. An agent determined to hide what it runs can get past them. Use permissions, an OS sandbox, or a container for that.
  • Same-user processes. An agent running as the same user as its supervisor can kill the supervisor and keep running. You get the silence alarm within the limit, but until you act the agent is unsupervised, and it can read files the supervisor reads. Run the supervisor as root with --as <user>, or use containers with watch mode.
  • Very fast detaching. A process that double-forks into a new session within about a second, before the supervisor samples it, escapes the supervisor. Use containers (docker pause and kill act on the whole cgroup) when that matters.
  • Editable hook settings. Claude Code reloads hooks when settings files change. .claude is a protected path, so writes prompt by default, but bypassPermissions allows them. The tamper rule blocks the obvious edits; /etc/claude-code/managed-settings.json (root-owned) makes it impossible.
  • Calls that bypass every integration. A tool that goes through no hook, plugin or check call is never checked. The supervisor can still freeze or kill the process.
  • Root, or a compromised machine. Anything with admin rights on the machine can remove hooks, edit managed settings and kill the supervisor.
  • Halt itself, or your owner key. Either could allow everything or stop everything. Neither can run code on your machine: no integration executes anything Halt sends, except the watch commands you configured locally.
  • Windows. The supervisor can stop agents (taskkill /T) but cannot pause them.
  • Approvals. Halt never approves anything. "Allowed" means no objection, and Claude Code's own permission rules and prompts apply unchanged.

Hardened setup for unattended agents

Keys

Owner key hok_

Everything: agents, rules, tripwires, alerts, STOP ALL. Stored hashed. Kept by you or an overseer, never by a supervised agent.

Operator key hop_

For overseer agents: read everything, pause, stop and STOP ALL. It can't resume, change rules, tripwires or alerts, delete agents, see other keys, or log in to the dashboard. Revocable.

Agent token hat_

One per agent, hashed and rotatable, used by the hook, plugin and check API. It can only ask for a decision and read its state, so a hijacked agent can't unpause itself. Rate-limited. Sent in headers only, never in URLs.

Supervisor token hsv_

One per agent, for halt.mjs only. Its heartbeats prove the supervisor is alive, and it is removed from the agent's environment, so the agent can't fake them.

Alert key hak_

Rides in phone notifications and chat webhooks. It can stop; it can resume only if you allow it. It signs webhooks (HMAC-SHA256). Rotate it if your ntfy topic leaks.

Connect code

Eight characters, single use, 15 minutes. halt.mjs connect trades it for fresh agent and supervisor tokens and installs them, so no token is pasted into a terminal or chat.

Data

For each checked call, Halt stores the tool name, a summary of up to 500 characters (the shell command, URL, recipient, or the path for file writes, never file contents), the decision and the rule that matched. The full input is scanned in memory for canary secrets and not stored. Activity is deleted after 7 days (Free) or 90 days (Pro). Daily counts are kept for usage statistics without any call contents. Webhook URLs must be public HTTPS hosts, so Halt can't be pointed at internal networks.

Tests you can run

One dependency-free file (Node 20+, macOS or Linux). It creates a throwaway account, downloads the published supervisor, runs real processes under it, and simulates outages with a local proxy it can switch off. Afterwards it kills everything it started and deletes its agents. Read it first: security-tests.mjs.

curl -fsSLO https://aiapp-halt.vercel.app/security-tests.mjs
HALT_URL=https://aiapp-halt.vercel.app node security-tests.mjs          # add --json for machine-readable output
sudo -E HALT_URL=https://aiapp-halt.vercel.app node security-tests.mjs  # also runs the --as isolation test

Tests marked LIMITATION show the documented gaps above. They pass when the gap behaves exactly as described.

Last run: 26 passed, 0 failed, 0 skipped

Against https://aiapp-halt.vercel.app on Sun, 18 Oct 2026 15:28:05 GMT (Node 22.22.0, linux 6.18.44-fc-v139, as root).

Supervisor

✓ passpause freezes the agent and its grandchildren mid-loop; resume continues them; stop kills the whole tree6.5s
✓ passa child that detaches into its own session and outlives its parent is still frozen and killed6.4s
✓ passwhen the main process exits early, what it left running stays supervised4.4s

Outages

✓ passdefault (fail-open): an outage does not stop a running agent, and the supervisor says so3.5s
✓ pass--fail-closed --grace 1: an outage kills the agent after the grace period3.4s
✓ pass--fail-closed refuses to start an agent while Halt is unreachable0.3s
✓ passa paused agent stays frozen through an outage, whatever the fail mode4.4s

Resume semantics

✓ passa stopped agent is never restarted: resuming changes its state only; a relaunch is refused until then6.6s
✓ passSTOP ALL kills supervised agents; RESUME ALL lifts only the flag: individually paused agents stay paused3.9s

Attacks on the supervisor

✓ passlimitation run as the same user, an agent can kill its supervisor and keep running unsupervised2.2s
✓ passwith --as <user> (supervisor as root), the agent cannot kill its supervisor and is still stopped1.5s

Claude Code hook

✓ passallowed calls get an empty 200 (normal permission prompts still apply); a paused agent is denied every tool, MCP tools and subagents included0.6s
✓ passan unknown token gets 401: a non-blocking hook error, unless the hook sets onFailure: block0.1s

Credentials

✓ passan agent token cannot change anything: unpausing itself or STOP ALL through the owner API is refused0.4s

Tool-call rules

✓ passblocks attempts to disable Halt itself: settings edits, managed settings, plugin config, killing the supervisor0.8s
✓ passlimitation keyword rules are a speed bump, not a sandbox: an obfuscated tamper command passes0.2s
✓ passlimitation one shell call is one action: a 500-iteration delete loop inside it counts once0.3s
✓ passa breaker pauses a runaway agent: deletion #21 within 5 minutes is denied and the agent is paused1.7s

Tripwires

✓ passa canary secret anywhere in a tool call's input (not only the summary) stops the agent0.4s

OpenClaw endpoint

✓ passanswers {block:false} while running and {block:true, blockReason} once stopped0.3s

Connection hardening

✓ passthe supervisor's own credential never reaches the agent; the agent keeps its own token1.5s
✓ passkilling the supervisor raises a 'supervisor went silent' alarm (dead-man's switch)24.1s
✓ passa flood (300 calls at once) with a leaked agent token is throttled (denied, not logged) and the owner is told once5.3s
✓ passthe alert key (it rides in notifications) can stop but not resume, unless the owner allows it0.7s
✓ passan operator key (for overseer agents) can watch and stop, but not resume, reconfigure or delete0.9s

Onboarding

✓ passone command connects Claude Code: the hook is merged into existing settings with a backup, and the code works once0.8s

Alongside it, an internal suite of 30 tests covers the route handlers, the OpenClaw plugin file (fail-open, fail-closed and timeouts) and this runner against every build. It passed 30 of 30 on Sun, 18 Oct 2026 15:28:05 GMT. Source for what runs on your machine: halt.mjs, OpenClaw plugin. See also Docs.