cmdcheck is a fast, standalone command-policy gate written in Rust. It sends proposed shell commands to the local Nimble decision model for Ollama and returns ALLOW, ASK, or DENY.
Use it from a terminal or integrate it with agents, shells, CI systems, and task runners. The policy engine is not tied to a particular hook system.
- Harm detection with configurable thresholds.
- Required proxy enforcement across ports and protocols.
- Semantic matching instead of exact command-string matching.
- Recursive inspection of shell, Python, Ruby, Perl, and Node scripts.
- Ordered allow/deny exceptions and custom policy questions.
- Build-time policy embedding—no configuration-file read at runtime.
- Optional live proxy preflight with a TTL cache.
Install Nimble, make sure Ollama exposes /v1/systemone, and edit cmdcheck.toml for your environment. Then build:
./rebuildCheck a command:
$ target/release/cmdcheck curl example.com
DENY: Rule matched: required_proxy = "http://127.0.0.1:8080"
$ target/release/cmdcheck curl example.com --proxy localhost:8080
ALLOW: Command passed configured checksNimble understands that localhost:8080 and 127.0.0.1:8080 refer to the same local proxy, as well as variations in option order and command syntax. Use --explain to print the underlying model scores to stderr while keeping the decision on stdout clean.
Important
cmdcheck returns a policy decision; it does not execute the proposed command. A hook host normally executes an allowed command. Expand the standalone wrapper below to do the same from a shell.
Execute approved commands from a shell
This wrapper preserves argument boundaries and invokes the command only after ALLOW:
cmdcheck_run() {
result="$(target/release/cmdcheck -- "$@")"
printf '%s\n' "$result"
case "$result" in
ALLOW:*) command "$@" ;;
ASK:*) return 125 ;;
DENY:*) return 126 ;;
*) return 127 ;;
esac
}$ cmdcheck_run curl example.com --proxy localhost:8080
ALLOW: Command passed configured checks
<curl output follows>ASK and DENY never invoke the command and return a nonzero status.
CLI options and input modes
CLI switches override the compiled enable/disable settings:
--harm / --no-harm
--proxy / --no-proxy
--preflight / --no-preflight
--explain
Pass the command as arguments:
target/release/cmdcheck -- curl https://example.comOr as plain text on stdin:
printf '%s' 'curl https://example.com' | target/release/cmdcheckExample integration: Claude Code
Claude Code sends a structured PreToolUse event on stdin. cmdcheck recognizes the event and returns the corresponding hook JSON:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/cmdcheck/target/release/cmdcheck"
}
]
}
]
}
}Test the adapter directly:
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"curl https://example.com"}}' \
| target/release/cmdcheckThis is one optional adapter; the core policy engine remains integration-independent.
Policy behavior and script inspection
The build script validates the policy and embeds it in the binary. Re-run ./rebuild after changing cmdcheck.toml.
Evaluation order:
- Ordered exception rules. The first match wins, so a first-match
allowapproves immediately. - Recursive script inspection.
- Nimble harm, proxy, special-instruction, and custom-question checks in one request.
- Optional proxy preflight for network commands.
Script inspection follows statically identifiable child scripts, resolves paths relative to the parent, avoids cycles, and applies bounded limits:
[script_inspection]
max_file_bytes = 262144
max_total_bytes = 524288
max_depth = 4
max_scripts = 16
on_truncated = "ask"
on_unreadable = "ask"
on_dynamic_child = "ask"
on_limit = "ask"Each outcome accepts allow, ask, or deny. Dynamic constructs such as bash "$SCRIPT_PATH" cannot be resolved safely before execution and use on_dynamic_child.
on_error controls Ollama, response, and proxy-preflight failures. on_pass controls successful decisions. Denials identify the compiled rule that was enforced.