Policy engine
Block / ask / allow harness tool calls based on built-in risk rules.
How it works#
Claude Code lets you register a PreToolUse hook in ~/.claude/settings.json — every tool call (Bash, Edit, Write, …) runs through the hook before claude executes it. The hook reads the tool input on stdin, decides allow / ask / deny, and writes that decision back on stdout. Controlum ships cu policy-check as that hook.
Install#
cu install-hook # idempotent; merges into ~/.claude/settings.json
cu install-hook -u # uninstallThe installer adds a single PreToolUse entry tagged description: "controlum-policy" with matcher Bash|ExecuteBash. Your existing hooks are preserved.
What gets blocked#
The catalogue is ordered most-dangerous-first; first match wins.
critical:
rm-rf-root rm -rf / → deny
curl-pipe-shell curl … | sh → deny
high:
ssh-key-access ~/.ssh/id_rsa → deny
aws-creds-access ~/.aws/credentials→ deny
sudo sudo … → ask
netcat nc / ncat / netcat→ ask
medium:
rm-rf rm -rf <path> → ask
git-force-push git push --force → ask
package-install apt / npm / pip … → ask
else: → allowCustom rules (per user/cwd)#
Author your own rules on the /policies page. Each rule has a pattern (regex or substring), an action (allow / ask / deny), and optional scope filters (cwd_prefix, tool_name). User rules run BEFORE the built-in catalogue — a user allow overrides a built-in deny, and vice versa.
Backend pushes the full rule set over WS to every daemon for your user on every CRUD edit; the daemon writes it atomically to ~/.controlum/policy-rules.json. cu policy-check reads that file on every hook invocation. Fail-open: missing file → built-in catalogue only.
Audit log#
Every classification (allow / ask / deny) is forwarded to the daemon over IPC, then to the backend over WS, and persisted in policy_checks (see migration 0003_policy_checks). Surfaced on each session's Permissions tab — newest decision first, coloured by risk:
- green dot — low risk / allowed
- yellow — medium / ask
- orange — high / ask or deny
- red — critical / deny
If you don't see anything in the tab, the hook isn't installed — cu install-hook fixes it.
Escape hatch#
Set CU_POLICY_ALLOW_ALL=1 in the environment claude runs from. This short-circuits the classifier to allow. Use sparingly — it disables every rule including the critical ones.
Claude only — codex coverage#
The policy engine works exclusively for Claude Code sessions today: it plugs into claude's PreToolUse hook contract. Codex (OpenAI's CLI) doesn't expose an analogous hook, so for codex sessions only the harness's native flags apply — --ask-for-approval <untrusted|on-failure|on-request|never> + --sandbox <read-only|workspace-write|danger-full-access>. The mapping from --cu-mode to those flags lives in Permission modes.
When you need policy-style enforcement around a codex session, run it through the ephemeral sandbox with network = none and a read-only mount — the harness is bounded by the container, not by an in-process classifier.
What's next#
- Edit/Write tool inspection (currently allow-by-default since claude permission-gates them).
- Per-machine rule scope (currently per-user; per-machine would let one daemon enforce stricter rules than another).
Pairs with: sandbox egress allowlist#
The policy engine blocks risky commands at the harness hook. For codex (no hook) and as a second layer for claude, pair this with the sandbox network=allowlist mode: only the hostnames you list can be CONNECTed by anything in the harness container. Two layers, both enforced — the policy engine answers which commands? and the allowlist answers which hosts?.