@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
package/bin/openclaw ADDED
@@ -0,0 +1,660 @@
1
+ #!/bin/bash
2
+ # OpenClaw + APort: interactive setup
3
+ # Run from repo root. One run secures OpenClaw: creates passport, installs the
4
+ # APort plugin (deterministic enforcement), writes config and guardrail wrappers.
5
+ # After setup, start OpenClaw with the generated config (e.g. --config ~/.openclaw/config.yaml).
6
+ #
7
+ # Usage: ./bin/openclaw [agent_id]
8
+ # agent_id (optional): Hosted passport ID from aport.io (e.g., ap_abc123...)
9
+ #
10
+ # Naming: This script has no .sh extension so it can be invoked as a single
11
+ # command (e.g. make openclaw-setup → openclaw). Binaries/entrypoints are
12
+ # typically named without extension (e.g. git, npm); .sh is for library scripts.
13
+
14
+ set -e
15
+
16
+ # Resolve REPO_ROOT: follow symlinks so npx (node_modules/.bin/agent-guardrails -> package/bin/openclaw) works
17
+ SCRIPT_PATH="${BASH_SOURCE[0]}"
18
+ while [ -L "$SCRIPT_PATH" ]; do
19
+ TARGET="$(readlink "$SCRIPT_PATH")"
20
+ if [[ "$TARGET" == /* ]]; then
21
+ SCRIPT_PATH="$TARGET"
22
+ else
23
+ SCRIPT_PATH="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)/$TARGET"
24
+ fi
25
+ done
26
+ SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
27
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
28
+
29
+ APORT_PLUGIN_PATH="$REPO_ROOT/extensions/openclaw-aport"
30
+ DEFAULT_CONFIG="${OPENCLAW_HOME:-$HOME/.openclaw}"
31
+
32
+ # Parse agent_id from command line
33
+ HOSTED_AGENT_ID=""
34
+ if [ -n "$1" ]; then
35
+ # Validate agent_id format (ap_ followed by 32 hex chars)
36
+ if [[ "$1" =~ ^ap_[a-f0-9]{32}$ ]]; then
37
+ HOSTED_AGENT_ID="$1"
38
+ else
39
+ echo "❌ Invalid agent_id format: $1"
40
+ echo " Expected: ap_[32 hex characters]"
41
+ echo " Example: ap_fa2f6d53bb5b4c98b9af0124285b6e0f"
42
+ exit 1
43
+ fi
44
+ fi
45
+
46
+ echo ""
47
+ echo " 🛡️ APort + OpenClaw Setup"
48
+ echo " ═══════════════════════════"
49
+ echo ""
50
+
51
+ if [ -n "$HOSTED_AGENT_ID" ]; then
52
+ echo " ✅ Using hosted passport"
53
+ echo " Agent ID: $HOSTED_AGENT_ID"
54
+ echo " Your passport will be fetched from APort API on every call."
55
+ echo ""
56
+ echo " This will:"
57
+ echo " 1. Choose where to store your OpenClaw config"
58
+ echo " 2. Install APort OpenClaw plugin (deterministic enforcement)"
59
+ echo " 3. Install skill wrappers and the APort skill for OpenClaw"
60
+ echo " 4. Show tool → policy pack mapping"
61
+ echo ""
62
+ else
63
+ echo " This will:"
64
+ echo " 1. Choose where to store your OpenClaw config (passport, decisions, audit)"
65
+ echo " 2. Run the passport wizard (OAP v1.0)"
66
+ echo " 3. Install APort OpenClaw plugin (deterministic enforcement)"
67
+ echo " 4. Install skill wrappers and the APort skill for OpenClaw"
68
+ echo " 5. Show tool → policy pack mapping"
69
+ echo ""
70
+ fi
71
+
72
+ # 1. OpenClaw config directory
73
+ echo " 📁 OpenClaw config directory"
74
+ echo " Default: $DEFAULT_CONFIG"
75
+ echo " (Enter = default; or full path e.g. /Users/you/project/.openclaw or relative e.g. ../my-project)"
76
+ echo ""
77
+ read -p " Config directory [$DEFAULT_CONFIG]: " CONFIG_DIR
78
+ CONFIG_DIR="${CONFIG_DIR:-$DEFAULT_CONFIG}"
79
+ CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
80
+ # Resolve to absolute path
81
+ if [ "${CONFIG_DIR#/}" = "$CONFIG_DIR" ]; then
82
+ # No leading / or ~: treat as relative, but fix common mistake (pasted path missing leading /)
83
+ first_seg="${CONFIG_DIR%%/*}"
84
+ case "$(echo "$first_seg" | tr 'A-Z' 'a-z')" in
85
+ users|home) CONFIG_DIR="/$CONFIG_DIR" ;;
86
+ *) CONFIG_DIR="$PWD/$CONFIG_DIR" ;;
87
+ esac
88
+ fi
89
+ # Fix duplicated path (e.g. .../aport-agent-guardrails/Users/uchi/... -> /Users/uchi/...)
90
+ if echo "$CONFIG_DIR" | grep -q '/Users/'; then
91
+ CONFIG_DIR=$(echo "$CONFIG_DIR" | sed 's|.*/Users/|/Users/|')
92
+ elif echo "$CONFIG_DIR" | grep -q '/home/'; then
93
+ CONFIG_DIR=$(echo "$CONFIG_DIR" | sed 's|.*/home/|/home/|')
94
+ fi
95
+ mkdir -p "$CONFIG_DIR"
96
+ CONFIG_DIR="$(cd "$CONFIG_DIR" && pwd)"
97
+
98
+ # APort data: passport, decision, audit in config_dir/aport/ (passport status = source of truth for suspend)
99
+ APORT_DIR="$CONFIG_DIR/aport"
100
+ mkdir -p "$APORT_DIR"
101
+
102
+ echo ""
103
+ echo " ✓ Using: $CONFIG_DIR"
104
+ echo ""
105
+
106
+ # 2. Passport setup (hosted or local)
107
+ # Resolve passport path: prefer aport/, fallback to config root (legacy)
108
+ if [ -f "$APORT_DIR/passport.json" ]; then
109
+ PASSPORT_FILE="$APORT_DIR/passport.json"
110
+ elif [ -f "$CONFIG_DIR/passport.json" ]; then
111
+ PASSPORT_FILE="$CONFIG_DIR/passport.json"
112
+ else
113
+ PASSPORT_FILE="$APORT_DIR/passport.json"
114
+ fi
115
+ export OPENCLAW_CONFIG_DIR="$CONFIG_DIR"
116
+ USE_HOSTED_PASSPORT=false
117
+
118
+ if [ -n "$HOSTED_AGENT_ID" ]; then
119
+ # Agent ID provided via command line - use hosted passport
120
+ echo " 📋 Passport: Hosted (agent_id: $HOSTED_AGENT_ID)"
121
+ echo " Skipping local passport creation."
122
+ echo ""
123
+ USE_HOSTED_PASSPORT=true
124
+ elif [ -f "$PASSPORT_FILE" ]; then
125
+ # Local passport exists
126
+ read -p " Passport already exists. Run wizard again (overwrite)? [y/N]: " again
127
+ if [ "$again" != "y" ] && [ "$again" != "Y" ]; then
128
+ echo " Skipping passport creation."
129
+ else
130
+ "$REPO_ROOT/bin/aport-create-passport.sh" --output "$PASSPORT_FILE"
131
+ fi
132
+ else
133
+ # No agent_id, no local passport - ask user
134
+ echo " 📋 Passport Options:"
135
+ echo " 1. Use hosted passport (agent_id from aport.io)"
136
+ echo " 2. Create new local passport (wizard)"
137
+ echo ""
138
+ read -p " Choice [1/2]: " passport_choice
139
+
140
+ if [ "$passport_choice" = "1" ]; then
141
+ echo ""
142
+ read -p " Enter agent_id from aport.io: " agent_id_input
143
+ # Validate format
144
+ if [[ ! "$agent_id_input" =~ ^ap_[a-f0-9]{32}$ ]]; then
145
+ echo " ❌ Invalid agent_id format. Expected: ap_[32 hex characters]"
146
+ exit 1
147
+ fi
148
+ HOSTED_AGENT_ID="$agent_id_input"
149
+ USE_HOSTED_PASSPORT=true
150
+ echo " ✅ Using hosted passport (agent_id: $HOSTED_AGENT_ID)"
151
+ echo ""
152
+ else
153
+ # Create local passport
154
+ "$REPO_ROOT/bin/aport-create-passport.sh" --output "$PASSPORT_FILE"
155
+ fi
156
+ fi
157
+
158
+ # 3. Install OpenClaw Plugin (Deterministic Enforcement)
159
+ echo ""
160
+ echo " 🔌 OpenClaw Plugin Installation"
161
+ echo " ════════════════════════════════"
162
+ echo ""
163
+ echo " The APort OpenClaw plugin provides DETERMINISTIC policy enforcement:"
164
+ echo " ✅ Platform enforces policy before EVERY tool execution"
165
+ echo " ✅ AI cannot bypass - enforcement happens at platform level"
166
+ echo " ✅ Fail-closed by default - blocks on error"
167
+ echo ""
168
+ echo " Compare this to AGENTS.md (best-effort only):"
169
+ echo " ⚠️ AI follows prompts to call guardrail"
170
+ echo " ⚠️ Can be bypassed via prompt injection"
171
+ echo " ⚠️ Not deterministic"
172
+ echo ""
173
+
174
+ PLUGIN_INSTALLED=false
175
+ if true; then
176
+ # Check if openclaw CLI is available
177
+ if ! command -v openclaw &> /dev/null; then
178
+ echo ""
179
+ echo " ⚠️ OpenClaw CLI not found in PATH"
180
+ echo " The plugin will be configured, but you'll need to install it manually:"
181
+ echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
182
+ echo ""
183
+ read -p " Continue anyway? [Y/n]: " continue_anyway
184
+ continue_anyway=${continue_anyway:-y}
185
+ if [ "$continue_anyway" != "y" ] && [ "$continue_anyway" != "Y" ]; then
186
+ echo " Skipping plugin installation."
187
+ fi
188
+ else
189
+ echo ""
190
+ echo " Installing plugin from: $APORT_PLUGIN_PATH"
191
+ echo " (Using -l to link local directory; OpenClaw accepts only registry or --link for install.)"
192
+ if openclaw plugins install -l "$APORT_PLUGIN_PATH" 2>&1; then
193
+ echo " ✅ Plugin installed successfully"
194
+ PLUGIN_INSTALLED=true
195
+ else
196
+ echo " ⚠️ Plugin installation failed. You can install manually later:"
197
+ echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
198
+ fi
199
+ echo ""
200
+
201
+ # Verify installation
202
+ if [ "$PLUGIN_INSTALLED" = true ]; then
203
+ if openclaw plugins list 2>/dev/null | grep -q "openclaw-aport"; then
204
+ echo " ✅ Plugin verified in plugins list"
205
+ else
206
+ echo " ⚠️ Plugin installed but not showing in 'openclaw plugins list'"
207
+ fi
208
+ echo ""
209
+ export OPENCLAW_CONFIG_DIR="$CONFIG_DIR"
210
+ # Restart only works when gateway is already running; else start in background.
211
+ # Never let gateway start/restart fail the APort installation.
212
+ if openclaw gateway restart 2>/dev/null; then
213
+ echo " ✅ Gateway restarted; plugin is active."
214
+ else
215
+ # Gateway was not running; start it in background so setup completes without blocking.
216
+ # Any failure here must not break APort installation (no exit 1).
217
+ mkdir -p "$CONFIG_DIR/logs" 2>/dev/null || true
218
+ ( OPENCLAW_CONFIG_DIR="$CONFIG_DIR" nohup openclaw gateway start >> "$CONFIG_DIR/logs/gateway.log" 2>&1 & ) 2>/dev/null || true
219
+ sleep 2
220
+ if openclaw gateway probe 2>/dev/null; then
221
+ echo " ✅ Gateway started in background; plugin is active."
222
+ echo " Dashboard: http://127.0.0.1:18789/"
223
+ else
224
+ echo " ⚠️ Gateway start was initiated; it may still be starting."
225
+ echo " If the dashboard is unreachable, run: openclaw doctor"
226
+ echo " Then start in a terminal: openclaw gateway start"
227
+ fi
228
+ fi
229
+ fi
230
+ fi
231
+
232
+ # Generate plugin configuration
233
+ echo ""
234
+ echo " 📝 Generating plugin configuration..."
235
+ CONFIG_YAML="$CONFIG_DIR/config.yaml"
236
+
237
+ # Ask for mode (skip for hosted passport - must use API)
238
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
239
+ PLUGIN_MODE="api"
240
+ echo ""
241
+ echo " Plugin mode: api (required for hosted passport)"
242
+ read -p " APort API URL [https://api.aport.io]: " api_url
243
+ api_url=${api_url:-https://api.aport.io}
244
+ else
245
+ echo ""
246
+ echo " Plugin mode:"
247
+ echo " 1. local - Use local guardrail script (privacy, offline)"
248
+ echo " 2. api - Use APort cloud API (default; for traction and cloud features)"
249
+ echo ""
250
+ read -p " Mode [1=local, 2=api]: " mode_choice
251
+ mode_choice=${mode_choice:-2}
252
+
253
+ if [ "$mode_choice" = "2" ]; then
254
+ PLUGIN_MODE="api"
255
+ echo ""
256
+ read -p " APort API URL [https://api.aport.io]: " api_url
257
+ api_url=${api_url:-https://api.aport.io}
258
+ else
259
+ PLUGIN_MODE="local"
260
+ fi
261
+ fi
262
+
263
+ echo ""
264
+ echo " Strict mode: block tools with no policy mapping (custom skills/ClawHub allowed by default)."
265
+ read -p " Enable strict mode? [y/N]: " strict_mode
266
+ strict_mode=${strict_mode:-n}
267
+ if [ "$strict_mode" = "y" ] || [ "$strict_mode" = "Y" ]; then
268
+ ALLOW_UNMAPPED="false"
269
+ else
270
+ ALLOW_UNMAPPED="true"
271
+ fi
272
+
273
+ # Create or update config.yaml
274
+ if [ ! -f "$CONFIG_YAML" ]; then
275
+ cat > "$CONFIG_YAML" << YAML
276
+ # OpenClaw Configuration
277
+ # Generated by APort setup script
278
+
279
+ plugins:
280
+ enabled: true
281
+ entries:
282
+ openclaw-aport:
283
+ enabled: true
284
+ config:
285
+ # Mode: "local" (use guardrail script) or "api" (use APort cloud API)
286
+ mode: $PLUGIN_MODE
287
+ YAML
288
+
289
+ # Add passport configuration (hosted = agentId only, local = passportFile)
290
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
291
+ cat >> "$CONFIG_YAML" << YAML
292
+
293
+ # Hosted passport (agent_id only - no local file)
294
+ agentId: $HOSTED_AGENT_ID
295
+ YAML
296
+ else
297
+ cat >> "$CONFIG_YAML" << YAML
298
+
299
+ # Passport file location (in aport/ subdir)
300
+ passportFile: $PASSPORT_FILE
301
+
302
+ # For local mode: path to guardrail script
303
+ guardrailScript: $CONFIG_DIR/.skills/aport-guardrail-bash.sh
304
+ YAML
305
+ fi
306
+
307
+ if [ "$PLUGIN_MODE" = "api" ]; then
308
+ cat >> "$CONFIG_YAML" << YAML
309
+
310
+ # For API mode: APort API endpoint
311
+ apiUrl: $api_url
312
+ YAML
313
+ fi
314
+
315
+ cat >> "$CONFIG_YAML" << YAML
316
+
317
+ # Fail-closed: block on error (default: true)
318
+ failClosed: true
319
+
320
+ # Allow unmapped tools (false = strict: block custom skills/ClawHub unless mapped)
321
+ allowUnmappedTools: $ALLOW_UNMAPPED
322
+ YAML
323
+
324
+ echo " ✅ Created $CONFIG_YAML"
325
+ else
326
+ # Config exists - check if plugin section already present
327
+ if grep -q "openclaw-aport:" "$CONFIG_YAML" 2>/dev/null; then
328
+ echo " ✅ Plugin configuration already present in $CONFIG_YAML"
329
+ else
330
+ echo "" >> "$CONFIG_YAML"
331
+ echo "# APort Plugin Configuration" >> "$CONFIG_YAML"
332
+ echo "plugins:" >> "$CONFIG_YAML"
333
+ echo " enabled: true" >> "$CONFIG_YAML"
334
+ echo " entries:" >> "$CONFIG_YAML"
335
+ echo " openclaw-aport:" >> "$CONFIG_YAML"
336
+ echo " enabled: true" >> "$CONFIG_YAML"
337
+ echo " config:" >> "$CONFIG_YAML"
338
+ echo " mode: $PLUGIN_MODE" >> "$CONFIG_YAML"
339
+
340
+ # Add passport configuration (hosted or local)
341
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
342
+ echo " agentId: $HOSTED_AGENT_ID" >> "$CONFIG_YAML"
343
+ else
344
+ echo " passportFile: $PASSPORT_FILE" >> "$CONFIG_YAML"
345
+ echo " guardrailScript: $CONFIG_DIR/.skills/aport-guardrail-bash.sh" >> "$CONFIG_YAML"
346
+ fi
347
+
348
+ if [ "$PLUGIN_MODE" = "api" ]; then
349
+ echo " apiUrl: $api_url" >> "$CONFIG_YAML"
350
+ fi
351
+
352
+ echo " failClosed: true" >> "$CONFIG_YAML"
353
+ echo " allowUnmappedTools: $ALLOW_UNMAPPED" >> "$CONFIG_YAML"
354
+ echo " ✅ Appended plugin configuration to $CONFIG_YAML"
355
+ fi
356
+ fi
357
+
358
+ # So the default config (openclaw.json) has plugin mode/apiUrl - gateway often loads it, not config.yaml
359
+ OPENCLAW_JSON="$CONFIG_DIR/openclaw.json"
360
+ if [ -f "$OPENCLAW_JSON" ] && command -v jq &>/dev/null; then
361
+ api_url_val="${api_url:-https://api.aport.io}"
362
+
363
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
364
+ # Hosted passport config (agentId only)
365
+ CONFIG_JSON=$(jq -n \
366
+ --arg mode "$PLUGIN_MODE" \
367
+ --arg agent_id "$HOSTED_AGENT_ID" \
368
+ --arg api_url "$api_url_val" \
369
+ --arg allow_unmapped "$ALLOW_UNMAPPED" \
370
+ '{ mode: $mode, agentId: $agent_id, failClosed: true, allowUnmappedTools: ($allow_unmapped == "true") } + (if $mode == "api" then { apiUrl: $api_url } else {} end)')
371
+ else
372
+ # Local passport config (passportFile in aport/)
373
+ CONFIG_JSON=$(jq -n \
374
+ --arg mode "$PLUGIN_MODE" \
375
+ --arg pf "$PASSPORT_FILE" \
376
+ --arg gs "$CONFIG_DIR/.skills/aport-guardrail-bash.sh" \
377
+ --arg api_url "$api_url_val" \
378
+ --arg allow_unmapped "$ALLOW_UNMAPPED" \
379
+ '{ mode: $mode, passportFile: $pf, guardrailScript: $gs, failClosed: true, allowUnmappedTools: ($allow_unmapped == "true") } + (if $mode == "api" then { apiUrl: $api_url } else {} end)')
380
+ fi
381
+
382
+ if jq --argjson cfg "$CONFIG_JSON" --arg plugin_path "$APORT_PLUGIN_PATH" '
383
+ .plugins = (.plugins // {}) |
384
+ .plugins.entries = (.plugins.entries // {}) |
385
+ .plugins.entries["openclaw-aport"] = ((.plugins.entries["openclaw-aport"] // {}) | .enabled = true | .config = $cfg) |
386
+ .plugins.load = (.plugins.load // {}) |
387
+ .plugins.load.paths = (
388
+ ((.plugins.load.paths // []) | map(select((type == "string" and contains("/openclaw-aport")) | not))) + [$plugin_path]
389
+ ) |
390
+ .plugins.installs = (.plugins.installs // {}) |
391
+ .plugins.installs["openclaw-aport"] = ((.plugins.installs["openclaw-aport"] // {})
392
+ | .source = "path"
393
+ | .sourcePath = $plugin_path
394
+ | .installPath = $plugin_path)
395
+ ' "$OPENCLAW_JSON" > "$OPENCLAW_JSON.tmp" 2>/dev/null && mv "$OPENCLAW_JSON.tmp" "$OPENCLAW_JSON"; then
396
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
397
+ echo " ✅ Merged plugin config into $OPENCLAW_JSON (mode=$PLUGIN_MODE, hosted passport, allowUnmappedTools=$ALLOW_UNMAPPED)"
398
+ else
399
+ echo " ✅ Merged plugin config into $OPENCLAW_JSON (mode=$PLUGIN_MODE, allowUnmappedTools=$ALLOW_UNMAPPED)"
400
+ fi
401
+ echo " ✅ Canonicalized plugin load path → $APORT_PLUGIN_PATH"
402
+ fi
403
+ fi
404
+
405
+ echo ""
406
+ echo " ✅ Plugin setup complete!"
407
+ echo ""
408
+ else
409
+ echo ""
410
+ echo " Skipping plugin installation."
411
+ echo " ⚠️ Without the plugin, enforcement relies on AGENTS.md (not deterministic)"
412
+ echo ""
413
+ fi
414
+
415
+ # 4. Store repo path and install wrappers
416
+ echo "$REPO_ROOT" > "$CONFIG_DIR/.aport-repo"
417
+ mkdir -p "$CONFIG_DIR/.skills"
418
+
419
+ write_wrapper() {
420
+ local name="$1"
421
+ cat > "$CONFIG_DIR/.skills/$name" << WRAP
422
+ #!/bin/bash
423
+ CONFIG_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")/.." && pwd)"
424
+ APORT_REPO_ROOT="\$(cat "\$CONFIG_DIR/.aport-repo" 2>/dev/null)"
425
+ [ -z "\$APORT_REPO_ROOT" ] && { echo "Error: \$CONFIG_DIR/.aport-repo missing. Re-run bin/openclaw from the guardrails repo." >&2; exit 1; }
426
+ export OPENCLAW_PASSPORT_FILE="\${OPENCLAW_PASSPORT_FILE:-\$CONFIG_DIR/aport/passport.json}"
427
+ export OPENCLAW_DECISION_FILE="\${OPENCLAW_DECISION_FILE:-\$CONFIG_DIR/aport/decision.json}"
428
+ export OPENCLAW_AUDIT_LOG="\${OPENCLAW_AUDIT_LOG:-\$CONFIG_DIR/aport/audit.log}"
429
+ exec "\$APORT_REPO_ROOT/bin/$name" "\$@"
430
+ WRAP
431
+ chmod +x "$CONFIG_DIR/.skills/$name"
432
+ }
433
+
434
+ write_wrapper "aport-guardrail.sh"
435
+ write_wrapper "aport-guardrail-bash.sh"
436
+ write_wrapper "aport-guardrail-api.sh"
437
+ write_wrapper "aport-guardrail-v2.sh"
438
+ write_wrapper "aport-create-passport.sh"
439
+ write_wrapper "aport-status.sh"
440
+
441
+ echo ""
442
+ echo " ✅ Wrappers installed in $CONFIG_DIR/.skills/"
443
+ echo " (They point to this repo and use your config dir for passport/decision/audit.)"
444
+ echo ""
445
+
446
+ # 4b. Normalize passport to OAP spec (spec_version + nested limits) then ensure allowed_commands
447
+ if [ -f "$PASSPORT_FILE" ] && command -v jq &>/dev/null; then
448
+ # 4b1. Ensure spec_version and nested limits (fix old or malformed passports)
449
+ # OAP spec: spec_version "oap/1.0", limits per capability e.g. limits["system.command.execute"]
450
+ NEED_FIX=0
451
+ if [ "$(jq -r '.spec_version // "missing"' "$PASSPORT_FILE")" != "oap/1.0" ]; then
452
+ NEED_FIX=1
453
+ fi
454
+ if jq -e '.limits.allowed_commands' "$PASSPORT_FILE" &>/dev/null; then
455
+ NEED_FIX=1
456
+ fi
457
+ if [ "$NEED_FIX" = "1" ]; then
458
+ echo " 📋 Normalizing passport to OAP spec (spec_version oap/1.0, nested limits)..."
459
+ jq '
460
+ (.spec_version = (.spec_version // "oap/1.0")) |
461
+ (if .limits.allowed_commands then
462
+ .limits["system.command.execute"] = ((.limits["system.command.execute"] // {}) |
463
+ .allowed_commands = (.limits.allowed_commands // ["*"]) |
464
+ .blocked_patterns = (.limits.blocked_patterns // ["rm -rf", "sudo"]) |
465
+ .max_execution_time = (.limits.max_execution_time // 300)) |
466
+ .limits |= del(.allowed_commands, .blocked_patterns, .max_execution_time)
467
+ else . end)
468
+ ' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
469
+ echo " ✅ Passport normalized."
470
+ fi
471
+ echo " 📋 Updating passport allowed_commands (default commands: bash, sh, ls, mkdir, npm, …)..."
472
+ # Default commands so normal exec works; user can add more in passport or via wizard
473
+ DEFAULT_CMDS='["npm","yarn","git","node","pnpm","npx","bash","sh","mkdir","cp","ls","cat","echo","pwd","mv","touch","which","open"]'
474
+ if jq -e '.limits["system.command.execute"]' "$PASSPORT_FILE" &>/dev/null; then
475
+ EXISTING=$(jq -r '.limits["system.command.execute"].allowed_commands // []' "$PASSPORT_FILE")
476
+ # Keep ["*"] if user chose allow-any in wizard; otherwise merge with default list
477
+ if echo "$EXISTING" | jq -e 'index("*") != null' &>/dev/null; then
478
+ MERGED="$EXISTING"
479
+ else
480
+ MERGED=$(jq -n \
481
+ --argjson existing "$EXISTING" \
482
+ --argjson default "$DEFAULT_CMDS" \
483
+ '$existing + $default | unique')
484
+ fi
485
+ jq --argjson merged "$MERGED" '.limits["system.command.execute"].allowed_commands = $merged' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
486
+ else
487
+ # New nested block: default to ["*"] per README (blocked_patterns still apply)
488
+ MERGED='["*"]'
489
+ jq --argjson merged "$MERGED" \
490
+ '.limits["system.command.execute"] = ((.limits["system.command.execute"] // {}) | .allowed_commands = $merged | .blocked_patterns = (.blocked_patterns // ["rm -rf", "sudo"]) | .max_execution_time = (.max_execution_time // 300))' "$PASSPORT_FILE" > "$PASSPORT_FILE.tmp" && mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
491
+ fi
492
+ echo " ✅ Passport updated: default commands are in allowed_commands."
493
+ echo ""
494
+ # Validate: run guardrail via same path OpenClaw will use; fail fast if denied
495
+ GUARDRAIL_SCRIPT="$CONFIG_DIR/.skills/aport-guardrail-bash.sh"
496
+ echo " 🔍 Self-check: running guardrail (same path OpenClaw uses)..."
497
+ if ! OPENCLAW_PASSPORT_FILE="$PASSPORT_FILE" "$GUARDRAIL_SCRIPT" system.command.execute '{"command":"bash"}' 2>/dev/null; then
498
+ echo ""
499
+ echo " ❌ Setup incomplete: guardrail self-check was DENIED."
500
+ echo " The passport may still be missing required commands (e.g. node)."
501
+ echo " Edit $PASSPORT_FILE and add needed commands to limits.system.command.execute.allowed_commands,"
502
+ echo " or re-run this script and choose to overwrite the passport in the wizard."
503
+ echo ""
504
+ exit 1
505
+ fi
506
+ echo " ✅ Guardrail self-check passed (ALLOW)."
507
+ echo ""
508
+ else
509
+ if [ ! -f "$PASSPORT_FILE" ]; then
510
+ echo " ⚠️ No passport found; skip allowlist update. Run the wizard first or re-run with an existing passport."
511
+ else
512
+ echo " ⚠️ jq not found; cannot auto-update passport allowed_commands. Add needed commands to limits.system.command.execute.allowed_commands manually."
513
+ fi
514
+ echo ""
515
+ fi
516
+
517
+ # 3b. Install APort skill so OpenClaw loads it (managed skill: ~/.openclaw/skills)
518
+ # OpenClaw loads from: workspace/skills, then ~/.openclaw/skills, then bundled
519
+ # See https://docs.openclaw.ai/tools/skills
520
+ # Copy from repo so installed skill matches skills/aport-agent-guardrail/SKILL.md (single source of truth)
521
+ SKILL_DIR="$CONFIG_DIR/skills/aport-guardrail"
522
+ REPO_SKILL="$REPO_ROOT/skills/aport-agent-guardrail/SKILL.md"
523
+ mkdir -p "$SKILL_DIR"
524
+ if [ -f "$REPO_SKILL" ]; then
525
+ cp "$REPO_SKILL" "$SKILL_DIR/SKILL.md"
526
+ else
527
+ echo " ⚠️ Repo SKILL.md not found at $REPO_SKILL; skipping skill install." >&2
528
+ fi
529
+ echo "✅ APort skill installed in $CONFIG_DIR/skills/aport-guardrail/"
530
+ echo " OpenClaw will load it (managed skill). Use it before effectful actions."
531
+ echo
532
+
533
+ # 4. Tool → policy mapping (how OpenClaw knows which policy pack)
534
+ echo "📋 Tool → policy pack mapping"
535
+ echo " OpenClaw (or your code) calls the guardrail with a tool name and context."
536
+ echo " The guardrail maps that to a policy pack in external/aport-policies:"
537
+ echo
538
+ cat << 'TABLE'
539
+ Tool name (examples) → Policy pack
540
+ ------------------------------------------------
541
+ git.create_pr, git.merge, git.* → code.repository.merge.v1
542
+ exec.run, system.command.* → system.command.execute.v1
543
+ message.send, messaging.* → messaging.message.send.v1
544
+ mcp.tool.*, mcp.* → mcp.tool.execute.v1
545
+ agent.session.*, session.* → agent.session.create.v1
546
+ agent.tool.*, tool.register → agent.tool.register.v1
547
+ payment.refund, finance.* → finance.payment.refund.v1
548
+ payment.charge → finance.payment.charge.v1
549
+ database.write, data.export → data.export.create.v1
550
+ TABLE
551
+ echo " Full list: docs/TOOL_POLICY_MAPPING.md"
552
+ echo
553
+
554
+ # 5. Enforcement summary
555
+ echo ""
556
+ echo " 🛡️ Enforcement Summary"
557
+ echo " ═══════════════════════"
558
+ echo ""
559
+
560
+ if [ "$PLUGIN_INSTALLED" = true ]; then
561
+ echo " ✅ DETERMINISTIC ENFORCEMENT: Plugin installed"
562
+ echo ""
563
+ echo " The APort OpenClaw plugin enforces policies at the platform level."
564
+ echo " Every tool execution is checked BEFORE running - AI cannot bypass."
565
+ echo ""
566
+ echo " Config: $CONFIG_DIR/config.yaml"
567
+ echo " Mode: $PLUGIN_MODE"
568
+ echo ""
569
+ echo " To verify plugin is active:"
570
+ echo " openclaw plugins list | grep openclaw-aport"
571
+ echo ""
572
+ else
573
+ echo " ⚠️ BEST-EFFORT ENFORCEMENT: Using AGENTS.md only"
574
+ echo ""
575
+ echo " Without the plugin, enforcement relies on the AI following prompts"
576
+ echo " in workspace/AGENTS.md to call the guardrail before actions."
577
+ echo ""
578
+ echo " This is NOT deterministic and can be bypassed."
579
+ echo ""
580
+ echo " To install the plugin later, run:"
581
+ echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
582
+ echo ""
583
+ fi
584
+
585
+ # 6. Next steps (paths use config dir above)
586
+ echo "📝 Next steps"
587
+ echo "-------------"
588
+ echo " (Paths below are for this config dir. Re-run setup if you use a different one.)"
589
+ echo ""
590
+ echo " 1. Smoke test – run this (one line); expect Exit: 0 = ALLOW:"
591
+ echo " (copy the line below as-is; it has your config path)"
592
+ echo " $CONFIG_DIR/.skills/aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'; echo \"Exit: \$? (0=ALLOW, 1=DENY)\""
593
+ echo ""
594
+
595
+ if [ "$PLUGIN_INSTALLED" = true ]; then
596
+ echo " 2. Start the gateway (run in a terminal and keep it open):"
597
+ echo " openclaw gateway start"
598
+ echo " (If the gateway was already running, use: openclaw gateway restart)"
599
+ echo ""
600
+ echo " 3. Test plugin enforcement by trying a blocked action"
601
+ echo " (The plugin will automatically block based on passport limits)"
602
+ echo ""
603
+ else
604
+ echo " 2. In OpenClaw: point skills to the guardrail script:"
605
+ echo " $CONFIG_DIR/.skills/aport-guardrail.sh"
606
+ echo ""
607
+ echo " 3. Optional – use API (self-hosted or cloud):"
608
+ echo " export APORT_API_URL=\"https://api.aport.io\""
609
+ echo " $CONFIG_DIR/.skills/aport-guardrail-api.sh system.command.execute '{\"command\":\"node --version\"}'"
610
+ echo ""
611
+ fi
612
+ # 7. Ensure workspace has APort rule in AGENTS.md (auto-install; no manual merge)
613
+ WORKSPACE_DIR="$CONFIG_DIR/workspace"
614
+ AGENTS="$WORKSPACE_DIR/AGENTS.md"
615
+ mkdir -p "$WORKSPACE_DIR"
616
+ APORT_MARKER="Pre-Action Authorization (APort Guardrails)"
617
+ if [ ! -f "$AGENTS" ]; then
618
+ cp "$REPO_ROOT/docs/AGENTS.md.example" "$AGENTS"
619
+ echo " ✅ Created $AGENTS with APort pre-action rule."
620
+ else
621
+ if grep -q "$APORT_MARKER" "$AGENTS" 2>/dev/null; then
622
+ echo " ✅ APort rule already present in $AGENTS"
623
+ else
624
+ echo "" >> "$AGENTS"
625
+ echo "---" >> "$AGENTS"
626
+ cat "$REPO_ROOT/docs/AGENTS.md.example" >> "$AGENTS"
627
+ echo " ✅ Appended APort pre-action rule to $AGENTS"
628
+ fi
629
+ fi
630
+ echo ""
631
+
632
+ if [ "$PLUGIN_INSTALLED" = true ]; then
633
+ echo " Note: AGENTS.md is installed for reference, but the plugin provides"
634
+ echo " deterministic enforcement (AI cannot bypass). AGENTS.md is optional."
635
+ else
636
+ echo " Note: Without the plugin, AGENTS.md is your only enforcement layer."
637
+ echo " This relies on the AI following instructions (not deterministic)."
638
+ fi
639
+
640
+ echo ""
641
+ echo " View status: $CONFIG_DIR/.skills/aport-status.sh"
642
+ echo ""
643
+ if [ "$USE_HOSTED_PASSPORT" = true ]; then
644
+ echo " Running smoke test (hosted passport): API guardrail with agent_id..."
645
+ if APORT_AGENT_ID="$HOSTED_AGENT_ID" APORT_API_URL="${api_url:-https://api.aport.io}" "$CONFIG_DIR/.skills/aport-guardrail-api.sh" system.command.execute '{"command":"node --version"}' 2>/dev/null; then
646
+ echo " ✅ Smoke test passed (hosted passport, exit 0 = ALLOW)"
647
+ else
648
+ echo " ⚠️ Smoke test skipped or failed (hosted). Ensure API is reachable; plugin will use agentId on each tool call."
649
+ fi
650
+ else
651
+ echo " Running smoke test: $CONFIG_DIR/.skills/aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'"
652
+ if "$CONFIG_DIR/.skills/aport-guardrail.sh" system.command.execute '{"command":"node --version"}'; then
653
+ echo " ✅ Smoke test passed (exit 0 = ALLOW)"
654
+ else
655
+ echo " ❌ Smoke test denied or failed (exit 1). Check passport/limits or run: $CONFIG_DIR/.skills/aport-status.sh"
656
+ fi
657
+ fi
658
+ echo ""
659
+ echo " Done. OpenClaw config: $CONFIG_DIR"
660
+ echo ""