@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,415 @@
1
+ # APort OpenClaw Plugin
2
+
3
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](package.json) — **APort Node SDK:** [npm](https://www.npmjs.com/package/@aporthq/sdk-node)
4
+
5
+ **Deterministic pre-action authorization for OpenClaw agents.**
6
+
7
+ This plugin registers `before_tool_call` hooks to enforce APort policies **before every tool execution**. No more relying on prompts or hoping the AI follows instructions - the platform enforces policy.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ ✅ **100% Deterministic** - Platform enforces, AI cannot bypass
14
+ ✅ **Fail-Closed** - Blocks on error (configurable)
15
+ ✅ **Local or API Mode** - Use local script or APort cloud API
16
+ ✅ **Zero OpenClaw Changes** - Uses existing plugin API
17
+ ✅ **Audit Logging** - Optional after_tool_call hook
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ### Option 1: Via Setup Script (Recommended)
24
+
25
+ ```bash
26
+ # From aport-agent-guardrails repo root — one run secures OpenClaw
27
+ ./bin/openclaw
28
+ ```
29
+
30
+ The setup script will:
31
+ 1. Ask for OpenClaw config directory (default `~/.openclaw`)
32
+ 2. Create passport (OAP v1.0 spec) there
33
+ 3. Prompt to install this OpenClaw plugin (deterministic enforcement)
34
+ 4. Generate `config.yaml` with plugin config (passport path, guardrail script path, mode)
35
+ 5. Install guardrail wrappers in `CONFIG_DIR/.skills/` (including `aport-guardrail-bash.sh` used by the plugin in local mode)
36
+ 6. Optionally install the APort skill, AGENTS.md rule, and run a smoke test
37
+ 7. Verify plugin installation
38
+
39
+ **One run is enough.** After that, start OpenClaw with the generated config (e.g. `openclaw gateway start --config ~/.openclaw/config.yaml`); the plugin will enforce policy on every tool call. See [QUICKSTART_OPENCLAW_PLUGIN.md](../../docs/QUICKSTART_OPENCLAW_PLUGIN.md).
40
+
41
+ ### Option 2: Manual Installation
42
+
43
+ ```bash
44
+ # From your OpenClaw config directory
45
+ openclaw plugins install /path/to/aport-agent-guardrails/extensions/openclaw-aport
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Configuration
51
+
52
+ Add to your OpenClaw `config.yaml`:
53
+
54
+ ```yaml
55
+ plugins:
56
+ enabled: true
57
+ entries:
58
+ openclaw-aport:
59
+ enabled: true
60
+ config:
61
+ # Mode: "local" (use guardrail script) or "api" (use APort cloud API)
62
+ mode: local
63
+
64
+ # Passport file location (in aport/ subdir; legacy: ~/.openclaw/passport.json)
65
+ passportFile: ~/.openclaw/aport/passport.json
66
+
67
+ # For local mode: path to guardrail script
68
+ guardrailScript: ~/.openclaw/.skills/aport-guardrail-bash.sh
69
+
70
+ # For API mode: APort API endpoint
71
+ apiUrl: https://api.aport.io
72
+ # Optional: set APORT_API_KEY in the environment if your API requires auth
73
+
74
+ # Fail-closed: block on error (default: true)
75
+ failClosed: true
76
+
77
+ # Run APort verify on every tool call; never reuse a previous decision (default: true)
78
+ alwaysVerifyEachToolCall: true
79
+
80
+ # Map exec to system.command.execute.v1 and check passport allowed_commands (default: true).
81
+ # Set to false to never block exec (OpenClaw can run any command; no guardrail for exec).
82
+ mapExecToPolicy: true
83
+ ```
84
+
85
+ ---
86
+
87
+ ## exec, allowed_commands, and unmapped tools
88
+
89
+ - **exec** is OpenClaw’s main “run something” tool: it can run the guardrail script (we delegate to the inner tool) or a real shell command (e.g. `mkdir`, `npm install`). By default we map **exec** → **system.command.execute.v1** and check the **command** against your passport’s **limits.system.command.execute.allowed_commands**. If `mkdir` (or another command) is not in that list, the policy denies with `oap.command_not_allowed`.
90
+ - **Fix:** Add every command you need to **allowed_commands** in your passport (e.g. `mkdir`, `cp`, `ls`, `cat`, `echo`, `pwd`, `mv`, `touch`, `npx`, `open`). Re-run the passport wizard to get an expanded default list, or edit `~/.openclaw/aport/passport.json` (or `~/.openclaw/passport.json` for legacy) and add to `limits.system.command.execute.allowed_commands`. If the guardrail is ever run via **exec** (e.g. a skill runs `bash ~/.openclaw/.skills/aport-guardrail.sh ...`), include **`bash`** (or the full script path) in **allowed_commands** so that invocation is allowed; the wizard default includes `bash` and `sh`.
91
+ - **Optional:** Set **mapExecToPolicy: false** in plugin config so **exec** is not mapped; then exec is treated as an unmapped tool and allowed (no command allowlist). Use only if you rely on other controls; this disables guardrail protection for shell commands.
92
+ - **read, write, edit, etc.** (OpenClaw file/browser tools) have **no policy mapping** in this plugin, so they are **allowed** by default when `allowUnmappedTools` is true. A “read failed: ENOENT” is the tool failing (e.g. file not found), not the guardrail blocking. Tool→policy mapping and passport limits are documented in [TOOL_POLICY_MAPPING.md](../../docs/TOOL_POLICY_MAPPING.md) and [OPENCLAW_TOOLS_AND_POLICIES.md](../../docs/OPENCLAW_TOOLS_AND_POLICIES.md).
93
+
94
+ ---
95
+
96
+ ## Every tool call = fresh APort check (no caching)
97
+
98
+ The plugin **never reuses a previous decision**. Each `before_tool_call` runs a new verify (local script or API). In local mode each call gets a **unique decision file path** (`decisions/<timestamp>-<id>.json`); the plugin only reads the file it passed to that invocation, so there is no cache or reuse.
99
+
100
+ - **mkdir** → APort runs → Deny
101
+ - **mkdir** again → APort runs again → Allow or Deny based on **current** passport/limits
102
+
103
+ **Exec with no command:** If OpenClaw sends an `exec` tool call with an empty or missing command (e.g. a probe or placeholder), the plugin allows it without calling the guardrail so those pre-checks are not blocked. The real `exec` with a command (e.g. `ls`) is still evaluated by the guardrail.
104
+
105
+ If you updated your passport (e.g. added a command to `allowed_commands` or changed limits), the next tool call is evaluated against the new state. Set `alwaysVerifyEachToolCall: false` only if you add a future cache and want to opt out of per-call verification.
106
+
107
+ ---
108
+
109
+ ## Agent instructions (AGENTS.md)
110
+
111
+ The **guardrail** always runs for every tool call. The **agent** (LLM) must not assume "same tool → same result as last time." Add this to your OpenClaw project's **AGENTS.md** (or equivalent) so the agent always invokes the tool and lets APort decide each time:
112
+
113
+ ```markdown
114
+ ## APort guardrails
115
+ - **Always invoke the tool** when the user requests an action. Do not skip or assume a tool will be denied because a previous invocation was denied.
116
+ - APort is re-evaluated on every tool call; passport or limits may have changed. The plugin does not reuse previous decisions.
117
+ ```
118
+
119
+ ---
120
+
121
+ ## How It Works
122
+
123
+ ```mermaid
124
+ sequenceDiagram
125
+ participant Agent as 🤖 OpenClaw Agent
126
+ participant Plugin as 🛡️ APort Plugin
127
+ participant Guard as 📋 Guardrail
128
+ participant Tool as 🔧 Tool
129
+
130
+ Agent->>Plugin: before_tool_call(toolName, params)
131
+ Plugin->>Plugin: Map tool → policy
132
+ Plugin->>Guard: Verify policy
133
+
134
+ alt Policy Allows
135
+ Guard-->>Plugin: ✅ Decision: Allow
136
+ Plugin-->>Agent: {} (continue)
137
+ Agent->>Tool: Execute tool
138
+ else Policy Denies
139
+ Guard-->>Plugin: ❌ Decision: Deny
140
+ Plugin-->>Agent: { block: true, blockReason }
141
+ Agent->>Agent: Throw error (tool NOT executed)
142
+ end
143
+ ```
144
+
145
+ ### Tool-to-Policy Mapping
146
+
147
+ | OpenClaw Tool | APort Policy |
148
+ |---------------|--------------|
149
+ | `git.create_pr`, `git.merge`, `git.push` | `code.repository.merge.v1` |
150
+ | `exec.run`, `system.command.*`, `bash` | `system.command.execute.v1` |
151
+ | `message.send`, `messaging.*` | `messaging.message.send.v1` |
152
+ | `mcp.*` | `mcp.tool.execute.v1` |
153
+ | `session.create` | `agent.session.create.v1` |
154
+ | `tool.register` | `agent.tool.register.v1` |
155
+ | `payment.refund` | `finance.payment.refund.v1` |
156
+ | `payment.charge` | `finance.payment.charge.v1` |
157
+ | `data.export` | `data.export.create.v1` |
158
+
159
+ Unmapped tools are **allowed** by default so [custom skills](https://clawhub.ai/skills), [ClawHub](https://clawhub.ai/skills?sort=downloads), and built-in tools (e.g. browser, memory) work without APort blocking them. Only tools we map (exec, git, message, mcp, payment, data.export, etc.) are checked against the passport. Set `allowUnmappedTools: false` for strict environments that want to block any unmapped tool.
160
+
161
+ ---
162
+
163
+ ## Modes
164
+
165
+ ### Local Mode (Default)
166
+
167
+ **Best for:** Privacy, offline use, no network dependency
168
+
169
+ ```yaml
170
+ config:
171
+ mode: local
172
+ passportFile: ~/.openclaw/passport.json
173
+ guardrailScript: ~/.openclaw/.skills/aport-guardrail-bash.sh
174
+ ```
175
+
176
+ **How it works:**
177
+ 1. Plugin calls local bash script
178
+ 2. Script evaluates policy using local passport
179
+ 3. Returns decision (exit 0 = allow, exit 1 = deny)
180
+
181
+ **No network required** - everything runs locally.
182
+
183
+ ### API Mode
184
+
185
+ **Best for:** Advanced features, cloud kill switch, audit logs
186
+
187
+ ```yaml
188
+ config:
189
+ mode: api
190
+ passportFile: ~/.openclaw/passport.json
191
+ apiUrl: https://api.aport.io # or your self-hosted API URL
192
+ # Set APORT_API_KEY in the environment if your API requires auth
193
+ ```
194
+
195
+ **How it works:**
196
+ 1. Plugin loads local passport
197
+ 2. Sends passport + context to APort API
198
+ 3. API evaluates (passport NOT stored, stateless)
199
+ 4. Returns signed decision
200
+
201
+ **Network required** - sends passport to API for evaluation.
202
+
203
+ ---
204
+
205
+ ## Testing
206
+
207
+ ### Test the Plugin
208
+
209
+ ```bash
210
+ # 1. Install plugin (via setup or manually)
211
+ openclaw plugins install /path/to/extensions/openclaw-aport
212
+
213
+ # 2. Configure in config.yaml (see above)
214
+
215
+ # 3. Start OpenClaw agent
216
+ openclaw agent start
217
+
218
+ # 4. Try a command that should be allowed
219
+ # (Agent will call plugin before executing)
220
+ "Create a file called test.txt"
221
+
222
+ # 5. Try a command that should be denied
223
+ "Run: rm -rf /"
224
+ # Expected: Plugin blocks with reason from passport limits
225
+ ```
226
+
227
+ ### Check Plugin Logs
228
+
229
+ ```bash
230
+ # Plugin logs to OpenClaw logs
231
+ openclaw logs | grep "APort Guardrails"
232
+
233
+ # Should see:
234
+ # [APort Guardrails] Loaded: mode=local, passportFile=~/.openclaw/passport.json
235
+ # [APort Guardrails] Checking tool: exec.run → policy: system.command.execute.v1
236
+ # [APort Guardrails] ALLOW: system.command.execute - mkdir test
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Troubleshooting
242
+
243
+ ### Plugin not loading
244
+
245
+ ```bash
246
+ # Check plugin list
247
+ openclaw plugins list
248
+
249
+ # Should show:
250
+ # openclaw-aport (enabled)
251
+ ```
252
+
253
+ If not listed:
254
+ 1. Verify installation: `openclaw plugins install /path/to/extensions/openclaw-aport`
255
+ 2. Check config.yaml has `plugins.entries.openclaw-aport.enabled: true`
256
+ 3. Restart OpenClaw gateway
257
+
258
+ ### Tools not being blocked
259
+
260
+ Check:
261
+ 1. **Plugin enabled?** `openclaw plugins list` should show `openclaw-aport (enabled)`
262
+ 2. **Tool mapped?** See "Tool-to-Policy Mapping" above. Unmapped tools are allowed by default (so skills work); set `allowUnmappedTools: false` to block them (strict).
263
+ 3. **Passport allows it?** Check passport limits in `~/.openclaw/passport.json`
264
+ 4. **Script working?** Test directly: `~/.openclaw/.skills/aport-guardrail-bash.sh system.command.execute '{"command":"ls"}'`
265
+
266
+ ### Error: "Failed to run guardrail script"
267
+
268
+ Check:
269
+ 1. Script exists: `ls -l ~/.openclaw/.skills/aport-guardrail-bash.sh`
270
+ 2. Script executable: `chmod +x ~/.openclaw/.skills/aport-guardrail-bash.sh`
271
+ 3. Script works: Run test command above
272
+
273
+ ### Error: "API request failed"
274
+
275
+ Check:
276
+ 1. API URL correct: `echo $APORT_API_URL` or check config.yaml
277
+ 2. API running: `curl $APORT_API_URL/health` (if self-hosted)
278
+ 3. If your API requires auth: set `APORT_API_KEY` in the environment (do not put it in config)
279
+ 4. Network connectivity
280
+
281
+ ### Error: "MissingEnvVarError: Missing env var APORT_API_KEY"
282
+
283
+ OpenClaw substitutes `${VAR}` in config and requires the variable to exist. **Do not put `apiKey: \${APORT_API_KEY}` in config.** Fix:
284
+
285
+ 1. **Remove apiKey from config:** Edit `~/.openclaw/openclaw.json` and delete the `"apiKey": "${APORT_API_KEY}"` line under `plugins.entries.openclaw-aport.config`, or run `make openclaw-setup` again (setup no longer writes apiKey to config).
286
+ 2. If your API requires auth, set `APORT_API_KEY` in the environment only; the plugin reads it at runtime.
287
+
288
+ ---
289
+
290
+ ## Security Considerations
291
+
292
+ ### Fail-Closed by Default
293
+
294
+ By default, `failClosed: true` means **any error blocks the tool**:
295
+ - Script not found → BLOCK
296
+ - API unreachable → BLOCK
297
+ - Invalid passport → BLOCK
298
+
299
+ This is secure-by-default. To fail-open (not recommended):
300
+
301
+ ```yaml
302
+ config:
303
+ failClosed: false # Allow on error (NOT RECOMMENDED)
304
+ ```
305
+
306
+ ### Plugin Trust
307
+
308
+ Plugins run **in-process** with full access to OpenClaw. Only install from trusted sources:
309
+ - Official APort plugin (this)
310
+ - Your own forks/modifications
311
+
312
+ Use `plugins.allow` allowlist in config.yaml:
313
+
314
+ ```yaml
315
+ plugins:
316
+ allow:
317
+ - openclaw-aport
318
+ - your-other-trusted-plugin
319
+ ```
320
+
321
+ ### Bypass Prevention
322
+
323
+ **With this plugin:** AI **cannot** bypass policy enforcement. The platform calls `before_tool_call` before every tool.
324
+
325
+ **Without this plugin (AGENTS.md only):** AI **can** bypass via:
326
+ - Prompt injection
327
+ - Forgetting to call guardrail
328
+ - Deciding action is "safe"
329
+
330
+ **Bottom line:** Plugin = deterministic. AGENTS.md = best-effort (not secure).
331
+
332
+ ---
333
+
334
+ ## Decisions and audit (OAP)
335
+
336
+ APort decisions are **structured and auditable**. They follow the [OAP v1.0 decision schema](https://github.com/aporthq/aport-spec/oap/decision-schema) (e.g. `decision_id`, `policy_id`, `allow`, `reasons`, `passport_digest`, `signature`, `kid`). The agent-passport API returns signed decisions and can chain them in an audit trail (KV/D1 + audit actions).
337
+
338
+ **Local mode (this plugin):**
339
+ - **Decisions** are written to `<config_dir>/decisions/<timestamp>-<id>.json` and **kept** (not deleted). Each file is a full OAP decision (allow or deny). Config dir is derived from `passportFile` (e.g. `~/.openclaw` → `~/.openclaw/decisions/`).
340
+ - **Audit log** one-line summary is appended to `<config_dir>/audit.log` by the guardrail script (tool, decision_id, allow, policy, code).
341
+ - Local evaluations use **unsigned** decisions (`signature: "ed25519:local-unsigned"`, `kid: "oap:local:dev-key"`). This is the open-source/local promise: structured decisions and audit trail, with optional signing in API or enterprise.
342
+
343
+ **API mode:** The APort API can return signed decisions (`ed25519:...`, `kid: oap:registry:...`) and log decisions server-side (e.g. DecisionService, chained audit). Use API mode when you need signed, verifiable decisions and central audit.
344
+
345
+ **Tamper-resistant local decisions:** Each decision file includes a **content_hash** (SHA-256 of the canonical decision payload). A **chain** is maintained in `decisions/.chain-state.json`: each decision stores `prev_decision_id` and `prev_content_hash`. If a file is edited or the chain is reordered, the plugin detects it (content_hash mismatch) and logs a warning. Decisions remain valid for allow/deny; the check is for audit integrity.
346
+
347
+ **References:** `agent-passport` [spec/oap/decision-schema.json](https://github.com/aporthq/agent-passport/blob/main/spec/oap/decision-schema.json), [examples](https://github.com/aporthq/agent-passport/tree/main/spec/oap/examples), and [functions/api/verify/policy/[pack_id].ts](https://github.com/aporthq/agent-passport/blob/main/functions/api/verify/policy/%5Bpack_id%5D.ts) for how decisions are built and logged.
348
+
349
+ ---
350
+
351
+ ## Performance and non-blocking behavior
352
+
353
+ - **Critical path:** Only policy evaluation and writing the decision file (so the plugin can read allow/deny) block the tool call. Chain state is updated synchronously so the next decision can link; audit log append runs in the background and must not block.
354
+ - **Plugin:** Tamper checking (content_hash verification) runs in `setImmediate` after the allow/deny return, so it never delays the tool call.
355
+ - **Guardrail script:** Audit log append is done in a background subshell (`( echo ... >> audit.log ) &`). Chain state write is best-effort (failures do not change the script exit code).
356
+
357
+ **Tests:** Run `npm test` in this directory. Unit tests cover mapping, integrity verification, and canonicalize; performance tests assert that hot paths stay within latency bounds; integration test runs the guardrail script when the repo is available and checks content_hash and chain.
358
+
359
+ ## Development
360
+
361
+ ### Running Locally
362
+
363
+ ```bash
364
+ # From this directory
365
+ cd extensions/openclaw-aport
366
+
367
+ # Test plugin registration
368
+ node index.js
369
+
370
+ # Link for local testing
371
+ npm link
372
+ openclaw plugins install $(pwd)
373
+ ```
374
+
375
+ ### Debugging
376
+
377
+ Add debug logging:
378
+
379
+ ```javascript
380
+ // In index.js
381
+ api.on('before_tool_call', async (event, ctx) => {
382
+ console.log('[DEBUG] Tool:', event.toolName);
383
+ console.log('[DEBUG] Params:', event.params);
384
+ // ...
385
+ });
386
+ ```
387
+
388
+ View logs:
389
+ ```bash
390
+ openclaw logs --follow | grep -E "(APort|DEBUG)"
391
+ ```
392
+
393
+ ---
394
+
395
+ ## License
396
+
397
+ Apache 2.0 - See [LICENSE](../../LICENSE)
398
+
399
+ ---
400
+
401
+ ## Support
402
+
403
+ - **Documentation:** [aport-agent-guardrails/docs](../../docs/)
404
+ - **Issues:** [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues)
405
+ - **Discord:** [discord.gg/aport](https://discord.gg/aport)
406
+
407
+ ---
408
+
409
+ ## Roadmap
410
+
411
+ - [ ] **Policy analytics** - Track allow/deny rates per policy
412
+ - [ ] **Custom mappings** - User-defined tool-to-policy mappings
413
+ - [ ] **Performance metrics** - Measure policy evaluation latency
414
+ - [ ] **Batch verification** - Verify multiple tools at once
415
+ - [ ] **Policy caching** - Cache decisions for repeated actions