How It Works
kiteguard is a single static Rust binary that integrates with AI agent hook systems. When an agent is about to take an action, it calls kiteguard — kiteguard inspects the payload, runs detectors, and exits 0 (allow) or 2 (block).
Claude Code
Claude Code provides a native lifecycle hook system. kiteguard registers in ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [{ "command": "/usr/local/bin/kiteguard" }],
"PreToolUse": [{ "command": "/usr/local/bin/kiteguard" }],
"PostToolUse": [{ "command": "/usr/local/bin/kiteguard" }],
"Stop": [{ "command": "/usr/local/bin/kiteguard" }]
}
}
User prompt
│
[1] UserPromptSubmit ── PII? Injection? → BLOCK
│
[2] PreToolUse ──────── Dangerous cmd? Bad path? Bad URL? → BLOCK
│
tool executes
│
[3] PostToolUse ─────── Injection in output? PII? → BLOCK
│
[4] Stop ────────────── Secrets in response? → REDACT
│
safe response
Cursor
Cursor's hook system fires at 10 distinct points. kiteguard registers in .cursor/hooks.json (project-level) and ~/.cursor/hooks.json (user-level) with failClosed: true on all blocking hooks:
{
"beforeSubmitPrompt": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"preToolUse": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"beforeShellExecution": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"beforeReadFile": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"beforeMCPExecution": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"beforeTabFileRead": [{ "command": "/usr/local/bin/kiteguard", "failClosed": true }],
"postToolUse": [{ "command": "/usr/local/bin/kiteguard" }],
"afterShellExecution": [{ "command": "/usr/local/bin/kiteguard" }],
"afterMCPExecution": [{ "command": "/usr/local/bin/kiteguard" }],
"afterAgentResponse": [{ "command": "/usr/local/bin/kiteguard" }]
}
User prompt
│
[1] beforeSubmitPrompt ── PII? Injection? → BLOCK
│
[2] preToolUse ─────────── Tool call inspection → BLOCK
[3] beforeShellExecution ─ Dangerous cmd? → BLOCK
[4] beforeReadFile ──────── Sensitive path? → BLOCK
[5] beforeMCPExecution ──── MCP SSRF? Injection? → BLOCK
[6] beforeTabFileRead ────── Sensitive tab path? → BLOCK
│
action executes
│
[7] postToolUse ──────── Tool output for injection/PII → LOG
[8] afterShellExecution ─ Shell output → LOG
[9] afterMCPExecution ─── MCP result for secrets → LOG
[10] afterAgentResponse ── Final response for PII → LOG
│
safe response
Client is auto-detected via the CURSOR_PROJECT_DIR environment variable.
Gemini CLI
Gemini CLI calls kiteguard with a JSON payload and reads a {"decision":"allow"} / {"decision":"deny", ...} JSON response on stdout.
{
"hooks": {
"before_tool": "/usr/local/bin/kiteguard",
"after_tool": "/usr/local/bin/kiteguard"
}
}
Fail-closed behavior
If kiteguard crashes or encounters an internal error, it exits 2 — blocking the action. It never fails open. For Cursor, failClosed: true is set in the hooks config so Cursor itself also blocks if the process fails to start.
Audit log
Every event — allowed or blocked — is written to ~/.kiteguard/audit.log as append-only JSONL. Prompt content is never logged; only a SHA-256 hash is stored. The log has a tamper-evident hash chain.