Architecture

Overview

kiteguard is a single static Rust binary. No runtime, no dependencies, no daemon.

Source structure

src/
├── main.rs              — entrypoint, client detection, hook dispatcher, fail-closed logic
├── hooks/               — one handler per hook event
│   ├── pre_prompt.rs    — UserPromptSubmit / beforeSubmitPrompt
│   ├── pre_tool.rs      — PreToolUse / preToolUse / beforeShellExecution / beforeReadFile / beforeMCPExecution / beforeTabFileRead
│   ├── post_tool.rs     — PostToolUse / postToolUse / afterShellExecution / afterMCPExecution
│   └── post_response.rs — Stop / afterAgentResponse
├── detectors/           — pure detection logic, no side effects
│   ├── commands.rs      — dangerous bash patterns
│   ├── injection.rs     — prompt injection
│   ├── paths.rs         — sensitive file paths
│   ├── pii.rs           — SSN, CC, email, phone
│   ├── secrets.rs       — API keys, tokens, credentials
│   └── urls.rs          — URL blocklist + SSRF
├── engine/
│   ├── policy.rs        — loads rules.json, provides defaults
│   ├── evaluator.rs     — routes inputs through detectors
│   └── verdict.rs       — Allow / Block / Redact enum
└── audit/
    ├── logger.rs        — append-only JSONL audit log
    └── webhook.rs       — optional HTTP event sink

Client detection

main.rs auto-detects which agent called kiteguard:

CLAUDE_HOOK_EVENT env set     → Claude Code path
CURSOR_PROJECT_DIR env set    → Cursor path
hookEventName in JSON payload → Cursor path (fallback)
hook_event_name in JSON payload → Gemini CLI path

This ensures correct response format (exit code vs JSON stdout) and correct event routing.

Data flow

stdin JSON
    │
    ▼
main.rs → detect client → parse payload → load policy → dispatch by event name
    │
    ▼
hooks/*.rs → engine/evaluator.rs → detectors/*.rs
    │
    ▼
Verdict: Allow | Block | Redact
    │
    ├── audit/logger.rs → ~/.kiteguard/audit.log
    ├── audit/webhook.rs → optional HTTP POST
    └── exit(0) or exit(2)   → Claude Code / Cursor reads exit code
           — OR —
        JSON stdout          → Gemini CLI reads {"decision":"allow/deny"}

Block response formats

AgentAllowBlock
Claude Codeexit 0exit 2
Cursorexit 0, stdout {}exit 2
Gemini CLIexit 0, stdout {"decision":"allow"}exit 0, stdout {"decision":"deny", "reason":"..."}

Design principles

  • Fail-closed — crashes block, never allow
  • No prompt content in logs — only SHA-256 hashes stored
  • Single binary — no install friction
  • Pure detectors — no side effects, easy to test
  • Local first — zero network calls unless webhook is explicitly configured