Architecture Overview
High-Level Flow
Dashed nodes are optional hooks, installed with install --optional.
Cursor limitation
postToolUse and afterAgentResponse are observe-only. Cursor processes tool outputs and displays AI responses before these hooks fire — violations are logged and warnings emitted, but content cannot be blocked or retracted. See Cursor Limitation for details.
Module Map
| Module | Purpose |
|---|---|
src/config.ts | Load and validate airs-config.json with env var resolution |
src/airs-client.ts | SDK wrapper with circuit breaker integration |
src/scanner.ts | Scan orchestration, DLP masking, UX block messages |
src/code-extractor.ts | Extract code blocks from AI responses |
src/tool-name-parser.ts | Parse MCP:server:tool format tool names |
src/content-limits.ts | Configurable skip/truncate thresholds (applied by the scanner to all directions) |
src/logger.ts | Structured JSON Lines logging with rotation |
src/circuit-breaker.ts | Failure tracking, cooldown bypass, automatic recovery |
src/dlp-masking.ts | Per-service enforcement actions (block/mask/allow) |
src/log-rotation.ts | Log file rotation at 10MB threshold |
src/types.ts | TypeScript interfaces for config, Cursor API, AIRS |
src/hooks/before-submit-prompt.ts | Cursor beforeSubmitPrompt entry point (can block) |
src/hooks/before-mcp-execution.ts | Cursor beforeMCPExecution entry point (can block) |
src/hooks/post-tool-use.ts | Cursor postToolUse entry point (observe-only; optional MCP output sanitization) |
src/hooks/after-agent-response.ts | Cursor afterAgentResponse entry point (observe-only) |
src/hooks/before-shell-execution.ts | Cursor beforeShellExecution entry point (optional, can block) |
src/hooks/after-shell-execution.ts | Cursor afterShellExecution entry point (optional, observe-only) |
src/hooks/before-read-file.ts | Cursor beforeReadFile entry point (optional, can block) |
src/hooks/before-tab-file-read.ts | Cursor beforeTabFileRead entry point (optional, can block) |
src/hooks/subagent-start.ts | Cursor subagentStart entry point (optional, can block) |
src/hooks/after-mcp-execution.ts | Cursor afterMCPExecution entry point (optional, observe-only) |
scripts/lib/hook-registry.ts | Registry of core + optional hooks driving install/uninstall/verify/doctor |
Request Lifecycle
Prompt Scan (beforeSubmitPrompt)
- Cursor pipes
{ prompt, user_email, ... }as JSON to stdin - Hook loads config, initializes logger
- Scanner sends prompt to AIRS via SDK (
promptcontent key) - Circuit breaker gates the request (bypass if open)
- AIRS returns verdict + detections
- If
enforcemode and verdict isblock: output{ "continue": false, "user_message": "..." } - If
observeor verdict isallow: output{ "continue": true }
MCP Tool Scan (beforeMCPExecution — can block)
- Cursor pipes
{ tool_name, tool_input, ... }as JSON to stdin - Hook loads config, initializes logger
- Content limits check: if input exceeds
max_scan_bytes, skip scan (fail-open) - Scanner sends tool input to AIRS via
tool_eventcontent key usingprofiles.tool - Circuit breaker gates the request (bypass if open)
- AIRS returns verdict + detections
- If
enforcemode and verdict isblock: output{ "continue": false, "user_message": "..." } - If
observeor verdict isallow: output{ "continue": true }
Tool Output Scan (postToolUse — observe-only)
- Cursor pipes
{ tool_name, tool_input, tool_output, ... }as JSON to stdin (after tool already executed) - Hook loads config, initializes logger, parses tool name
- Route by tool type:
MCP:*→ scan input+output astool_eventBash→ scan output asresponseWrite/Edit→ scan new content asprompt(DLP)- Skip list → no scan
- Content limits applied: truncate or skip oversized content
- If violation detected: log to audit trail + emit warning (cannot block — observe-only)
Response Scan (afterAgentResponse — observe-only)
- Cursor pipes
{ text, ... }as JSON to stdin (after the response is already displayed) - Hook loads config, initializes logger
- Code extractor splits response into natural language + code blocks
- Scanner sends both to AIRS (
response+code_responsecontent keys) code_responsetriggers WildFire/ATP malicious code detection- If violation detected: log to audit trail + emit warning (cannot block — observe-only)
- If clean: output
{ "permission": "allow" }
Build Modes
| Mode | Command | Hook Execution | Startup |
|---|---|---|---|
| Production | node dist/hooks/*.js | Precompiled JS | ~800ms |
| Development | npx tsx src/hooks/*.ts | JIT TypeScript | ~2.5s |
The install-hooks script points at compiled JS by default. See Contributing for the development workflow.
Optional Hook Scans
The optional hooks reuse the same lifecycle with different content routing:
beforeShellExecution→ command text astool_event(profiles.tool), deny + exit 2 on enforce-mode blockafterShellExecution→ terminal output asresponse(profiles.response), observe-onlybeforeReadFile/beforeTabFileRead→ file contents asprompt(profiles.prompt, DLP), deny on enforce-mode blocksubagentStart→ task text asprompt, deny on enforce-mode blockafterMCPExecution→ tool input + result JSON astool_event, observe-onlypostToolUsewithsanitize_mcp_output: true→ flagged MCP output replaced viaupdated_mcp_tool_output