tool-call-warrant
v0.1.0
Published
Pre-execution guard layer for AI agent tool calls. Warrants are structured confirmations that pass through a rule engine before a tool is allowed to execute.
Maintainers
Readme
tool-call-warrant v0.1.0
Pre-execution guard layer for AI agent tool calls. Warrants are structured confirmations that pass through a rule engine before a tool is allowed to execute.
Why this exists
AI coding agents (Claude Code, Cursor, AGY, Gemini CLI) execute tool calls
with zero pre-execution confirmation. When an agent decides to strace
a production server, delete 50k files, DROP TABLE, or POST to a webhook,
the human sees only the retrospective log. This is the core "rogue agent"
failure mode that developers complain about in production.
tool-call-warrant is a zero-dependency, cross-runtime (Python + Node)
plugin that intercepts tool calls before execution and returns a structured
warrant verdict: ALLOW, DENY, MODIFY, or CONFIRM.
Install
Python
pip install -e .
warrant-check --helpNode
npm install -g .
warrant-check --help # uses scripts/warrant-check.mjs via package.json `bin`Zero runtime dependencies in both languages.
dependencies = []/"dependencies": {}.
Quickstart
As a CLI
# Simulate a destructive call — returns DENY (exit 1)
warrant-check check --tool bash --args '{"command":"rm -rf /app"}' --format json
# Simulate a safe call — returns ALLOW (exit 0)
warrant-check check --tool mcp__filesystem__ls --args '{"path":"/app/docs"}'
# Force ALLOW despite DENY (human override; logs to audit trail)
warrant-check check --tool bash --args '{"command":"rm -rf /app"}' --confirm
# Inspect audit log
warrant-check audit --since 2026-08-09T00:00:00Z --format jsonAs a library (Python)
import sys; sys.path.insert(0, "src")
from warrant_core import classify_risk, get_verdict
risk, matched = classify_risk("bash", {"command": "rm -rf /"})
# risk = "DESTROY"; matched = ["destroy:recursive delete"]
verdict = get_verdict(risk) # "DENY"As a library (Node)
import { classifyRisk, getVerdict } from 'tool-call-warrant';
const [risk, matched] = classifyRisk("bash", { command: "rm -rf /" });
// risk = "DESTROY"; matched = ["destroy:recursive delete"]
const verdict = getVerdict(risk); // "DENY"Risk Classification
| Risk | Patterns | Verdict | Exit code |
|---|---|---|---|
| DESTROY | rm -rf, DROP TABLE, TRUNCATE, kill -9, shutdown, mkfs | DENY | 1 |
| EXFILTRATE | Private IPs, curl \| sh, eval(base64_decode(...)), ~/.env, DB dumps, printenv | CONFIRM | 2 |
| EXECUTE | bash -c, eval(, subprocess, child_process.spawn, gcc/go build/rustc/npm install, \| sh | CONFIRM | 2 |
| OBSERVE | ps aux, SELECT *, chmod 777, ls -R /, cat ~/.ssh/id_rsa | ALLOW | 0 |
| BENIGN | ls, git status, cat README.md, find ., npm test | ALLOW | 0 |
| UNKNOWN | (no pattern matched) | CONFIRM | 2 |
Global blocks (always force DENY → DESTROY):
curl <anything> | shwget -O- | sheval(base64_decode(...))
CLI Subcommands
| Subcommand | Purpose | Exit codes |
|---|---|---|
| check | Evaluate a tool call against rules | 0=ALLOW/MODIFY, 1=DENY, 2=CONFIRM |
| diff | Show arg changes (proposed → allowed) | 0=ok, 1=invalid input |
| audit | Read historical warrant decisions | 0=ok, 1=invalid input |
| parse | Validate warrant JSON schema | 0=valid, 1=invalid |
check flags
| Flag | Effect |
|---|---|
| --tool NAME | (required) Tool name |
| --args JSON | Tool arguments as JSON object (default {}) |
| --rules PATH | Path to rules file (reserved for v0.2) |
| --format json\|text | Output format (default text) |
| --confirm | Human override: force ALLOW |
| --reject | Human override: force DENY |
| --modify | Human override: emit MODIFY verdict |
| --dry-run | Do not write audit log entry |
| --audit | With --dry-run: still write audit log |
Hook Integration (per-agent)
{
"event": "pre_tool_call",
"tool": "*",
"handler": "warrant-check --tool ${TOOL_NAME} --args ${TOOL_ARGS} --format json",
"on_allow": "proceed",
"on_deny": "block_and_log",
"on_confirm": "pause_and_notify"
}See skills/tool-call-warrant/SKILL.md for per-framework integration notes.
Cited Evidence (Honest Pillar)
- HN Ask: How do you enforce permissions for AI agent tool calls in production? — 350+ points, multiple "how do I prevent rogue agents" threads
- HN signal: "AgentWard — After an AI agent deleted files, I built a runtime enforcer."
- HN signal: "Runtime security for AI agents (injection, tool abuse, data exfiltration)"
- GitHub Search:
@twire/guard(Edge/Node only, 0.1.1) and@jc4649/pi-toolcall-guard(pi-specific, 0.1.0) — both narrow in scope, neither Python+Node cross-runtime - npm + PyPI:
tool-call-guard,tool-call-confirm,tool-interceptor— no cross-runtime distribution found
Named Competitors
| Package | Runtime | Limit |
|---|---|---|
| @twire/guard | Edge/Node only | No Python, narrow scope |
| @jc4649/pi-toolcall-guard | pi-agent only | Framework-locked, no general plugin |
Limitations / Non-Goals
- Rule engine is synchronous, single-process. No distributed enforcement across hosts.
- Rules file is plaintext. No schema validation beyond JSON syntax (custom rules loader is v0.2).
- First-run UX requires manual
--auditto inspect behavior before enabling auto-confirm. - Not an OS-level sandbox. Use seccomp, firejail, gVisor for kernel-level isolation.
- Not an LLM-side guardrail. Does not filter agent output or detect prompt-injection.
- Not a substitute for human review. Edge cases (compound operations, multi-step plans) need operator judgment.
Plugin Manifest
See plugin.json for the full agent-runtime plugin declaration (hooks,
scripts, rules, skills). The manifest declares dependencies: [] per spec.
Architecture
tool-call-warrant/
├── plugin.json # Agent-runtime manifest
├── pyproject.toml # Python packaging (zero deps)
├── package.json # Node packaging (zero deps)
├── src/
│ ├── warrant_core.py # Python rule engine (zero deps)
│ ├── index.mjs # Node rule engine mirror (zero deps)
│ └── index.d.ts # TypeScript definitions
├── scripts/
│ ├── warrant_check.py # Python CLI
│ ├── warrant-check.mjs # Node CLI
│ └── pre-push-gate.sh # Mechanical gate before push
├── rules/
│ └── tool-warrant-rules.md
├── skills/
│ └── tool-call-warrant/SKILL.md
├── tests/
│ ├── COVERAGE.md # 50 enumerated acceptance criteria
│ ├── test_warrant.py # 128 pytest tests
│ └── test_warrant.mjs # 107 Node tests
├── README.md
├── LICENSE # MIT
├── CHANGELOG.md
└── QA_REPORT.md # Self-review (build card)Verification
pytest -q # 128 passed
npm test # 107 passed
warrant-check --help # CLI works
node scripts/warrant-check.mjs --helpLicense
MIT — see LICENSE.
Changelog
See CHANGELOG.md.
