@aporthq/aport-agent-guardrails 1.0.8

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.
Files changed (237) hide show
  1. package/LICENSE +217 -0
  2. package/README.md +481 -0
  3. package/bin/agent-guardrails +133 -0
  4. package/bin/aport-create-passport.sh +444 -0
  5. package/bin/aport-cursor-hook.sh +90 -0
  6. package/bin/aport-guardrail-api.sh +108 -0
  7. package/bin/aport-guardrail-bash.sh +394 -0
  8. package/bin/aport-guardrail-v2.sh +5 -0
  9. package/bin/aport-guardrail.sh +5 -0
  10. package/bin/aport-resolve-paths.sh +71 -0
  11. package/bin/aport-status.sh +276 -0
  12. package/bin/frameworks/crewai.sh +49 -0
  13. package/bin/frameworks/cursor.sh +95 -0
  14. package/bin/frameworks/langchain.sh +48 -0
  15. package/bin/frameworks/n8n.sh +36 -0
  16. package/bin/frameworks/openclaw.sh +19 -0
  17. package/bin/lib/allowlist.sh +18 -0
  18. package/bin/lib/common.sh +28 -0
  19. package/bin/lib/config.sh +46 -0
  20. package/bin/lib/constants.sh +232 -0
  21. package/bin/lib/detect.sh +65 -0
  22. package/bin/lib/error.sh +269 -0
  23. package/bin/lib/passport.sh +19 -0
  24. package/bin/lib/templates/.gitkeep +1 -0
  25. package/bin/lib/templates/config.yaml +6 -0
  26. package/bin/lib/validation.sh +206 -0
  27. package/bin/openclaw +660 -0
  28. package/docs/ADDING_A_FRAMEWORK.md +87 -0
  29. package/docs/AGENTS.md.example +40 -0
  30. package/docs/CODE_REVIEW.md +192 -0
  31. package/docs/DEPLOYMENT_READINESS.md +81 -0
  32. package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
  33. package/docs/FRAMEWORK_ROADMAP.md +41 -0
  34. package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
  35. package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
  36. package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
  37. package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
  38. package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
  39. package/docs/QUICKSTART.md +470 -0
  40. package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
  41. package/docs/README.md +28 -0
  42. package/docs/RELEASE.md +87 -0
  43. package/docs/REPO_LAYOUT.md +47 -0
  44. package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
  45. package/docs/TOOL_POLICY_MAPPING.md +46 -0
  46. package/docs/UPGRADE.md +46 -0
  47. package/docs/VERIFICATION_METHODS.md +97 -0
  48. package/docs/assets/README.md +8 -0
  49. package/docs/assets/porter.svg +54 -0
  50. package/docs/development/ERROR_CODES.md +616 -0
  51. package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
  52. package/docs/frameworks/crewai.md +114 -0
  53. package/docs/frameworks/cursor.md +159 -0
  54. package/docs/frameworks/langchain.md +72 -0
  55. package/docs/frameworks/n8n.md +40 -0
  56. package/docs/frameworks/openclaw.md +40 -0
  57. package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
  58. package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
  59. package/docs/launch/AWESOME_REPOS.md +53 -0
  60. package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
  61. package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
  62. package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
  63. package/docs/launch/EVIDENCE_README.md +61 -0
  64. package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
  65. package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
  66. package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
  67. package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
  68. package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
  69. package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
  70. package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
  71. package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
  72. package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
  73. package/docs/launch/READINESS_SUMMARY.md +262 -0
  74. package/docs/launch/README.md +68 -0
  75. package/docs/launch/USER_STORIES.md +327 -0
  76. package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
  77. package/docs/operations/MONITORING.md +588 -0
  78. package/docs/reviews/2026-02-18-staff-review.md +268 -0
  79. package/extensions/openclaw-aport/README.md +415 -0
  80. package/extensions/openclaw-aport/index.js +625 -0
  81. package/extensions/openclaw-aport/openclaw-aport.js +7 -0
  82. package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
  83. package/extensions/openclaw-aport/package.json +36 -0
  84. package/extensions/openclaw-aport/test.js +307 -0
  85. package/external/aport-policies/README.md +363 -0
  86. package/external/aport-policies/agent.session.create.v1/README.md +345 -0
  87. package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
  88. package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
  89. package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
  90. package/external/aport-policies/code.release.publish.v1/README.md +51 -0
  91. package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
  92. package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
  93. package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
  94. package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
  95. package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
  96. package/external/aport-policies/data.export.create.v1/README.md +226 -0
  97. package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
  98. package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
  99. package/external/aport-policies/data.export.create.v1/policy.json +133 -0
  100. package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
  101. package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
  102. package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
  103. package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
  104. package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
  105. package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
  106. package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
  107. package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
  108. package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
  109. package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
  110. package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
  111. package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
  112. package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
  113. package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
  114. package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
  115. package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
  116. package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
  117. package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
  118. package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
  119. package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
  120. package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
  121. package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
  122. package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
  123. package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
  124. package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
  125. package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
  126. package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
  127. package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
  128. package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
  129. package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
  130. package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
  131. package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
  132. package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
  133. package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
  134. package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
  135. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
  136. package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
  137. package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
  138. package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
  139. package/external/aport-policies/governance.data.access.v1/README.md +292 -0
  140. package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
  141. package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
  142. package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
  143. package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
  144. package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
  145. package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
  146. package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
  147. package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
  148. package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
  149. package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
  150. package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
  151. package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
  152. package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
  153. package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
  154. package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
  155. package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
  156. package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
  157. package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
  158. package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
  159. package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
  160. package/external/aport-policies/policy-template.json +107 -0
  161. package/external/aport-policies/system.command.execute.v1/README.md +275 -0
  162. package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
  163. package/external/aport-spec/CONTRIBUTING.md +273 -0
  164. package/external/aport-spec/LICENSE +21 -0
  165. package/external/aport-spec/README.md +168 -0
  166. package/external/aport-spec/conformance/README.md +294 -0
  167. package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
  168. package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
  169. package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
  170. package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
  171. package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
  172. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
  173. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
  174. package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
  175. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
  176. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
  177. package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
  178. package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
  179. package/external/aport-spec/conformance/package.json +44 -0
  180. package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
  181. package/external/aport-spec/conformance/src/cases.ts +371 -0
  182. package/external/aport-spec/conformance/src/ed25519.ts +167 -0
  183. package/external/aport-spec/conformance/src/jcs.ts +85 -0
  184. package/external/aport-spec/conformance/src/runner.ts +533 -0
  185. package/external/aport-spec/conformance/src/validators.ts +185 -0
  186. package/external/aport-spec/conformance/test-runner.js +315 -0
  187. package/external/aport-spec/conformance/tsconfig.json +21 -0
  188. package/external/aport-spec/error-schema.json +192 -0
  189. package/external/aport-spec/index.json +12 -0
  190. package/external/aport-spec/integrations/clawmoat/README.md +12 -0
  191. package/external/aport-spec/integrations/shield/README.md +245 -0
  192. package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
  193. package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
  194. package/external/aport-spec/integrations/shield/test/README.md +58 -0
  195. package/external/aport-spec/integrations/shield/test/shield.md +40 -0
  196. package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
  197. package/external/aport-spec/metrics-schema.json +504 -0
  198. package/external/aport-spec/oap/CHANGELOG.md +54 -0
  199. package/external/aport-spec/oap/VERSION.md +40 -0
  200. package/external/aport-spec/oap/capability-registry.md +229 -0
  201. package/external/aport-spec/oap/conformance.md +257 -0
  202. package/external/aport-spec/oap/decision-schema.json +114 -0
  203. package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
  204. package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
  205. package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
  206. package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
  207. package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
  208. package/external/aport-spec/oap/oap-spec.md +426 -0
  209. package/external/aport-spec/oap/passport-schema.json +396 -0
  210. package/external/aport-spec/oap/security.md +213 -0
  211. package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
  212. package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
  213. package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
  214. package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
  215. package/external/aport-spec/oap/vc/tools/README.md +278 -0
  216. package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
  217. package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
  218. package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
  219. package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
  220. package/external/aport-spec/oap/vc/tools/package.json +38 -0
  221. package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
  222. package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
  223. package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
  224. package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
  225. package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
  226. package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
  227. package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
  228. package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
  229. package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
  230. package/external/aport-spec/passport-schema.json +586 -0
  231. package/external/aport-spec/rate-limiting.md +136 -0
  232. package/external/aport-spec/transport-profile.md +325 -0
  233. package/external/aport-spec/webhook-spec.md +314 -0
  234. package/package.json +70 -0
  235. package/skills/aport-agent-guardrail/SKILL.md +314 -0
  236. package/src/evaluator.js +252 -0
  237. package/src/server/index.js +72 -0
@@ -0,0 +1,596 @@
1
+ # OpenClaw Local Integration Guide
2
+
3
+ **Get OpenClaw secure with APort in under 5 minutes - No cloud required!**
4
+
5
+ This guide shows you how to integrate APort's policy enforcement with OpenClaw using **local passport and policy files**. Perfect for individual developers who want pre-action authorization without cloud dependencies.
6
+
7
+ ---
8
+
9
+ ## 🎯 What You'll Get
10
+
11
+ ✅ **Pre-action verification** - Commands and MCP tools checked before execution
12
+ ✅ **Graduated controls** - Set limits (max commands, blocked patterns, etc.)
13
+ ✅ **Security patterns** - Built-in protection against injection, path traversal, etc.
14
+ ✅ **Audit logging** - All decisions logged locally
15
+ ✅ **Kill switch** - Emergency stop via local file
16
+
17
+ **No cloud API needed** - Everything works offline!
18
+
19
+ ---
20
+
21
+ ## 🚀 Quick Start (5 Minutes)
22
+
23
+ ### Step 1: Start APort API Server
24
+
25
+ **Use the agent-passport API server** - it runs locally and provides the full evaluation engine.
26
+
27
+ ```bash
28
+ # Start agent-passport server (from agent-passport repo)
29
+ cd /path/to/agent-passport
30
+ npm run dev
31
+ # Server runs on https://api.aport.io (or your self-hosted API)
32
+ ```
33
+
34
+ **Note:** The evaluation engine runs in the `agent-passport` server. This guardrail repo provides examples and SDKs that call the API.
35
+
36
+ ---
37
+
38
+ ### Step 2: Create Local Passport
39
+
40
+ Create `~/.openclaw/passport.json`:
41
+
42
+ ```json
43
+ {
44
+ "agent_id": "ap_openclaw_local_001",
45
+ "name": "OpenClaw Local Agent",
46
+ "controller_type": "person",
47
+ "description": "Local OpenClaw agent with APort policy enforcement",
48
+ "owner_id": "user@example.com",
49
+ "owner": "Your Name",
50
+ "role": "Developer",
51
+ "capabilities": [
52
+ { "id": "system.command.execute", "description": "System command execution" },
53
+ { "id": "mcp.tool.execute", "description": "MCP tool execution" },
54
+ { "id": "agent.session.create", "description": "Agent session creation" },
55
+ { "id": "agent.tool.register", "description": "Tool registration" }
56
+ ],
57
+ "limits": {
58
+ "allowed_commands": ["npm", "git", "node", "python", "make", "curl"],
59
+ "max_execution_time": 300,
60
+ "blocked_patterns": ["rm -rf", "sudo"],
61
+ "allowed_servers": ["https://mcp.github.com", "https://mcp.openai.com"],
62
+ "max_calls_per_minute": 60,
63
+ "allowed_tools": ["github.pull_requests.create", "github.issues.create"],
64
+ "max_sessions_per_day": 10,
65
+ "max_tools_per_session": 50
66
+ },
67
+ "regions": ["US"],
68
+ "status": "active",
69
+ "assurance_level": "L2",
70
+ "contact": "user@example.com",
71
+ "version": "1.0.0",
72
+ "created_at": "2026-02-08T00:00:00Z",
73
+ "expires_at": "2026-03-08T00:00:00Z"
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ### Step 3: Create Policy Files
80
+
81
+ Copy the 4 OpenClaw policies to `~/.openclaw/policies/`:
82
+
83
+ ```bash
84
+ mkdir -p ~/.openclaw/policies
85
+
86
+ # Copy from agent-passport repo
87
+ cp /Users/uchi/Downloads/projects/agent-passport/policies/system.command.execute.v1/policy.json ~/.openclaw/policies/
88
+ cp /Users/uchi/Downloads/projects/agent-passport/policies/mcp.tool.execute.v1/policy.json ~/.openclaw/policies/
89
+ cp /Users/uchi/Downloads/projects/agent-passport/policies/agent.session.create.v1/policy.json ~/.openclaw/policies/
90
+ cp /Users/uchi/Downloads/projects/agent-passport/policies/agent.tool.register.v1/policy.json ~/.openclaw/policies/
91
+ ```
92
+
93
+ ---
94
+
95
+ ### Step 4: Verify API Server is Running
96
+
97
+ Make sure the agent-passport server is running:
98
+
99
+ ```bash
100
+ # Check if server is running
101
+ curl https://api.aport.io/health || echo "API not reachable - check APORT_API_URL or network"
102
+ ```
103
+
104
+ The API server provides the full evaluation engine - no need to copy code!
105
+
106
+ ---
107
+
108
+ ## 📋 Integration Examples
109
+
110
+ ### Example 1: Command Execution Verification
111
+
112
+ **Before executing any command in OpenClaw:**
113
+
114
+ **Option A: Using Local Server (Recommended)**
115
+
116
+ ```python
117
+ import subprocess
118
+ import json
119
+ import os
120
+ import requests
121
+
122
+ def verify_command(command, args=None):
123
+ """Verify command execution against APort policy via local server"""
124
+ passport_file = os.path.expanduser("~/.openclaw/passport.json")
125
+ api_base = os.getenv("APORT_API_BASE", "https://api.aport.io")
126
+
127
+ # Load agent_id from passport
128
+ with open(passport_file) as f:
129
+ passport = json.load(f)
130
+ agent_id = passport["agent_id"]
131
+
132
+ # Build context
133
+ context = {
134
+ "agent_id": agent_id,
135
+ "policy_id": "system.command.execute.v1",
136
+ "command": command,
137
+ "args": args or []
138
+ }
139
+
140
+ # Call verification API (local server)
141
+ response = requests.post(
142
+ f"{api_base}/api/verify/policy/system.command.execute.v1",
143
+ json={"context": context}
144
+ )
145
+
146
+ decision = response.json().get("decision") or response.json()
147
+
148
+ if not decision.get("allow"):
149
+ reasons = decision.get("reasons", [])
150
+ reason_text = ", ".join([r.get("message", "") for r in reasons])
151
+ raise PermissionError(f"Policy denied: {reason_text}")
152
+
153
+ return decision
154
+ ```
155
+
156
+ **Option B: Using Evaluation Engine Directly (Advanced)**
157
+
158
+ ```python
159
+ import sys
160
+ import os
161
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src', 'evaluator'))
162
+
163
+ from generic_evaluator import evaluateGenericPolicyV2
164
+ import json
165
+
166
+ # Mock env for local use (no KV/D1 needed for basic evaluation)
167
+ class MockEnv:
168
+ pass
169
+
170
+ def verify_command(command, args=None):
171
+ """Verify command execution using evaluation engine directly"""
172
+ passport_file = os.path.expanduser("~/.openclaw/passport.json")
173
+
174
+ # Load passport
175
+ with open(passport_file) as f:
176
+ passport = json.load(f)
177
+
178
+ # Build context
179
+ context = {
180
+ "command": command,
181
+ "args": args or []
182
+ }
183
+
184
+ # Evaluate policy directly (no API call needed)
185
+ decision = await evaluateGenericPolicyV2(
186
+ MockEnv(), # Mock env - no DB access needed for basic checks
187
+ "system.command.execute.v1",
188
+ passport,
189
+ context
190
+ )
191
+
192
+ if not decision.get("allow"):
193
+ reasons = decision.get("reasons", [])
194
+ reason_text = ", ".join([r.get("message", "") for r in reasons])
195
+ raise PermissionError(f"Policy denied: {reason_text}")
196
+
197
+ return decision
198
+ ```
199
+
200
+ # Usage in OpenClaw
201
+ def execute_command(command, args=None):
202
+ # Pre-action verification
203
+ verify_command(command, args)
204
+
205
+ # Execute command (if allowed)
206
+ subprocess.run([command] + (args or []))
207
+ ```
208
+
209
+ **Test it:**
210
+
211
+ ```python
212
+ # This should be ALLOWED
213
+ execute_command("npm", ["install"])
214
+
215
+ # This should be DENIED (blocked pattern)
216
+ try:
217
+ execute_command("rm", ["-rf", "/"])
218
+ except PermissionError as e:
219
+ print(f"Blocked: {e}")
220
+
221
+ # This should be DENIED (not in allowlist)
222
+ try:
223
+ execute_command("sudo", ["apt", "update"])
224
+ except PermissionError as e:
225
+ print(f"Blocked: {e}")
226
+ ```
227
+
228
+ ---
229
+
230
+ ### Example 2: MCP Tool Verification
231
+
232
+ **Before calling any MCP tool:**
233
+
234
+ ```python
235
+ def verify_mcp_tool(server, tool, parameters):
236
+ """Verify MCP tool execution against APort policy"""
237
+ passport_file = os.path.expanduser("~/.openclaw/passport.json")
238
+ api_base = os.getenv("APORT_API_BASE", "https://api.aport.io")
239
+
240
+ # Load agent_id from passport
241
+ with open(passport_file) as f:
242
+ passport = json.load(f)
243
+ agent_id = passport["agent_id"]
244
+
245
+ # Build context
246
+ context = {
247
+ "agent_id": agent_id,
248
+ "policy_id": "mcp.tool.execute.v1",
249
+ "server": server,
250
+ "tool": tool,
251
+ "parameters": parameters
252
+ }
253
+
254
+ # Call verification API
255
+ import requests
256
+ response = requests.post(
257
+ f"{api_base}/api/verify/policy/mcp.tool.execute.v1",
258
+ json={"context": context}
259
+ )
260
+
261
+ decision = response.json().get("decision") or response.json()
262
+
263
+ if not decision.get("allow"):
264
+ reasons = decision.get("reasons", [])
265
+ reason_text = ", ".join([r.get("message", "") for r in reasons])
266
+ raise PermissionError(f"Policy denied: {reason_text}")
267
+
268
+ return decision
269
+
270
+ # Usage in OpenClaw MCP integration
271
+ def call_mcp_tool(server, tool, parameters):
272
+ # Pre-action verification
273
+ verify_mcp_tool(server, tool, parameters)
274
+
275
+ # Call MCP tool (if allowed)
276
+ # ... your MCP client code here ...
277
+ pass
278
+ ```
279
+
280
+ **Test it:**
281
+
282
+ ```python
283
+ # This should be ALLOWED (server in allowlist, tool allowed)
284
+ call_mcp_tool(
285
+ "https://mcp.github.com",
286
+ "github.pull_requests.create",
287
+ {"repo": "test/repo", "title": "Test PR"}
288
+ )
289
+
290
+ # This should be DENIED (server not in allowlist)
291
+ try:
292
+ call_mcp_tool(
293
+ "https://evil-server.com",
294
+ "malicious.tool",
295
+ {}
296
+ )
297
+ except PermissionError as e:
298
+ print(f"Blocked: {e}")
299
+
300
+ # This should be DENIED (tool not allowed)
301
+ try:
302
+ call_mcp_tool(
303
+ "https://mcp.github.com",
304
+ "github.repos.delete",
305
+ {"repo": "test/repo"}
306
+ )
307
+ except PermissionError as e:
308
+ print(f"Blocked: {e}")
309
+ ```
310
+
311
+ ---
312
+
313
+ ### Example 3: Complete OpenClaw Integration
314
+
315
+ **Wrap OpenClaw's tool execution:**
316
+
317
+ ```python
318
+ # .openclaw/extensions/aport.py
319
+
320
+ import os
321
+ import json
322
+ import requests
323
+ from typing import Dict, Any, Optional
324
+
325
+ class APortGuardrail:
326
+ """APort policy enforcement for OpenClaw"""
327
+
328
+ def __init__(self, passport_file: Optional[str] = None, api_base: Optional[str] = None):
329
+ self.passport_file = passport_file or os.path.expanduser("~/.openclaw/passport.json")
330
+ self.api_base = api_base or os.getenv("APORT_API_BASE", "https://api.aport.io")
331
+ self._agent_id = None
332
+
333
+ @property
334
+ def agent_id(self) -> str:
335
+ """Lazy load agent_id from passport"""
336
+ if self._agent_id is None:
337
+ with open(self.passport_file) as f:
338
+ passport = json.load(f)
339
+ self._agent_id = passport["agent_id"]
340
+ return self._agent_id
341
+
342
+ def verify(self, policy_id: str, context: Dict[str, Any]) -> Dict[str, Any]:
343
+ """Verify action against policy"""
344
+ full_context = {
345
+ "agent_id": self.agent_id,
346
+ "policy_id": policy_id,
347
+ **context
348
+ }
349
+
350
+ response = requests.post(
351
+ f"{self.api_base}/api/verify/policy/{policy_id}",
352
+ json={"context": full_context}
353
+ )
354
+
355
+ decision = response.json().get("decision") or response.json()
356
+
357
+ if not decision.get("allow"):
358
+ reasons = decision.get("reasons", [])
359
+ reason_text = ", ".join([r.get("message", "") for r in reasons])
360
+ raise PermissionError(f"Policy denied: {reason_text}")
361
+
362
+ return decision
363
+
364
+ def verify_command(self, command: str, args: list = None) -> Dict[str, Any]:
365
+ """Verify command execution"""
366
+ return self.verify("system.command.execute.v1", {
367
+ "command": command,
368
+ "args": args or []
369
+ })
370
+
371
+ def verify_mcp_tool(self, server: str, tool: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
372
+ """Verify MCP tool execution"""
373
+ return self.verify("mcp.tool.execute.v1", {
374
+ "server": server,
375
+ "tool": tool,
376
+ "parameters": parameters
377
+ })
378
+
379
+ def verify_session_create(self, session_config: Dict[str, Any]) -> Dict[str, Any]:
380
+ """Verify agent session creation"""
381
+ return self.verify("agent.session.create.v1", session_config)
382
+
383
+ def verify_tool_register(self, tool_config: Dict[str, Any]) -> Dict[str, Any]:
384
+ """Verify tool registration"""
385
+ return self.verify("agent.tool.register.v1", tool_config)
386
+
387
+
388
+ # Usage in OpenClaw agent
389
+ guardrail = APortGuardrail()
390
+
391
+ # Before executing command
392
+ try:
393
+ guardrail.verify_command("npm", ["install"])
394
+ # Execute command...
395
+ except PermissionError as e:
396
+ print(f"Command blocked: {e}")
397
+
398
+ # Before calling MCP tool
399
+ try:
400
+ guardrail.verify_mcp_tool(
401
+ "https://mcp.github.com",
402
+ "github.pull_requests.create",
403
+ {"repo": "test/repo"}
404
+ )
405
+ # Call MCP tool...
406
+ except PermissionError as e:
407
+ print(f"MCP tool blocked: {e}")
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 🔒 Security Features
413
+
414
+ ### Built-in Security Patterns
415
+
416
+ The `system.command.execute.v1` policy includes **40+ built-in security patterns** that are **always enforced**, even if not in your `blocked_patterns`:
417
+
418
+ ✅ **Command Injection** - Blocks `;`, `|`, `&`, `` ` ``, `$()`, `&&`, `||`
419
+ ✅ **Script Execution** - Blocks `bash -c`, `python -c`, `node -e`
420
+ ✅ **Path Traversal** - Blocks `../`, `..\`
421
+ ✅ **Privilege Escalation** - Blocks `sudo`, `su`, `doas`
422
+ ✅ **Dangerous Operations** - Blocks `rm -rf`, `format`, `dd if=`
423
+ ✅ **Environment Files** - Blocks `.env` file access
424
+ ✅ **Config Files** - Blocks `nginx.conf`, `apache2.conf` access
425
+ ✅ **Credentials** - Blocks AWS credentials, SSH keys, etc.
426
+ ✅ **Network Exfiltration** - Blocks `curl`, `wget` with URLs
427
+ ✅ **And 30+ more patterns...**
428
+
429
+ **These cannot be bypassed** - even if you add commands to `allowed_commands`, dangerous patterns are still blocked!
430
+
431
+ ---
432
+
433
+ ## 📊 Policy Mapping
434
+
435
+ | OpenClaw Action | Policy Pack | Policy File |
436
+ |----------------|-------------|-------------|
437
+ | `exec.run("npm install")` | `system.command.execute.v1` | `system.command.execute.v1/policy.json` |
438
+ | `mcp.call("github.pull_requests.create")` | `mcp.tool.execute.v1` | `mcp.tool.execute.v1/policy.json` |
439
+ | `agent.create_session()` | `agent.session.create.v1` | `agent.session.create.v1/policy.json` |
440
+ | `agent.register_tool()` | `agent.tool.register.v1` | `agent.tool.register.v1/policy.json` |
441
+
442
+ ---
443
+
444
+ ## 🧪 Testing
445
+
446
+ ### Test Command Verification
447
+
448
+ ```bash
449
+ # Should ALLOW
450
+ bin/aport-guardrail.sh system.command.execute.v1 '{
451
+ "command": "npm",
452
+ "args": ["install"]
453
+ }'
454
+
455
+ # Should DENY (blocked pattern)
456
+ bin/aport-guardrail.sh system.command.execute.v1 '{
457
+ "command": "rm",
458
+ "args": ["-rf", "/"]
459
+ }'
460
+
461
+ # Should DENY (not in allowlist)
462
+ bin/aport-guardrail.sh system.command.execute.v1 '{
463
+ "command": "sudo",
464
+ "args": ["apt", "update"]
465
+ }'
466
+ ```
467
+
468
+ ### Test MCP Tool Verification
469
+
470
+ ```bash
471
+ # Should ALLOW
472
+ bin/aport-guardrail.sh mcp.tool.execute.v1 '{
473
+ "server": "https://mcp.github.com",
474
+ "tool": "github.pull_requests.create",
475
+ "parameters": {"repo": "test/repo"}
476
+ }'
477
+
478
+ # Should DENY (server not in allowlist)
479
+ bin/aport-guardrail.sh mcp.tool.execute.v1 '{
480
+ "server": "https://evil-server.com",
481
+ "tool": "malicious.tool",
482
+ "parameters": {}
483
+ }'
484
+ ```
485
+
486
+ ---
487
+
488
+ ## 🚨 Suspend agent (kill switch = passport status)
489
+
490
+ **Passport is the source of truth.** To suspend the agent, set passport `status` to `suspended`:
491
+
492
+ ```bash
493
+ # Suspend: set passport status to suspended
494
+ jq '.status = "suspended"' ~/.openclaw/aport/passport.json > /tmp/passport.tmp && mv /tmp/passport.tmp ~/.openclaw/aport/passport.json
495
+
496
+ # All verifications will now DENY with oap.passport_suspended
497
+ bin/aport-guardrail.sh system.command.execute '{"command":"npm install"}'
498
+ # Exit 1, decision has allow: false, reasons[0].code: oap.passport_suspended
499
+
500
+ # Resume: set status back to active
501
+ jq '.status = "active"' ~/.openclaw/aport/passport.json > /tmp/passport.tmp && mv /tmp/passport.tmp ~/.openclaw/aport/passport.json
502
+ ```
503
+
504
+ ---
505
+
506
+ ## 📝 Audit Logging
507
+
508
+ All decisions are logged to `~/.openclaw/audit.log`:
509
+
510
+ ```
511
+ 2026-02-08T10:00:00Z | system.command.execute.v1 | ALLOW | npm install | decision_id=abc123
512
+ 2026-02-08T10:01:00Z | system.command.execute.v1 | DENY | rm -rf / | reason=blocked_pattern
513
+ 2026-02-08T10:02:00Z | mcp.tool.execute.v1 | ALLOW | github.pull_requests.create | decision_id=def456
514
+ ```
515
+
516
+ View recent activity:
517
+ ```bash
518
+ tail -f ~/.openclaw/audit.log
519
+ ```
520
+
521
+ ---
522
+
523
+ ## 🔄 Upgrading to Cloud (Optional)
524
+
525
+ When you're ready for team collaboration and global kill switch:
526
+
527
+ 1. **Sign up** at https://aport.io
528
+ 2. **Get API key** from dashboard
529
+ 3. **Update passport** with `agent_id` from cloud registry
530
+ 4. **Set environment variable:**
531
+ ```bash
532
+ export APORT_API_BASE=https://api.aport.io
533
+ export APORT_API_KEY=ap_live_xxxxx
534
+ ```
535
+
536
+ **Benefits:**
537
+ - ✅ Global kill switch (<15 seconds)
538
+ - ✅ Multi-machine sync
539
+ - ✅ Ed25519 signed receipts
540
+ - ✅ Team collaboration
541
+ - ✅ Analytics dashboard
542
+
543
+ ---
544
+
545
+ ## 🆘 Troubleshooting
546
+
547
+ ### Problem: "Passport not found"
548
+ ```bash
549
+ # Create passport
550
+ cat > ~/.openclaw/passport.json <<EOF
551
+ {
552
+ "agent_id": "ap_openclaw_local_001",
553
+ "status": "active",
554
+ ...
555
+ }
556
+ EOF
557
+ ```
558
+
559
+ ### Problem: "API server not running"
560
+ ```bash
561
+ # Start local API server
562
+ cd /Users/uchi/Downloads/projects/agent-passport
563
+ npm run dev
564
+ ```
565
+
566
+ ### Problem: "All commands denied"
567
+ ```bash
568
+ # Check passport status (source of truth)
569
+ jq '.status' ~/.openclaw/aport/passport.json
570
+ # Should be "active"; if "suspended" or "revoked", set back to "active" to resume
571
+ ```
572
+
573
+ ---
574
+
575
+ ## 📚 Next Steps
576
+
577
+ 1. ✅ **Customize limits** - Edit `~/.openclaw/passport.json` limits
578
+ 2. ✅ **Add more policies** - Copy additional policies from agent-passport repo
579
+ 3. ✅ **Integrate with OpenClaw** - Add verification to your agent code
580
+ 4. ✅ **Monitor audit logs** - Set up alerts for policy violations
581
+ 5. ✅ **Upgrade to cloud** - When ready for team features
582
+
583
+ ---
584
+
585
+ ## 🎉 You're Done!
586
+
587
+ OpenClaw is now secure with APort policy enforcement. All commands and MCP tools are verified before execution, with built-in protection against injection, path traversal, and other attacks.
588
+
589
+ **Questions?** Check out:
590
+ - [QuickStart: OpenClaw Plugin](QUICKSTART_OPENCLAW_PLUGIN.md)
591
+ - [Tool / Policy Mapping](TOOL_POLICY_MAPPING.md)
592
+ - [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues)
593
+
594
+ ---
595
+
596
+ **Made with ❤️ by Uchi (https://github.com/uchibeke/)**
@@ -0,0 +1,54 @@
1
+ # OpenClaw tools, policies, and passport
2
+
3
+ How the APort plugin maps OpenClaw tools to policies and where limits live (passport vs policy).
4
+
5
+ ---
6
+
7
+ ## How it works
8
+
9
+ 1. **Tool → policy** is defined in the **plugin** (`mapToolToPolicy` in `extensions/openclaw-aport/index.js`). OpenClaw tool names (e.g. `exec`, `message`, `read`) are mapped to APort policy IDs (e.g. `system.command.execute.v1`, `messaging.message.send.v1`).
10
+ 2. **Passport** holds **capabilities** (what the agent is allowed to do at a high level) and **limits** (per-policy constraints). There is **no global “tool allowlist”** in the passport for OpenClaw tool names. Control is per policy:
11
+ - **exec** → policy **system.command.execute.v1** → passport **limits.system.command.execute** (e.g. `allowed_commands`, `blocked_patterns`).
12
+ - **message** / **messaging.*** → policy **messaging.message.send.v1** → passport **limits.messaging** (e.g. `msgs_per_min`, `msgs_per_day`).
13
+ - **read**, **write**, **edit**, **browser**, **cron**, etc. → **no mapping** in this plugin → treated as unmapped (allowed by default when `allowUnmappedTools: true`).
14
+ 3. **OAP spec:** The passport schema has **limits** per policy and, for MCP, **mcp.servers** / **mcp.tools**. It does not define a single “allowed OpenClaw tools” list; the plugin decides which tools are mapped and which are unmapped.
15
+
16
+ ---
17
+
18
+ ## exec and allowed_commands (OAP: limits live in the passport)
19
+
20
+ Per the **Open Agent Passport (OAP) spec**, the passport has a **limits** object: *operational limits per capability*. Each policy reads its limits from the passport under a key that matches the policy (e.g. `system.command.execute`, `messaging`). There is no separate “allowed list” config outside the passport.
21
+
22
+ - **Where it’s set:** **passport.limits.system.command.execute.allowed_commands** (array of strings). The guardrail script and API both read `LIMITS = passport.limits["system.command.execute"]` and then `.allowed_commands[]` from that. So **allowed_commands is supposed to be set in the passport**; the wizard (or you) populate it when creating/editing the passport.
23
+ - **How it’s enforced:** For **system.command.execute.v1**, the evaluator checks the request’s `context.command` (e.g. `mkdir` or `mkdir -p foo`). The command is allowed if: (1) **allow-all:** `allowed_commands` contains `"*"` (any command passes the allowlist; **blocked_patterns** still apply), or (2) **allowlist:** the command matches one of the passport’s `allowed_commands` entries (exact or prefix match). If there is at least one entry and the command doesn’t match any and there’s no `"*"`, the policy returns **oap.command_not_allowed** (“Command must be in allowed list”). The **bash** guardrail and the **agent-passport API** both support `"*"` for “allow all commands.”
24
+ - OpenClaw uses **exec** both to run the guardrail script (we detect that and evaluate the **inner** tool) and to run real shell commands (`mkdir`, `npm install`, etc.). When the run is a real command, we evaluate **system.command.execute.v1** and check the command against **passport limits.system.command.execute.allowed_commands**. So: **every command you want to allow must be in that passport array** (e.g. `mkdir`, `cp`, `ls`, `cat`, `echo`, `pwd`, `mv`, `touch`, `npx`, `open`). Re-run the passport wizard for an expanded default, or edit the passport and add them.
25
+ - **If the guardrail is run via exec** (e.g. a skill runs `bash ~/.openclaw/.skills/aport-guardrail.sh ...`), that **exec** is also checked against **allowed_commands**. Include **`bash`** (so `bash /path/to/aport-guardrail.sh ...` passes prefix match) or the full script path so the guardrail invocation is allowed. The default wizard list includes `bash` and `sh`; if you use a narrow list, add them.
26
+ - If you want OpenClaw to run any command without guardrail checks for exec, set **mapExecToPolicy: false** in the plugin config; then **exec** is unmapped and allowed (no policy check). This disables command allowlisting for exec.
27
+
28
+ ---
29
+
30
+ ## read, write, and other unmapped tools
31
+
32
+ - **read**, **write**, **edit**, **apply_patch**, **browser**, **cron**, **gateway**, **sessions_***, **nodes**, **image**, **web_search**, **web_fetch** are **not** mapped to any APort policy in this plugin. They are **unmapped** and **allowed** by default (when `allowUnmappedTools: true`). So **read is enabled** by default: the plugin sees no policy for `read`, returns allow, and OpenClaw runs the read tool.
33
+ - A failure like **read failed: ENOENT: no such file or directory** is the **tool** failing (e.g. wrong path or missing file, such as `config.json` when the app uses `config.yaml`), not the guardrail blocking. The guardrail already allowed the call; the error happens when the tool executes.
34
+ - To enforce policy on file access you would add a new policy (e.g. **file.read.v1**) and map **read** to it in the plugin, with passport limits (e.g. **allowed_paths**). That is not implemented today.
35
+
36
+ ---
37
+
38
+ ## Summary table
39
+
40
+ | OpenClaw tool (examples) | APort policy | Passport limits (key) |
41
+ |--------------------------|---------------------------|------------------------------------------|
42
+ | exec | system.command.execute.v1 | limits.system.command.execute |
43
+ | message, messaging.* | messaging.message.send.v1 | limits.messaging |
44
+ | git.* | code.repository.merge.v1 | limits.code.repository.merge |
45
+ | mcp.* | mcp.tool.execute.v1 | (API / MCP limits) |
46
+ | read, write, edit, etc. | *(none)* | *(unmapped, allowed by default)* |
47
+
48
+ ---
49
+
50
+ ## References
51
+
52
+ - [TOOL_POLICY_MAPPING.md](TOOL_POLICY_MAPPING.md) — Full mapping table and script behavior.
53
+ - [OpenClaw Tools](https://docs.openclaw.ai/tools) — Official list of OpenClaw tools (exec, read, message, cron, etc.).
54
+ - Plugin config: `mapExecToPolicy`, `allowUnmappedTools` in [extensions/openclaw-aport/README.md](../extensions/openclaw-aport/README.md).