Update (v1.0.13): The claim that the cursor hook works for Claude Code is incorrect. The cursor hook outputs
permission: allow/deny— Claude Code expectshookSpecificOutput.permissionDecision. A dedicated Claude Code integration is now available:npx @aporthq/aport-agent-guardrails claude-code
Cursor and VS Code with GitHub Copilot support config-driven hooks that run before shell execution or tool use. The APort hook script reads JSON from stdin, calls the existing APort guardrail (policy + passport), and returns allow/deny; exit 2 blocks the action.
| Use case | What it is | When to use it |
|---|---|---|
| Guardrails (CLI/setup) | One-line installer: runs the passport wizard, writes ~/.cursor/hooks.json with the path to the APort hook script. Does not run Cursor for you. |
Getting started: create passport and install the hook so Cursor calls our script before the agent runs a command or tool. |
| Core (runtime) | The hook script (bin/aport-cursor-hook.sh) and evaluator (bash or API): when the agent runs a command/tool, Cursor invokes the script; we verify and return allow/deny. Optionally, the Node package @aporthq/aport-agent-guardrails-cursor exposes Evaluator and getHookPath() if you need them in code. |
Guardrails = after setup, the hook runs automatically. Use the Node package only if you're building tooling that needs the evaluator or hook path. |
For Cursor, you almost always use Guardrails (CLI) once to install the hook; the Core behavior (the script + evaluator) then runs automatically whenever the agent uses the terminal or a tool.
- Hooks: Cursor uses
~/.cursor/hooks.json(or.cursor/hooks.jsonin the project). Hooks such asbeforeShellExecutionandpreToolUserun a command (our script). The host sends JSON to stdin and reads JSON from stdout; exit code 2 = block. - VS Code Copilot: Agent hooks (Preview) use
~/.claude/settings.jsonor.github/hooks/*.jsonwithPreToolUse; same idea: command, stdin JSON, stdout JSON, exit 2 = block. - Claude Code: Uses
~/.claude/settings.jsonwith a different output format (hookSpecificOutput.permissionDecision). Use the dedicated Claude Code integration instead of this Cursor hook — see claude-code.md.
Our script accepts Cursor- and Copilot-style payloads (e.g. command, or tool/input), maps to the system.command.execute policy, calls the bash guardrail, and returns permission: allow|deny plus optional agentMessage.
Hook script path: The hook script (aport-cursor-hook.sh) resolves bin/aport-guardrail-bash.sh relative to its own directory (script dir → parent = package root). When you install via npx, the installer writes the path to the script inside the npx cache (e.g. …/node_modules/@aporthq/aport-agent-guardrails/bin/aport-cursor-hook.sh), so the guardrail script is found at …/bin/aport-guardrail-bash.sh. If you copy the hook script elsewhere, ensure bin/aport-guardrail-bash.sh exists at the same relative location or set APORT_GUARDRAIL_SCRIPT (or equivalent) so the hook can find the evaluator.
npx @aporthq/aport-agent-guardrails cursor
# or
npx @aporthq/aport-agent-guardrails --framework=cursorThis runs the passport wizard and writes ~/.cursor/hooks.json with the path to the APort hook script. The wizard uses a framework-specific default for where to store the passport: for Cursor the default is ~/.cursor/aport/passport.json (so passport and evaluation data live with Cursor’s own data). The first question in the wizard is “Passport file path [default]:” — press Enter to use that default or type a different path. In non-interactive mode you can pass --output /path/to/passport.json to choose the path. Restart Cursor (or reload the window) after setup so the hooks are loaded.
- No
~/.cursor/hooks.json? That file is created when you run the installer. If you getNo such file or directory, the Cursor integration is not installed yet. Run:(ornpx @aporthq/aport-agent-guardrails cursor
npx @aporthq/aport-agent-guardrails --framework=cursor). The installer writes~/.cursor/hooks.jsonand runs the passport wizard. - Hooks file: After installing, open
~/.cursor/hooks.json(user-level) or.cursor/hooks.json(project). You should seebeforeShellExecutionand/orpreToolUseentries whosecommandis the path toaport-cursor-hook.sh. - Restart required: Cursor loads hooks at startup. After installing, restart Cursor (or Reload Window from the command palette) so the new hooks are active.
- Passport: The hook uses the passport created by the wizard. The default path for Cursor is
~/.cursor/aport/passport.json(each framework has its own default; see Default paths below). The resolver probes~/.cursor, then~/.openclaw, etc., so the hook finds the passport without extra config.
The guardrail only runs when the Cursor agent is about to run a shell command or use a tool. It does not run when you type commands in the terminal yourself.
| Who runs the command | Hook runs? | Guardrail can block? |
|---|---|---|
You type rm file in the Cursor terminal |
No | No — it’s your shell, not the agent. |
| The agent runs a command (e.g. after you ask “run rm file”) | Yes (beforeShellExecution) |
Yes — exit 2 blocks the agent’s command. |
| The agent uses a tool that sends a command | Yes (preToolUse) |
Yes. |
| The agent uses a built-in “delete file” action (no shell) | No | No — direct file API, no hook. |
So:
- Checked: When the agent runs a command in the terminal (e.g.
rm file,npm install) or uses a tool that goes through the hook → our script runs and can block (exit 2). - Not checked: (1) You typing in the terminal — the hook is never invoked. (2) The agent using a built-in “delete file” / “edit file” action (editor API) — no shell, so no hook.
To test that the guardrail is working, ask the agent to run a terminal command your passport blocks (e.g. “Run in the terminal: rm -rf /path/to/file”). Do not type the command yourself in the terminal — that bypasses the hook.
Two ways to test: (1) Run the hook from the terminal to verify the script and populate the audit log. (2) Ask the Cursor agent to run a command in chat to verify the full installation.
From the repo root (or wherever the hook script lives):
# Allow path (e.g. cat a file) — exit 0
echo '{"command":"cat test.md"}' | bin/aport-cursor-hook.sh
echo "Exit: $?"
# Deny path (e.g. rm -rf) — exit 2
echo '{"command":"rm -rf test.md"}' | bin/aport-cursor-hook.sh
echo "Exit: $?"After running the hook (or after the agent runs a command), check the passport and decisions:
# From repo root: status (passport, capabilities, limits, latest decision, recent activity)
bin/aport-status.sh
# Audit log: one line per decision (timestamp, tool, decision_id, allow/deny, policy, context e.g. command)
cat ~/.cursor/aport/audit.log
# Last decision (full OAP JSON)
cat ~/.cursor/aport/decision.jsonIf you used a different passport path during setup, the audit log and decision file are in that path’s aport/ dir (e.g. ~/.openclaw/aport/ if you chose the OpenClaw default).
In Cursor chat, ask the agent to run a command (do not type it in the terminal yourself):
- Should allow: “Run in the terminal:
cat test.md” — command runs; audit log gets anallow=trueline. - Should block: “Run in the terminal:
rm -rf test.md” — Cursor should block the command; audit log gets anallow=falseline.
Then run bin/aport-status.sh and cat ~/.cursor/aport/audit.log to confirm the new entries.
- Hooks file:
~/.cursor/hooks.json(user) or.cursor/hooks.json(project). The installer writes the former by default. - Passport and default paths: Each framework stores passport and evaluation data in its own default location. For Cursor the default is
~/.cursor/aport/passport.json(withdecision.jsonandaudit.login~/.cursor/aport/). You can always choose a different path: in the wizard the first question is the passport path (default shown in brackets); in non-interactive mode use--output /path/to/passport.json. The Python evaluator and bash resolver use the same default-path map (e.g.python/aport_guardrails/core/evaluator.py→DEFAULT_PASSPORT_PATHS,bin/lib/config.sh→get_default_passport_path). - Hook script:
bin/aport-cursor-hook.shin this repo (or in the npm package when installed via npx). The installer puts its absolute path intohooks.json. The hook does not set a config dir; the path resolver probes~/.cursor,~/.openclaw,~/.aport/langchain, etc., and uses the first directory that containsaport/passport.json.
- Passport status: Run
bin/aport-status.sh(from repo) or the guardrail’s status script. It uses the same path resolution as the hook (probes~/.cursor,~/.openclaw, etc.), so it will show the passport under~/.cursor/aport/if that’s where you created it. - Audit trail: Allow/deny decisions are appended to the audit log in the same data dir as the passport (e.g.
~/.cursor/aport/audit.logwhen using the Cursor default). Each line includes timestamp, tool, decision_id, allow/deny, policy id, and context (the actual command forsystem.command.execute, recipient for messaging, repo/branch for merge).bin/aport-status.shshows this context in Latest Decision and Recent Activity.
Same as all frameworks: passport is the source of truth. Set passport status to suspended (or active to resume). The guardrail denies every call until the passport is active again.
- VS Code + GitHub Copilot: Add a PreToolUse hook in
~/.claude/settings.json(or project.claude/settings.json, or.github/hooks/*.json) that runs the same script. See Agent hooks (Preview).
For Claude Code, use the dedicated Claude Code integration instead — it uses the correct output format (hookSpecificOutput.permissionDecision) and supports all Claude Code tool types.
The script accepts multiple input shapes (e.g. command, tool/input) and returns the host-expected JSON; exit 0 = allow, exit 2 = block.
If you need the evaluator or hook path in your own Node/TypeScript code (e.g. custom tooling or scripts):
npm install @aporthq/aport-agent-guardrails-cursor # or -core if you only need Evaluatorimport { Evaluator, getHookPath } from '@aporthq/aport-agent-guardrails-cursor';
// Default path where the hook script is expected (~/.cursor/aport-cursor-hook.sh)
const hookPath = getHookPath();
// Use the evaluator programmatically (same as @aporthq/aport-agent-guardrails-core)
const evaluator = new Evaluator(null, 'cursor');
const decision = evaluator.verifySync({}, { capability: 'system.command.execute.v1' }, { tool: 'run_command', input: 'ls' });Runtime enforcement in Cursor is done by the hook script, not by this package; the package is for programmatic use only.
- Unit: Hook script with mock stdin — allow (exit 0, JSON
allowed: true), deny (exit 2,allowed: false). Seetests/unit/test-cursor-hook.sh. - Integration: Run script with sample Cursor-style JSON; assert output format and exit code. Cursor setup:
tests/frameworks/cursor/setup.sh(writes hooks.json, config dir).
Implemented (Story E). APort Agent Guardrail for Cursor. Installer: npx @aporthq/aport-agent-guardrails cursor; hook script: bin/aport-cursor-hook.sh; config: ~/.cursor/hooks.json. Same script usable for VS Code Copilot. For Claude Code, use the dedicated integration: npx @aporthq/aport-agent-guardrails claude-code (see claude-code.md).