@aporthq/aport-agent-guardrails 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,7 +54,25 @@ while [[ $# -gt 0 ]]; do
54
54
  esac
55
55
  done
56
56
 
57
- # If no framework from args, try APORT_FRAMEWORK (non-interactive) or detection
57
+ # If no framework from args, check if first REST argument is a framework name
58
+ if [[ -z "$framework" ]] && [[ ${#REST[@]} -gt 0 ]]; then
59
+ first_arg="${REST[0]}"
60
+ # Check if first arg looks like a framework name (lowercase alphanumeric + hyphen)
61
+ if [[ "$first_arg" =~ ^[a-z0-9-]+$ ]]; then
62
+ # Valid framework names
63
+ valid_frameworks=(openclaw langchain crewai cursor claude-code n8n)
64
+ for valid_fw in "${valid_frameworks[@]}"; do
65
+ if [[ "$first_arg" == "$valid_fw" ]]; then
66
+ framework="$first_arg"
67
+ # Remove framework from REST so remaining args are pass-through
68
+ REST=("${REST[@]:1}")
69
+ break
70
+ fi
71
+ done
72
+ fi
73
+ fi
74
+
75
+ # If still no framework from args, try APORT_FRAMEWORK (non-interactive) or detection
58
76
  if [[ -z "$framework" ]]; then
59
77
  if [[ -n "${APORT_FRAMEWORK:-}" ]]; then
60
78
  framework="$APORT_FRAMEWORK"
@@ -109,6 +109,64 @@ DEFAULT_AGENT_NAME=${DEFAULT_AGENT_NAME:-"OpenClaw Agent"}
109
109
  DEFAULT_AGENT_DESC=$(get_identity_description) || true
110
110
  DEFAULT_AGENT_DESC=${DEFAULT_AGENT_DESC:-"Local OpenClaw AI agent with APort guardrails"}
111
111
 
112
+ # Framework-aware defaults: each framework needs different capabilities to function
113
+ case "${APORT_FRAMEWORK:-}" in
114
+ claude-code)
115
+ # Claude Code: file ops, web, sub-agents, MCP tools are core functionality
116
+ DEFAULT_FILE_READ=y
117
+ DEFAULT_FILE_WRITE=y
118
+ DEFAULT_WEB_FETCH=y
119
+ DEFAULT_WEB_BROWSER=y
120
+ DEFAULT_AGENT_SESSION=y
121
+ DEFAULT_MCP_TOOL=y
122
+ ;;
123
+ cursor)
124
+ # Cursor: IDE with shell, file ops, web search, agent mode
125
+ DEFAULT_FILE_READ=y
126
+ DEFAULT_FILE_WRITE=y
127
+ DEFAULT_WEB_FETCH=y
128
+ DEFAULT_WEB_BROWSER=n
129
+ DEFAULT_AGENT_SESSION=y
130
+ DEFAULT_MCP_TOOL=n
131
+ ;;
132
+ crewai)
133
+ # CrewAI: multi-agent framework — agents spawn sub-agents, use tools
134
+ DEFAULT_FILE_READ=y
135
+ DEFAULT_FILE_WRITE=y
136
+ DEFAULT_WEB_FETCH=y
137
+ DEFAULT_WEB_BROWSER=n
138
+ DEFAULT_AGENT_SESSION=y
139
+ DEFAULT_MCP_TOOL=y
140
+ ;;
141
+ langchain)
142
+ # LangChain/LangGraph: tool calling, web, files
143
+ DEFAULT_FILE_READ=y
144
+ DEFAULT_FILE_WRITE=y
145
+ DEFAULT_WEB_FETCH=y
146
+ DEFAULT_WEB_BROWSER=n
147
+ DEFAULT_AGENT_SESSION=n
148
+ DEFAULT_MCP_TOOL=n
149
+ ;;
150
+ n8n)
151
+ # n8n: workflow automation — HTTP, file, database, messaging
152
+ DEFAULT_FILE_READ=y
153
+ DEFAULT_FILE_WRITE=y
154
+ DEFAULT_WEB_FETCH=y
155
+ DEFAULT_WEB_BROWSER=n
156
+ DEFAULT_AGENT_SESSION=n
157
+ DEFAULT_MCP_TOOL=n
158
+ ;;
159
+ *)
160
+ # Generic / unknown framework: conservative defaults
161
+ DEFAULT_FILE_READ=n
162
+ DEFAULT_FILE_WRITE=n
163
+ DEFAULT_WEB_FETCH=n
164
+ DEFAULT_WEB_BROWSER=n
165
+ DEFAULT_AGENT_SESSION=n
166
+ DEFAULT_MCP_TOOL=n
167
+ ;;
168
+ esac
169
+
112
170
  if [ -n "$NON_INTERACTIVE" ]; then
113
171
  # CI/tests: use defaults, no prompts. Use --output or APORT_FRAMEWORK for default path. Match interactive defaults (README: messaging out of the box).
114
172
  owner_id="$DEFAULT_EMAIL"
@@ -119,10 +177,12 @@ if [ -n "$NON_INTERACTIVE" ]; then
119
177
  exec_cap=y
120
178
  msg_cap=y
121
179
  data_cap=n
122
- file_read_cap=n
123
- file_write_cap=n
124
- web_fetch_cap=n
125
- web_browser_cap=n
180
+ file_read_cap=$DEFAULT_FILE_READ
181
+ file_write_cap=$DEFAULT_FILE_WRITE
182
+ web_fetch_cap=$DEFAULT_WEB_FETCH
183
+ web_browser_cap=$DEFAULT_WEB_BROWSER
184
+ agent_session_cap=$DEFAULT_AGENT_SESSION
185
+ mcp_tool_cap=$DEFAULT_MCP_TOOL
126
186
  max_pr_size=500
127
187
  max_prs_per_day=10
128
188
  max_msgs_per_day=100
@@ -195,7 +255,11 @@ else
195
255
  # Choose capabilities
196
256
  echo " 🔐 Capabilities"
197
257
  echo " ───────────────"
198
- echo " Choose what your agent can do (y/n). Defaults: PRs, exec, and messaging = yes (matches README/docs); others = no."
258
+ if [ "${APORT_FRAMEWORK:-}" = "claude-code" ]; then
259
+ echo " Choose what your agent can do (y/n). Claude Code defaults: most capabilities = yes."
260
+ else
261
+ echo " Choose what your agent can do (y/n). Defaults: PRs, exec, and messaging = yes (matches README/docs); others = no."
262
+ fi
199
263
  echo ""
200
264
  read -p " • Create and merge pull requests? [Y/n]: " pr_cap
201
265
  pr_cap=${pr_cap:-y}
@@ -206,21 +270,51 @@ else
206
270
  read -p " • Send messages (email, SMS, etc.)? [Y/n]: " msg_cap
207
271
  msg_cap=${msg_cap:-y}
208
272
 
209
- read -p " Read files from disk? [y/N]: " file_read_cap
210
- file_read_cap=${file_read_cap:-n}
273
+ if [ "$DEFAULT_FILE_READ" = "y" ]; then
274
+ read -p " • Read files from disk? [Y/n]: " file_read_cap
275
+ else
276
+ read -p " • Read files from disk? [y/N]: " file_read_cap
277
+ fi
278
+ file_read_cap=${file_read_cap:-$DEFAULT_FILE_READ}
211
279
 
212
- read -p " Write/edit files on disk? [y/N]: " file_write_cap
213
- file_write_cap=${file_write_cap:-n}
280
+ if [ "$DEFAULT_FILE_WRITE" = "y" ]; then
281
+ read -p " • Write/edit files on disk? [Y/n]: " file_write_cap
282
+ else
283
+ read -p " • Write/edit files on disk? [y/N]: " file_write_cap
284
+ fi
285
+ file_write_cap=${file_write_cap:-$DEFAULT_FILE_WRITE}
214
286
 
215
- read -p " Fetch data from web (HTTP requests)? [y/N]: " web_fetch_cap
216
- web_fetch_cap=${web_fetch_cap:-n}
287
+ if [ "$DEFAULT_WEB_FETCH" = "y" ]; then
288
+ read -p " • Fetch data from web (HTTP requests)? [Y/n]: " web_fetch_cap
289
+ else
290
+ read -p " • Fetch data from web (HTTP requests)? [y/N]: " web_fetch_cap
291
+ fi
292
+ web_fetch_cap=${web_fetch_cap:-$DEFAULT_WEB_FETCH}
217
293
 
218
- read -p " Automate web browser? [y/N]: " web_browser_cap
219
- web_browser_cap=${web_browser_cap:-n}
294
+ if [ "$DEFAULT_WEB_BROWSER" = "y" ]; then
295
+ read -p " • Automate web browser? [Y/n]: " web_browser_cap
296
+ else
297
+ read -p " • Automate web browser? [y/N]: " web_browser_cap
298
+ fi
299
+ web_browser_cap=${web_browser_cap:-$DEFAULT_WEB_BROWSER}
220
300
 
221
301
  read -p " • Export data (database, files, etc.)? [y/N]: " data_cap
222
302
  data_cap=${data_cap:-n}
223
303
 
304
+ if [ "$DEFAULT_AGENT_SESSION" = "y" ]; then
305
+ read -p " • Spawn sub-agents and tasks? [Y/n]: " agent_session_cap
306
+ else
307
+ read -p " • Spawn sub-agents and tasks? [y/N]: " agent_session_cap
308
+ fi
309
+ agent_session_cap=${agent_session_cap:-$DEFAULT_AGENT_SESSION}
310
+
311
+ if [ "$DEFAULT_MCP_TOOL" = "y" ]; then
312
+ read -p " • Use MCP tools (external integrations)? [Y/n]: " mcp_tool_cap
313
+ else
314
+ read -p " • Use MCP tools (external integrations)? [y/N]: " mcp_tool_cap
315
+ fi
316
+ mcp_tool_cap=${mcp_tool_cap:-$DEFAULT_MCP_TOOL}
317
+
224
318
  echo ""
225
319
 
226
320
  # Configure limits
@@ -351,6 +445,12 @@ fi
351
445
  if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
352
446
  capabilities_json="$capabilities_json{\"id\": \"data.export\"},"
353
447
  fi
448
+ if [ "${agent_session_cap:-n}" = "y" ] || [ "${agent_session_cap:-n}" = "Y" ]; then
449
+ capabilities_json="$capabilities_json{\"id\": \"agent.session.create\"},"
450
+ fi
451
+ if [ "${mcp_tool_cap:-n}" = "y" ] || [ "${mcp_tool_cap:-n}" = "Y" ]; then
452
+ capabilities_json="$capabilities_json{\"id\": \"mcp.tool.execute\"},"
453
+ fi
354
454
  # Remove trailing comma
355
455
  capabilities_json="${capabilities_json%,}]"
356
456
 
@@ -438,6 +538,14 @@ if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
438
538
  limits_json="$limits_json\"data.export\": {\"max_rows\": $max_export_rows, \"allow_pii\": $allow_pii_bool, \"allowed_collections\": [\"*\"]},"
439
539
  fi
440
540
 
541
+ if [ "${agent_session_cap:-n}" = "y" ] || [ "${agent_session_cap:-n}" = "Y" ]; then
542
+ limits_json="$limits_json\"agent.session.create\": {\"max_concurrent\": 10},"
543
+ fi
544
+
545
+ if [ "${mcp_tool_cap:-n}" = "y" ] || [ "${mcp_tool_cap:-n}" = "Y" ]; then
546
+ limits_json="$limits_json\"mcp.tool.execute\": {\"allowed_servers\": [\"*\"]},"
547
+ fi
548
+
441
549
  # Remove trailing comma
442
550
  limits_json="${limits_json%,}}"
443
551
 
@@ -540,7 +648,13 @@ echo " 🔐 Capabilities:"
540
648
  [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ] && echo " • Create and merge pull requests"
541
649
  [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ] && echo " • Execute system commands"
542
650
  [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ] && echo " • Send messages"
651
+ [ "$file_read_cap" = "y" ] || [ "$file_read_cap" = "Y" ] && echo " • Read files"
652
+ [ "$file_write_cap" = "y" ] || [ "$file_write_cap" = "Y" ] && echo " • Write/edit files"
653
+ [ "$web_fetch_cap" = "y" ] || [ "$web_fetch_cap" = "Y" ] && echo " • Fetch from web"
654
+ [ "$web_browser_cap" = "y" ] || [ "$web_browser_cap" = "Y" ] && echo " • Automate browser"
543
655
  [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ] && echo " • Export data"
656
+ [ "${agent_session_cap:-n}" = "y" ] || [ "${agent_session_cap:-n}" = "Y" ] && echo " • Spawn sub-agents and tasks"
657
+ [ "${mcp_tool_cap:-n}" = "y" ] || [ "${mcp_tool_cap:-n}" = "Y" ] && echo " • Use MCP tools"
544
658
  echo ""
545
659
  echo " 📝 Next steps:"
546
660
  echo " • Review limits: vim $PASSPORT_FILE"
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env bash
2
- # APort Cursor/Copilot/Claude Code hook: read JSON from stdin, call guardrail, return allow/deny; exit 2 = block.
3
- # Compatible with Cursor (beforeShellExecution, preToolUse), VS Code Copilot, and Claude Code.
4
- # Input: JSON with "command" and/or "tool"/"name"/"input" (host-dependent). We map to system.command.execute.
5
- # Output: JSON with "permission": "allow"|"deny" (Cursor) and "allowed": true|false; optional "agentMessage"/"reason".
6
- # Exit: 0 = allow, 2 = block (deny). Other exits = hook error (host may proceed or fail-open).
2
+ # APort Cursor hook: reads JSON from stdin, maps tool to APort policy, calls guardrail.
3
+ # Handles all Cursor hook events: beforeShellExecution, preToolUse, beforeMCPExecution,
4
+ # beforeReadFile, subagentStart.
5
+ # Output: JSON with "permission": "allow"|"deny"; optional "agentMessage"/"user_message".
6
+ # Exit: 0 = allow, 2 = block (deny). Other exits = hook error (Cursor may fail-open).
7
+ #
8
+ # Cursor preToolUse tool_name values: Shell, Read, Write, Grep, Delete, Task, MCP:<name>
9
+ # See: https://cursor.com/docs/hooks
7
10
 
8
11
  set -e
9
12
 
@@ -11,14 +14,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
14
  ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
15
  GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
13
16
 
14
- # Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/langchain, etc. when OPENCLAW_CONFIG_DIR not set
17
+ # Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/*, etc.
15
18
  # shellcheck source=bin/aport-resolve-paths.sh
16
19
  . "$ROOT_DIR/bin/aport-resolve-paths.sh"
17
20
 
18
21
  # Read stdin (single JSON object; Cursor sends one payload per invocation)
19
22
  INPUT=""
20
23
  if [ -t 0 ]; then
21
- # No stdin (e.g. manual test): treat as allow to avoid blocking
22
24
  INPUT='{}'
23
25
  else
24
26
  INPUT="$(cat)"
@@ -30,61 +32,145 @@ if [ -z "$INPUT" ]; then
30
32
  exit 0
31
33
  fi
32
34
 
33
- # Parse and normalize to tool + context for guardrail
34
- # Cursor beforeShellExecution: { "command": "..." }
35
- # preToolUse / Copilot: { "tool": "runTerminalCommand", "input": { "command": "..." } } or similar
36
- TOOL_NAME="exec.run"
35
+ # Require jq for JSON parsing
36
+ if ! command -v jq &> /dev/null; then
37
+ echo '{"permission":"deny","allowed":false,"agentMessage":"APort: jq is required"}'
38
+ exit 2
39
+ fi
40
+
41
+ # Deny helper: outputs Cursor-format JSON and exits 2
42
+ deny() {
43
+ local reason="$1"
44
+ jq -n -c --arg reason "$reason" \
45
+ '{permission:"deny",allowed:false,agentMessage:$reason,reason:$reason}'
46
+ exit 2
47
+ }
48
+
49
+ # Safe jq extraction: returns '{}' on any jq error
50
+ safe_jq() {
51
+ local input="$1" filter="$2"
52
+ local result
53
+ result="$(echo "$input" | jq -c "$filter" 2> /dev/null)" || result='{}'
54
+ [ -z "$result" ] && result='{}'
55
+ echo "$result"
56
+ }
57
+
58
+ # Detect hook event type from input fields and route accordingly.
59
+ # Cursor sends different JSON shapes per hook event:
60
+ # beforeShellExecution: { "command": "...", "cwd": "..." }
61
+ # preToolUse: { "tool_name": "Shell|Read|Write|...", "tool_input": {...} }
62
+ # beforeMCPExecution: { "tool_name": "...", "tool_input": {...}, "server": "..." }
63
+ # beforeReadFile: { "file_path": "...", "content": "..." }
64
+ # subagentStart: { "subagent_id": "...", "subagent_type": "...", "task": "..." }
65
+ # We detect by checking for distinguishing fields.
66
+
67
+ GUARDRAIL_TOOL=""
37
68
  CONTEXT_JSON="{}"
38
- if command -v jq &> /dev/null; then
39
- CMD=$(echo "$INPUT" | jq -r '.command // .input.command // .input.cmd // .args[0] // ""')
40
- if [ -n "$CMD" ] && [ "$CMD" != "null" ]; then
41
- CONTEXT_JSON=$(echo "$INPUT" | jq -c '{command: (.command // .input.command // .input.cmd), args: (.args // .input.args // [])}' 2> /dev/null || echo "{}")
42
- if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
43
- CONTEXT_JSON=$(jq -n -c --arg cmd "$CMD" '{command: $cmd}')
44
- fi
45
- fi
46
- # If no command found, still pass through; guardrail may deny unknown
47
- if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
48
- CONTEXT_JSON="$INPUT"
49
- fi
69
+
70
+ # Check for hook_event_name first (newer Cursor versions include it)
71
+ HOOK_EVENT="$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2> /dev/null)"
72
+ TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // ""' 2> /dev/null)"
73
+
74
+ if [ "$HOOK_EVENT" = "beforeReadFile" ] || { [ -z "$HOOK_EVENT" ] && [ -z "$TOOL_NAME" ] && echo "$INPUT" | jq -e '.file_path and .content' &> /dev/null; }; then
75
+ # beforeReadFile: file reads — allow without evaluator (same as Claude Code)
76
+ exit 0
77
+
78
+ elif [ "$HOOK_EVENT" = "subagentStart" ] || { [ -z "$HOOK_EVENT" ] && echo "$INPUT" | jq -e '.subagent_id' &> /dev/null; }; then
79
+ # subagentStart: sub-agent spawning
80
+ GUARDRAIL_TOOL="session.create"
81
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{description: (.task // ""), subagent_type: (.subagent_type // "")}')"
82
+
83
+ elif [ "$HOOK_EVENT" = "beforeMCPExecution" ] || { [ -n "$TOOL_NAME" ] && echo "$INPUT" | jq -e '.server // .url' &> /dev/null; }; then
84
+ # beforeMCPExecution: MCP tool calls (has server/url field)
85
+ GUARDRAIL_TOOL="mcp.tool"
86
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{tool_name: (.tool_name // ""), tool_input: (.tool_input // {})}')"
87
+
88
+ elif [ -n "$TOOL_NAME" ]; then
89
+ # preToolUse: Cursor tool names — Shell, Read, Write, Grep, Delete, Task, MCP:<name>
90
+ case "$TOOL_NAME" in
91
+ Shell)
92
+ GUARDRAIL_TOOL="bash"
93
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{command: (.tool_input.command // "")}')"
94
+ ;;
95
+ Read | Grep)
96
+ # Read-family: allow without calling evaluator
97
+ exit 0
98
+ ;;
99
+ Write)
100
+ GUARDRAIL_TOOL="write"
101
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{file_path: (.tool_input.file_path // .tool_input.path // "")}')"
102
+ ;;
103
+ Delete)
104
+ GUARDRAIL_TOOL="write"
105
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{file_path: (.tool_input.file_path // .tool_input.path // "")}')"
106
+ ;;
107
+ Task)
108
+ GUARDRAIL_TOOL="session.create"
109
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{description: (.tool_input.description // .tool_input.prompt // "")}')"
110
+ ;;
111
+ MCP:*)
112
+ GUARDRAIL_TOOL="mcp.tool"
113
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{tool_name: (.tool_name // ""), tool_input: (.tool_input // {})}')"
114
+ ;;
115
+ *)
116
+ # Unknown preToolUse tool: fail-closed
117
+ deny "🛡️ APort: unknown tool '$TOOL_NAME' — fail-closed policy"
118
+ ;;
119
+ esac
120
+
121
+ elif echo "$INPUT" | jq -e '.command' &> /dev/null; then
122
+ # beforeShellExecution: { "command": "..." }
123
+ GUARDRAIL_TOOL="bash"
124
+ CMD="$(echo "$INPUT" | jq -r '.command // ""' 2> /dev/null)"
125
+ CONTEXT_JSON="$(jq -n -c --arg cmd "$CMD" '{command: $cmd}')"
126
+
127
+ elif echo "$INPUT" | jq -e '.tool // .input.command' &> /dev/null; then
128
+ # Legacy Copilot-style: { "tool": "runTerminalCommand", "input": { "command": "..." } }
129
+ GUARDRAIL_TOOL="bash"
130
+ CMD="$(echo "$INPUT" | jq -r '.input.command // .input.cmd // .args[0] // ""' 2> /dev/null)"
131
+ CONTEXT_JSON="$(jq -n -c --arg cmd "$CMD" '{command: $cmd}')"
132
+
50
133
  else
51
- CONTEXT_JSON="$INPUT"
134
+ # Unrecognized input shape: fail-closed
135
+ deny "🛡️ APort: unrecognized hook input — fail-closed policy"
136
+ fi
137
+
138
+ # Use a per-invocation decision file to avoid race conditions with concurrent tool calls
139
+ HOOK_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}"
140
+ if [ -n "$HOOK_DECISION_FILE" ]; then
141
+ HOOK_DECISION_FILE="${HOOK_DECISION_FILE%.json}-$$.json"
142
+ export OPENCLAW_DECISION_FILE="$HOOK_DECISION_FILE"
52
143
  fi
53
144
 
54
- # Call existing bash guardrail: exit 0 = allow, exit 1 = deny (forward config for subprocess)
145
+ # Call core evaluator
55
146
  set +e
56
- OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" OPENCLAW_PASSPORT_FILE="${OPENCLAW_PASSPORT_FILE:-}" OPENCLAW_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}" "$GUARDRAIL" "$TOOL_NAME" "$CONTEXT_JSON" 2> /dev/null
147
+ "$GUARDRAIL" "$GUARDRAIL_TOOL" "$CONTEXT_JSON" 2> /dev/null
57
148
  GUARDRAIL_EXIT=$?
58
149
  set -e
59
150
 
151
+ # Clean up per-invocation decision file
152
+ cleanup_decision() { [ -n "$HOOK_DECISION_FILE" ] && rm -f "$HOOK_DECISION_FILE" 2> /dev/null; }
153
+
60
154
  if [ "$GUARDRAIL_EXIT" -eq 0 ]; then
155
+ cleanup_decision
61
156
  echo '{"permission":"allow","allowed":true}'
62
157
  exit 0
63
158
  fi
64
159
 
65
- # Deny: output reason from decision file if available (guardrail writes decision before exit 1)
160
+ # Deny: read reason from decision file
66
161
  REASON="Policy denied this action."
67
- if [ -n "${OPENCLAW_DECISION_FILE:-}" ] && [ -f "$OPENCLAW_DECISION_FILE" ] && command -v jq &> /dev/null; then
68
- R=$(jq -r '.reasons[0].message // empty' "$OPENCLAW_DECISION_FILE" 2> /dev/null)
69
- if [ -n "$R" ]; then
70
- REASON="$R"
71
- fi
162
+ if [ -n "$HOOK_DECISION_FILE" ] && [ -f "$HOOK_DECISION_FILE" ]; then
163
+ R="$(jq -r '.reasons[0].message // empty' "$HOOK_DECISION_FILE" 2> /dev/null)"
164
+ [ -n "$R" ] && REASON="$R"
72
165
  fi
73
- # If no decision file was set, try common config dirs so we can show actual deny reason
74
- if [ "$REASON" = "Policy denied this action." ] && command -v jq &> /dev/null; then
166
+ # Fallback: try common config dirs
167
+ if [ "$REASON" = "Policy denied this action." ]; then
75
168
  for DEC in "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}/aport/decision.json" "$HOME/.cursor/aport/decision.json" "$HOME/.openclaw/aport/decision.json"; do
76
169
  if [ -f "$DEC" ]; then
77
- R=$(jq -r '.reasons[0].message // empty' "$DEC" 2> /dev/null)
78
- if [ -n "$R" ]; then
79
- REASON="$R"
80
- break
81
- fi
170
+ R="$(jq -r '.reasons[0].message // empty' "$DEC" 2> /dev/null)"
171
+ [ -n "$R" ] && REASON="$R" && break
82
172
  fi
83
173
  done
84
174
  fi
85
- # Fallback: help user debug guardrail/script errors
86
- if [ "$REASON" = "Policy denied this action." ]; then
87
- REASON="Policy denied or guardrail error. Check passport and guardrail script (see docs/frameworks/cursor.md)."
88
- fi
89
- echo "{\"permission\":\"deny\",\"allowed\":false,\"agentMessage\":$(echo "$REASON" | jq -Rs .),\"reason\":$(echo "$REASON" | jq -Rs .)}"
90
- exit 2
175
+ cleanup_decision
176
+ deny "🛡️ APort: $REASON"
@@ -45,12 +45,13 @@ run_setup() {
45
45
  if [ -f "$CURSOR_HOOKS_FILE" ] && command -v jq &> /dev/null; then
46
46
  EXISTING=$(cat "$CURSOR_HOOKS_FILE")
47
47
  if echo "$EXISTING" | jq -e '.hooks' &> /dev/null; then
48
- # Add APort hook to beforeShellExecution and preToolUse (avoid duplicate)
48
+ # Add APort hook to all supported lifecycle events (avoid duplicate)
49
49
  NEW_HOOKS=$(echo "$EXISTING" | jq -c --arg cmd "$HOOK_SCRIPT" '
50
- (.hooks.beforeShellExecution // []) as $b |
51
- (.hooks.preToolUse // []) as $p |
52
- .hooks.beforeShellExecution = ($b | map(select(.command != $cmd)) | . + [{ "command": $cmd }]) |
53
- .hooks.preToolUse = ($p | map(select(.command != $cmd)) | . + [{ "command": $cmd }])
50
+ def upsert_hook: map(select(.command != $cmd)) | . + [{ "command": $cmd }];
51
+ .hooks.beforeShellExecution = ((.hooks.beforeShellExecution // []) | upsert_hook) |
52
+ .hooks.preToolUse = ((.hooks.preToolUse // []) | upsert_hook) |
53
+ .hooks.beforeMCPExecution = ((.hooks.beforeMCPExecution // []) | upsert_hook) |
54
+ .hooks.subagentStart = ((.hooks.subagentStart // []) | upsert_hook)
54
55
  ')
55
56
  [ -f "$CURSOR_HOOKS_FILE" ] && cp "$CURSOR_HOOKS_FILE" "${CURSOR_HOOKS_FILE}.bak"
56
57
  echo "$NEW_HOOKS" > "$CURSOR_HOOKS_FILE"
@@ -82,7 +83,9 @@ _write_cursor_hooks_file() {
82
83
  version: 1,
83
84
  hooks: {
84
85
  beforeShellExecution: [{ command: $cmd }],
85
- preToolUse: [{ command: $cmd }]
86
+ preToolUse: [{ command: $cmd }],
87
+ beforeMCPExecution: [{ command: $cmd }],
88
+ subagentStart: [{ command: $cmd }]
86
89
  }
87
90
  }' > "$file"
88
91
  else
@@ -93,7 +96,9 @@ _write_cursor_hooks_file() {
93
96
  "version": 1,
94
97
  "hooks": {
95
98
  "beforeShellExecution": [{"command": "${escaped_cmd}"}],
96
- "preToolUse": [{"command": "${escaped_cmd}"}]
99
+ "preToolUse": [{"command": "${escaped_cmd}"}],
100
+ "beforeMCPExecution": [{"command": "${escaped_cmd}"}],
101
+ "subagentStart": [{"command": "${escaped_cmd}"}]
97
102
  }
98
103
  }
99
104
  EOF
package/docs/RELEASE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Release process and version policy
2
2
 
3
- **Current release:** 1.0.13 (see [CHANGELOG.md](../CHANGELOG.md)).
3
+ **Current release:** 1.0.14 (see [CHANGELOG.md](../CHANGELOG.md)).
4
4
 
5
5
  We keep **one version number** across all published packages (Node core, Python core, and every framework adapter). That avoids “core is 1.2 but CLI is 0.9” and keeps the story simple for users and support.
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aporthq/aport-agent-guardrails",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Policy enforcement guardrails for OpenClaw-compatible agent frameworks",
5
5
  "workspaces": [
6
6
  "packages/*",
@@ -1,91 +1,84 @@
1
1
  ---
2
2
  name: aport-agent-guardrail
3
- description: Pre-action authorization for AI agents. Enforces policies before tools execute—blocks unauthorized commands, data exfiltration, and malicious actions. Works with OpenClaw, IronClaw, PicoClaw via before_tool_call hook. Deterministic enforcement the agent cannot bypass. Optional API mode (APORT_API_URL, APORT_AGENT_ID, APORT_API_KEY) for hosted passports and signed decisions.
4
- homepage: https://aport.io
5
- metadata: {"openclaw":{"requires":{"bins":["jq"]},"envOptional":["APORT_API_URL","APORT_AGENT_ID","APORT_API_KEY"]}}
3
+ description: >
4
+ Pre-action authorization for AI agents. Installs an OpenClaw before_tool_call hook that
5
+ evaluates every tool call against a passport and policy before execution. Blocks unauthorized
6
+ commands, data exfiltration, and policy violations. Supports local (offline) and hosted
7
+ (API) passport modes. Requires Node.js 18+ and npx.
8
+ metadata:
9
+ author: uchibeke
10
+ version: 1.1.11
11
+ tags: security, guardrails, authorization, ai-agent, openclaw, aport, policy-enforcement
6
12
  ---
7
13
 
8
14
  # APort Agent Guardrail
9
15
 
10
- **Skill identifier:** `aport-agent-guardrail` · **Category:** Security / Infrastructure
16
+ Pre-action authorization for AI agents. Installs an OpenClaw `before_tool_call` hook that
17
+ evaluates every tool call against a passport (identity + capabilities + limits) and policy
18
+ **before** it executes. If the policy denies the call, the tool does not run.
11
19
 
12
- ---
13
-
14
- ## 🛡️ What This Skill Does
15
-
16
- **Pre-action authorization for AI agents.** Every tool call is evaluated against a passport (identity + capabilities + limits) and policy **before** it executes. If denied, the tool never runs.
20
+ This skill provides setup instructions. The enforcement logic comes from the
21
+ [@aporthq/aport-agent-guardrails](https://github.com/aporthq/aport-agent-guardrails)
22
+ npm package, which is open-source (Apache 2.0) and can be audited before installation.
17
23
 
18
- **Key features:**
19
- - ✅ **Deterministic enforcement** – Runs in `before_tool_call` hook; agent cannot bypass
20
- - ✅ **Blocks malicious actions** – Unauthorized commands, data exfiltration, API abuse prevented
21
- - ✅ **Structured policies** – Based on [Open Agent Passport (OAP) v1.0](https://github.com/aporthq/aport-spec/tree/main)
22
- - ✅ **Fail-closed by default** – Errors deny tool execution (security over availability)
23
- - ✅ **Audit trail** – Every decision logged with tamper-evident hashes
24
- - ✅ **Framework-agnostic** – OpenClaw, IronClaw, PicoClaw, and compatible runtimes
24
+ ## When to use this skill
25
25
 
26
- **How it protects you:**
27
- - Prompt injection Agent cannot bypass hook-based enforcement
28
- - Malicious skills All tool calls checked regardless of source
29
- - Unauthorized commands Allowlist + 40+ blocked patterns (rm -rf, sudo, etc.)
30
- - Data exfiltration → File access, messaging, web requests controlled
31
- - Resource exhaustion → Rate limits, size caps enforced
32
-
33
- **Install once, protected forever.** The plugin runs automatically on every tool call.
34
-
35
- ---
26
+ - User wants to add guardrails to their AI agent setup
27
+ - User asks about protecting against unauthorized tool calls
28
+ - User wants pre-action authorization for OpenClaw, IronClaw, or PicoClaw agents
29
+ - User needs audit trails for AI agent actions
36
30
 
37
- ## Quick Start
31
+ ## How it works
38
32
 
39
- ```bash
40
- # Install APort guardrails (one-time setup)
41
- npx @aporthq/aport-agent-guardrails
33
+ ```
34
+ User Request -> Agent Decision -> APort Hook -> [ALLOW/DENY] -> Tool Execution
35
+ |
36
+ Policy + Passport
37
+ ```
42
38
 
43
- # Follow wizard to create passport and configure policies
44
- # Plugin auto-registers with OpenClaw
39
+ 1. Agent decides to use a tool (e.g., run a shell command)
40
+ 2. OpenClaw fires the `before_tool_call` hook
41
+ 3. APort loads the passport, maps the tool to a policy, checks allowlists and limits
42
+ 4. Decision: ALLOW (tool runs) or DENY (tool blocked)
43
+ 5. Decision is logged to the audit trail
45
44
 
46
- # Now your agent is protected
47
- # All tool calls checked before execution
48
- ```
45
+ Enforcement runs in the OpenClaw hook layer, not in agent prompts. However, like any
46
+ application-layer security control, it depends on the integrity of the runtime environment
47
+ (OS, OpenClaw, filesystem). See the [Security Model](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/SECURITY_MODEL.md) for trust boundaries.
49
48
 
50
- **With hosted passport (optional):**
51
- ```bash
52
- # Get agent_id from aport.io
53
- npx @aporthq/aport-agent-guardrails <agent_id>
54
- ```
49
+ ## Prerequisites
55
50
 
56
- **Requirements:** Node 18+, jq
51
+ Check these before starting:
57
52
 
58
- ---
53
+ 1. **Node.js 18+** and **npx** — run `node -v` to verify (must show v18 or higher)
54
+ 2. **OpenClaw** (or compatible runtime) — the hook registers as an OpenClaw plugin
59
55
 
60
- ## 📦 Installation
56
+ ## Installation
61
57
 
62
- ### Option 1: npm (recommended)
58
+ ### Quick start (recommended)
63
59
 
64
60
  ```bash
65
61
  npx @aporthq/aport-agent-guardrails
66
62
  ```
67
63
 
68
- **The wizard will:**
69
- 1. Create or load passport (local file or hosted from aport.io)
64
+ The wizard will:
65
+ 1. Create or load a passport (local file or hosted from aport.io)
70
66
  2. Configure capabilities and limits
71
- 3. Install OpenClaw plugin automatically
72
- 4. Set up wrapper scripts
67
+ 3. Register the OpenClaw plugin (adds `before_tool_call` hook)
68
+ 4. Set up wrapper scripts under `~/.openclaw/`
73
69
 
74
- **After install:** Plugin enforces before every tool call. No further action needed.
70
+ After install, the hook runs on every tool call automatically.
75
71
 
76
- ### Option 2: With hosted passport
72
+ ### With hosted passport (optional)
77
73
 
78
74
  ```bash
79
75
  npx @aporthq/aport-agent-guardrails <agent_id>
80
76
  ```
81
77
 
82
- Get `agent_id` at [aport.io](https://aport.io/builder/create/) for:
83
- - Cryptographically signed decisions
84
- - Global suspend (<200ms across all systems)
85
- - Centralized audit and compliance dashboards
86
- - Team collaboration
78
+ Get `agent_id` at [aport.io](https://aport.io/builder/create/) for signed decisions,
79
+ global suspend, and centralized audit dashboards.
87
80
 
88
- ### Option 3: From source
81
+ ### From source
89
82
 
90
83
  ```bash
91
84
  git clone https://github.com/aporthq/aport-agent-guardrails
@@ -93,319 +86,103 @@ cd aport-agent-guardrails
93
86
  ./bin/openclaw
94
87
  ```
95
88
 
96
- **Guides:**
97
- - [QuickStart: OpenClaw Plugin](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/QUICKSTART_OPENCLAW_PLUGIN.md)
98
- - [Hosted passport setup](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/HOSTED_PASSPORT_SETUP.md)
89
+ ### What gets installed
99
90
 
100
- ---
91
+ Files created under `~/.openclaw/`:
92
+ - Plugin config in `config.yaml` or `openclaw.json`
93
+ - Wrapper scripts in `.skills/aport-guardrail*.sh`
94
+ - `aport/passport.json` (local mode only)
95
+ - `aport/decision.json` and `aport/audit.log` (created at runtime)
101
96
 
102
- ## 🚀 Usage
97
+ Total disk usage: ~100KB for scripts + passport/decision files.
103
98
 
104
- ### Automatic enforcement (default)
99
+ ## Usage
105
100
 
106
- **After installation, the plugin runs automatically:**
101
+ After installation, the hook runs automatically on every tool call:
107
102
 
108
103
  ```bash
109
- # Your agent uses tools normally
104
+ # Allowed command hook approves, tool executes
110
105
  agent> run git status
111
- # APort: passport checked policy evaluated ALLOW → tool executes
106
+ # APort: passport checked -> policy evaluated -> ALLOW
112
107
 
108
+ # Blocked command — hook denies, tool does not run
113
109
  agent> run rm -rf /
114
- # APort: passport checked blocked pattern detected DENY → tool blocked
110
+ # APort: passport checked -> blocked pattern detected -> DENY
115
111
  ```
116
112
 
117
- **You do nothing.** The plugin enforces on every tool call in the background.
118
-
119
- ### Testing the guardrail (optional)
120
-
121
- **Direct script calls for testing or automation:**
113
+ ### Testing the hook manually
122
114
 
123
115
  ```bash
124
- # Test allowed command
116
+ # Test allowed command (exit 0 = ALLOW)
125
117
  ~/.openclaw/.skills/aport-guardrail.sh system.command.execute '{"command":"ls"}'
126
- # Exit 0 = ALLOW
127
118
 
128
- # Test blocked command
119
+ # Test blocked command (exit 1 = DENY)
129
120
  ~/.openclaw/.skills/aport-guardrail.sh system.command.execute '{"command":"rm -rf /"}'
130
- # Exit 1 = DENY
131
-
132
- # Test messaging
133
- ~/.openclaw/.skills/aport-guardrail.sh messaging.message.send '{"channel":"whatsapp","to":"+15551234567"}'
134
121
  ```
135
122
 
136
- **Exit codes:**
137
- - `0` = ALLOW (tool may proceed)
138
- - `1` = DENY (reason in decision.json)
139
-
140
- **Decision logs:**
141
- - Latest: `~/.openclaw/aport/decision.json`
123
+ Decision logs:
124
+ - Latest decision: `~/.openclaw/aport/decision.json`
142
125
  - Audit trail: `~/.openclaw/aport/audit.log`
143
- - API mode: Signed receipts via APort API
144
-
145
- ---
146
-
147
- ## 🔍 How It Works
148
126
 
149
- ### Pre-Action Authorization Flow
127
+ ## Modes
150
128
 
151
- ```
152
- User Request → Agent Decision → APort Check → [ALLOW/DENY] → Tool Execution
153
-
154
- Policy + Passport
155
- ```
156
-
157
- 1. **User makes request** (e.g., "Deploy to production")
158
- 2. **Agent decides to use tool** (e.g., exec.run with git push)
159
- 3. **OpenClaw fires hook** (`before_tool_call`)
160
- 4. **APort evaluates:**
161
- - Load passport (identity, capabilities, limits)
162
- - Map tool → policy (exec → system.command.execute.v1)
163
- - Check allowlist, blocked patterns, rate limits
164
- 5. **Decision:** ALLOW or DENY
165
- 6. **Audit:** Log decision with timestamp, policy, reason codes
166
-
167
- **Agent cannot bypass.** Hook is registered by OpenClaw, not controlled by prompts.
168
-
169
- ### What Gets Installed
170
-
171
- **Plugin registration:**
172
- - OpenClaw plugin added to config (enforces before_tool_call)
173
- - TypeScript/JavaScript plugin loaded on OpenClaw start
174
-
175
- **Files created (under ~/.openclaw/):**
176
- - `config.yaml` or `openclaw.json` – Plugin configuration
177
- - `.skills/aport-guardrail*.sh` – Wrapper scripts for local/API evaluation
178
- - `aport/passport.json` – Your agent passport (local mode only)
179
- - `aport/decision.json` – Latest decision (runtime)
180
- - `aport/audit.log` – Audit trail (runtime)
181
-
182
- **Total disk usage:** ~100KB scripts + your passport/decisions
183
-
184
- **Review code:** [GitHub repository](https://github.com/aporthq/aport-agent-guardrails)
185
-
186
- ---
187
-
188
- ## 🌐 Network and Privacy
129
+ ### Local mode (default)
189
130
 
190
- ### Local Mode (Default)
131
+ - All evaluation happens on your machine, zero network calls
132
+ - Passport stored locally at `~/.openclaw/aport/passport.json`
133
+ - Works offline
134
+ - Note: local passport file must be protected from tampering (standard filesystem permissions)
191
135
 
192
- **Zero network calls:**
193
- - ✅ All evaluation on your machine
194
- - ✅ Passport stored locally
195
- - ✅ Decisions stay local
196
- - ✅ Full privacy
197
- - ✅ Works offline
136
+ ### API mode (optional)
198
137
 
199
- **Perfect for:** Development, personal use, air-gapped environments
138
+ - Passport hosted in the aport.io registry (not stored locally)
139
+ - Signed decisions (Ed25519) for tamper-evident audit trails
140
+ - Global suspend across all systems
141
+ - Centralized compliance dashboards
142
+ - Sends tool name + context to API (does not send file contents, env vars, or credentials)
200
143
 
201
- ### API Mode (Optional)
144
+ ## Environment variables
202
145
 
203
- **Network usage:**
204
- - Tool name + context → APort API for policy evaluation
205
- - Hosted passport fetched from registry (if using agent_id)
206
- - Signed decisions returned (Ed25519 cryptographic signatures)
146
+ All optional. Local mode requires no environment variables.
207
147
 
208
- **Benefits:**
209
- - ✅ Cryptographically signed decisions
210
- - ✅ Court-admissible audit trail
211
- - ✅ Global suspend across all systems
212
- - ✅ Centralized compliance dashboards
213
- - ✅ No local passport tampering possible
214
-
215
- **API endpoint:** `https://api.aport.io` (or custom via APORT_API_URL)
216
-
217
- **Data sent:**
218
- - Tool name (e.g., "system.command.execute")
219
- - Context (e.g., {"command": "ls"})
220
- - Passport (if local) or agent_id (if hosted)
221
-
222
- **Data NOT sent:**
223
- - File contents
224
- - Environment variables
225
- - API keys or credentials
226
- - Unrelated system information
227
-
228
- **To verify:** Use local mode (no network) or inspect open-source code.
229
-
230
- ---
231
-
232
- ## ⚙️ Environment Variables
233
-
234
- | Variable | When Used | Purpose |
148
+ | Variable | When used | Purpose |
235
149
  |----------|-----------|---------|
236
- | `APORT_API_URL` | API mode | Override endpoint (default: `https://api.aport.io`). Use for self-hosted or custom API. |
237
- | `APORT_AGENT_ID` | Hosted passport | Passport ID from aport.io. API fetches passport from registry. |
238
- | `APORT_API_KEY` | If API requires auth | Authentication token. Set in environment (not config files). |
239
-
240
- **Local mode:** No environment variables needed. Passport read from `~/.openclaw/aport/passport.json`.
150
+ | `APORT_API_URL` | API mode | Override endpoint (default: `https://api.aport.io`) |
151
+ | `APORT_AGENT_ID` | Hosted passport | Passport ID from aport.io |
152
+ | `APORT_API_KEY` | If API requires auth | Authentication token |
241
153
 
242
- **Hosted mode:** Pass `agent_id` to installer or set APORT_AGENT_ID.
154
+ ## Default protections
243
155
 
244
- ---
156
+ - **Shell commands** — Allowlist enforcement, 40+ blocked patterns (`rm -rf`, `sudo`, `chmod 777`, etc.), interpreter bypass detection
157
+ - **Messaging** — Rate limits, recipient allowlist, channel restrictions
158
+ - **File access** — Path restrictions, blocks access to `.env`, SSH keys, system directories
159
+ - **Web requests** — Domain allowlist, SSRF protection, rate limiting
160
+ - **Git operations** — PR size limits, branch restrictions
245
161
 
246
- ## 🔧 Tool Name Mapping
162
+ ## Tool name mapping
247
163
 
248
- | When agent calls… | Tool name | Policy |
249
- |------------------|-----------|--------|
164
+ | Agent action | Tool name | Policy checks |
165
+ |--------------|-----------|---------------|
250
166
  | Shell commands | `system.command.execute` | Allowlist, blocked patterns |
251
- | WhatsApp/Email/Slack | `messaging.message.send` | Rate limits, recipient allowlist |
252
- | Create/merge PRs | `git.create_pr`, `git.merge` | PR size, branch restrictions |
167
+ | Messaging (WhatsApp/Email/Slack) | `messaging.message.send` | Rate limits, recipient allowlist |
168
+ | PRs | `git.create_pr`, `git.merge` | PR size, branch restrictions |
253
169
  | MCP tools | `mcp.tool.execute` | Server/tool allowlist |
254
- | Data export | `data.export` | Row limits, PII filtering |
255
170
  | File read/write | `data.file.read`, `data.file.write` | Path restrictions |
256
- | Web requests | `web.fetch`, `web.browser` | Domain allowlist, SSRF protection |
257
-
258
- **Context format:** Valid JSON, e.g., `'{"command":"ls"}'` or `'{"channel":"whatsapp","to":"+1..."}'`
171
+ | Web requests | `web.fetch`, `web.browser` | Domain allowlist |
259
172
 
260
- ---
173
+ ## Troubleshooting
261
174
 
262
- ## 📋 Out-of-the-Box Protections
175
+ | Problem | Fix |
176
+ |---------|-----|
177
+ | Plugin not enforcing | Check `openclaw plugin list` shows aport-guardrail |
178
+ | Connection refused (API mode) | Verify `APORT_API_URL` is reachable |
179
+ | Tool blocked unexpectedly | Check `~/.openclaw/aport/decision.json` for deny reason |
180
+ | npx not found | Install Node.js 18+: https://nodejs.org |
263
181
 
264
- **Shell commands (system.command.execute.v1):**
265
- - Allowlist enforcement (only specified commands run)
266
- - 40+ blocked patterns: `rm -rf`, `sudo`, `chmod 777`, `dd if=`, `mkfs`, etc.
267
- - Interpreter bypasses blocked: `python -c`, `node -e`, `base64` encoding
268
- - Command injection patterns detected
182
+ ## Documentation
269
183
 
270
- **Messaging (messaging.message.send.v1):**
271
- - Rate limits (msgs_per_min, msgs_per_day)
272
- - Recipient allowlist
273
- - Channel restrictions
274
-
275
- **File access (data.file.read/write.v1):**
276
- - Path restrictions (block /etc, /bin, system directories)
277
- - Prevent .env, SSH key theft
278
-
279
- **Web requests (web.fetch/browser.v1):**
280
- - Domain allowlist
281
- - SSRF protection (block private IPs)
282
- - Rate limiting
283
-
284
- **Git operations (code.repository.merge.v1):**
285
- - PR size limits
286
- - Branch restrictions
287
- - Review requirements
288
-
289
- **All policies at:** https://aport.io/policy-packs
290
-
291
- ---
292
-
293
- ## 🔐 Security Model
294
-
295
- ### What APort Protects
296
-
297
- **✅ Agent action security:**
298
- - Prompt injection (hook-based enforcement, not prompt-based)
299
- - Malicious third-party skills
300
- - Unauthorized commands
301
- - Data exfiltration via files, messaging, web requests
302
- - Resource exhaustion (rate/size limits)
303
-
304
- ### Trust Model
305
-
306
- **APort operates at the application layer** (between agent decision and tool execution).
307
-
308
- **You must trust:**
309
- - Your operating system (file permissions, process isolation)
310
- - OpenClaw runtime (hooks execute correctly)
311
- - APort code (open-source, verifiable)
312
-
313
- **Local mode additionally trusts:**
314
- - Filesystem integrity (passport not tampered)
315
-
316
- **API mode eliminates:**
317
- - Local passport tampering (fetched from API)
318
- - Decision tampering (cryptographically signed)
319
-
320
- **Out of scope (OS/infrastructure security):**
321
- - File system compromise
322
- - OpenClaw CVEs
323
- - Network attacks (MITM, DNS poisoning)
324
- - Supply chain attacks
325
-
326
- **This is standard for application-layer authorization** (same model as OAuth, IAM, policy engines).
327
-
328
- ---
329
-
330
- ## 🎯 Use Cases
331
-
332
- **Protect against malicious skills:**
333
- - Install APort before adding community skills
334
- - Every skill's tool calls are checked
335
- - Malicious actions blocked before execution
336
-
337
- **Compliance and audit:**
338
- - Tamper-evident decision logs
339
- - Court-admissible audit trail (API mode with Ed25519 signatures)
340
- - SOC 2, HIPAA, SOX compliance support
341
-
342
- **Team deployments:**
343
- - Shared passport across systems (global suspend)
344
- - Centralized policy updates
345
- - Consistent enforcement
346
-
347
- **Air-gapped environments:**
348
- - Use local mode (zero network)
349
- - All evaluation on-premise
350
- - Self-hosted policy packs
351
-
352
- ---
353
-
354
- ## 📚 Documentation
355
-
356
- **APort Guardrails:**
184
+ - [Source code](https://github.com/aporthq/aport-agent-guardrails) (Apache 2.0)
357
185
  - [QuickStart: OpenClaw Plugin](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/QUICKSTART_OPENCLAW_PLUGIN.md)
358
186
  - [Security Model & Trust Boundaries](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/SECURITY_MODEL.md)
359
187
  - [Hosted Passport Setup](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/HOSTED_PASSPORT_SETUP.md)
360
- - [Tool/Policy Mapping](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/TOOL_POLICY_MAPPING.md)
361
- - [Verification Methods (Local vs API)](https://github.com/aporthq/aport-agent-guardrails/blob/main/docs/VERIFICATION_METHODS.md)
362
-
363
- **OpenClaw:**
364
- - [CLI: skills](https://docs.openclaw.ai/cli/skills)
365
- - [Skills Documentation](https://docs.openclaw.ai/tools/skills)
366
- - [Skills Config](https://docs.openclaw.ai/tools/skills-config)
367
- - [ClawHub](https://docs.openclaw.ai/tools/clawhub)
368
-
369
- **Security:**
370
- - [SECURITY.md](https://github.com/aporthq/aport-agent-guardrails/blob/main/SECURITY.md) - Prompt injection, Cisco findings
371
- - [OAP Specification](https://github.com/aporthq/aport-spec/tree/main) - Open Agent Passport standard
372
-
373
- ---
374
-
375
- ## 🤝 Support and Community
376
-
377
- **GitHub:** [aporthq/aport-agent-guardrails](https://github.com/aporthq/aport-agent-guardrails)
378
- **Website:** [aport.io](https://aport.io)
379
- **Issues:** [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues)
380
-
381
- **Open-source:** Apache 2.0 License
382
- **Code review:** All code publicly available for inspection
383
-
384
- ---
385
-
386
- ## ❓ FAQ
387
-
388
- **Q: Does this slow down my agent?**
389
- A: Minimal overhead. API mode: ~60-100ms. Local mode: <300ms. Runs in parallel with agent thinking.
390
-
391
- **Q: Can I use this offline?**
392
- A: Yes. Local mode works without network connectivity.
393
-
394
- **Q: What if I need custom policies?**
395
- A: API mode: Pass custom policy JSON in request. Local mode: Edit bash script or use API mode.
396
-
397
- **Q: How do I suspend my agent?**
398
- A: Local: Set passport status to "suspended". Hosted: Log in to aport.io and suspend (global effect).
399
-
400
- **Q: Is my data sent to APort?**
401
- A: Local mode: No. API mode: Tool name + context only (no credentials, file contents, or env vars).
402
-
403
- **Q: Can the agent bypass this?**
404
- A: No. Enforcement is in the platform hook (`before_tool_call`), not controllable by prompts.
405
-
406
- **Q: What happens if APort errors?**
407
- A: Default: Tool blocked (fail-closed). Configurable via `failClosed` setting.
408
-
409
- ---
410
-
411
- **Made with 🛡️ by [APort](https://aport.io) · Open-source on [GitHub](https://github.com/aporthq/aport-agent-guardrails) · Apache 2.0 License**
188
+ - [OAP Specification](https://github.com/aporthq/aport-spec/tree/main)