@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,444 @@
1
+ #!/bin/bash
2
+ # aport-create-passport.sh
3
+ # Interactive passport creation wizard (OAP v1.0).
4
+ # Use for any framework; for OpenClaw with a custom config directory, run ./bin/openclaw instead.
5
+ #
6
+ # Usage: ./aport-create-passport.sh [--output FILE] [--non-interactive] [--framework=NAME]
7
+ # --output FILE Write passport to FILE (overrides framework default).
8
+ # --non-interactive Use defaults only; no prompts (for CI/tests). Use --output or set APORT_FRAMEWORK for default path.
9
+ # --framework=NAME Default passport path for this framework (cursor, openclaw, langchain, crewai, n8n).
10
+ #
11
+ # In interactive mode the first question is "Passport file path [default]:"; you can press Enter for the
12
+ # framework default or type a different path. In non-interactive mode, --output always overrides; if not
13
+ # given and APORT_FRAMEWORK is set, the framework default path is used.
14
+
15
+ set -e
16
+
17
+ PASSPORT_FILE=""
18
+ NON_INTERACTIVE=""
19
+ # Parse --output, --non-interactive, --framework=
20
+ while [ $# -gt 0 ]; do
21
+ case "$1" in
22
+ --output) [ -n "${2:-}" ] && PASSPORT_FILE="$2" && shift ;;
23
+ --non-interactive) NON_INTERACTIVE=1 ;;
24
+ --framework=*) APORT_FRAMEWORK="${1#--framework=}" ;;
25
+ esac
26
+ shift
27
+ done
28
+
29
+ # Repo root and lib for get_default_passport_path
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
31
+ LIB_DIR="$SCRIPT_DIR/bin/lib"
32
+ if [ -f "$LIB_DIR/config.sh" ]; then
33
+ # shellcheck source=bin/lib/config.sh
34
+ . "$LIB_DIR/config.sh"
35
+ fi
36
+
37
+ # Default passport path: --output wins; else framework-specific default; else OpenClaw legacy path
38
+ if [ -z "$PASSPORT_FILE" ]; then
39
+ if [ -n "${APORT_FRAMEWORK:-}" ] && type get_default_passport_path &> /dev/null; then
40
+ PASSPORT_FILE="$(get_default_passport_path "$APORT_FRAMEWORK")"
41
+ else
42
+ PASSPORT_FILE="$HOME/.openclaw/aport/passport.json"
43
+ fi
44
+ fi
45
+ PASSPORT_FILE="${PASSPORT_FILE/#\~/$HOME}"
46
+
47
+ # Config dir: from env, or derived from passport path (e.g. .../aport/passport.json -> parent of aport)
48
+ if [ -n "${OPENCLAW_CONFIG_DIR:-}" ]; then
49
+ CONFIG_DIR="${OPENCLAW_CONFIG_DIR/#\~/$HOME}"
50
+ else
51
+ CONFIG_DIR="$(dirname "$PASSPORT_FILE")"
52
+ case "$PASSPORT_FILE" in
53
+ */aport/passport.json) CONFIG_DIR="$(dirname "$CONFIG_DIR")" ;;
54
+ esac
55
+ CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
56
+ fi
57
+ IDENTITY_FILE="$CONFIG_DIR/workspace/IDENTITY.md"
58
+
59
+ # Repo root for external/aport-spec submodule
60
+ SPEC_SCHEMA="$SCRIPT_DIR/external/aport-spec/oap/passport-schema.json"
61
+ # OAP spec: spec_version "oap/1.0", limits nested per capability (e.g. limits["system.command.execute"]).
62
+ # Local creation has no KYC/assurance proof → L0.
63
+ if [ -f "$SPEC_SCHEMA" ] && command -v jq &> /dev/null; then
64
+ DEFAULT_SPEC_VERSION=$(jq -r '.properties.spec_version.const // "oap/1.0"' "$SPEC_SCHEMA")
65
+ else
66
+ DEFAULT_SPEC_VERSION="oap/1.0"
67
+ fi
68
+ [ -z "$DEFAULT_SPEC_VERSION" ] || [ "$DEFAULT_SPEC_VERSION" = "null" ] && DEFAULT_SPEC_VERSION="oap/1.0"
69
+ DEFAULT_ASSURANCE_LEVEL="L0"
70
+
71
+ # --- Smart defaults ---
72
+ get_default_email() {
73
+ local e
74
+ e=$(git config user.email 2> /dev/null)
75
+ if [ -n "$e" ]; then
76
+ echo "$e"
77
+ return
78
+ fi
79
+ if command -v gh &> /dev/null; then
80
+ e=$(gh api user --jq '.email // .login + "@users.noreply.github.com"' 2> /dev/null)
81
+ [ -n "$e" ] && echo "$e"
82
+ fi
83
+ }
84
+
85
+ get_identity_name() {
86
+ [ ! -f "$IDENTITY_FILE" ] && return
87
+ # OpenClaw IDENTITY.md: "Name: ..." or "**Name**: ..."
88
+ grep -iE '^\s*\*\{0,2\}Name\*\{0,2\}\s*:' "$IDENTITY_FILE" 2> /dev/null | head -1 | sed 's/^[^:]*:[[:space:]]*//; s/^[[:space:]]*//; s/[[:space:]]*$//' | tr -d '\n' | head -c 200
89
+ }
90
+
91
+ get_identity_description() {
92
+ [ ! -f "$IDENTITY_FILE" ] && return
93
+ # Prefer Vibe: or Description: line (case-insensitive)
94
+ local v
95
+ v=$(grep -iE '^\s*\*\{0,2\}(Vibe|Description)\*\{0,2\}\s*:' "$IDENTITY_FILE" 2> /dev/null | head -1 | sed 's/^[^:]*:[[:space:]]*//; s/^[[:space:]]*//; s/[[:space:]]*$//' | tr -d '\n')
96
+ if [ -n "$v" ]; then
97
+ echo "$v" | head -c 300
98
+ return
99
+ fi
100
+ awk '/^#/ { next } /^[[:space:]]*$/ { next } { print; exit }' "$IDENTITY_FILE" 2> /dev/null | head -c 300
101
+ }
102
+
103
+ # Avoid set -e exit when git/gh unavailable (e.g. CI with no git user.email)
104
+ DEFAULT_EMAIL=$(get_default_email) || true
105
+ DEFAULT_EMAIL=${DEFAULT_EMAIL:-"user@example.com"}
106
+ DEFAULT_OWNER_TYPE="user"
107
+ DEFAULT_AGENT_NAME=$(get_identity_name) || true
108
+ DEFAULT_AGENT_NAME=${DEFAULT_AGENT_NAME:-"OpenClaw Agent"}
109
+ DEFAULT_AGENT_DESC=$(get_identity_description) || true
110
+ DEFAULT_AGENT_DESC=${DEFAULT_AGENT_DESC:-"Local OpenClaw AI agent with APort guardrails"}
111
+
112
+ if [ -n "$NON_INTERACTIVE" ]; then
113
+ # CI/tests: use defaults, no prompts. Use --output or APORT_FRAMEWORK for default path. Match interactive defaults (README: messaging out of the box).
114
+ owner_id="$DEFAULT_EMAIL"
115
+ owner_type="$DEFAULT_OWNER_TYPE"
116
+ agent_name="$DEFAULT_AGENT_NAME"
117
+ agent_description="$DEFAULT_AGENT_DESC"
118
+ pr_cap=y
119
+ exec_cap=y
120
+ msg_cap=y
121
+ data_cap=n
122
+ max_pr_size=500
123
+ max_prs_per_day=10
124
+ max_msgs_per_day=100
125
+ allowed_repos_input="*"
126
+ exec_allow_scope="*"
127
+ should_expire=n
128
+ never_expires="true"
129
+ expires_at=""
130
+ if command -v uuidgen &> /dev/null; then
131
+ passport_id=$(uuidgen)
132
+ else
133
+ passport_id="local-$(date +%s)-$(openssl rand -hex 4 2> /dev/null || echo $((RANDOM)))"
134
+ fi
135
+ else
136
+ echo ""
137
+ echo " 🛂 APort Passport Creation Wizard"
138
+ echo " ══════════════════════════════════"
139
+ echo ""
140
+ echo " Creates an Open Agent Passport (OAP v1.0) for your agent."
141
+ echo " (Press Enter to use the default when shown in brackets.)"
142
+ echo ""
143
+ # First question: where to store the passport (default = framework-specific or OpenClaw path)
144
+ read -p " Passport file path [$PASSPORT_FILE]: " passport_input
145
+ if [ -n "$passport_input" ]; then
146
+ PASSPORT_FILE="${passport_input/#\~/$HOME}"
147
+ fi
148
+ mkdir -p "$(dirname "$PASSPORT_FILE")"
149
+ CONFIG_DIR="$(dirname "$PASSPORT_FILE")"
150
+ # If passport is in .../aport/passport.json, config dir is parent of aport
151
+ case "$PASSPORT_FILE" in
152
+ */aport/passport.json) CONFIG_DIR="$(dirname "$(dirname "$PASSPORT_FILE")")" ;;
153
+ esac
154
+ CONFIG_DIR="${CONFIG_DIR/#\~/$HOME}"
155
+ IDENTITY_FILE="$CONFIG_DIR/workspace/IDENTITY.md"
156
+ echo ""
157
+
158
+ # Check if passport already exists
159
+ if [ -f "$PASSPORT_FILE" ]; then
160
+ read -p " Passport already exists. Overwrite? [y/N]: " overwrite
161
+ if [ "$overwrite" != "y" ] && [ "$overwrite" != "Y" ]; then
162
+ echo " Aborting. Re-run and choose a different path, or use --output to specify a file."
163
+ exit 1
164
+ fi
165
+ echo ""
166
+ fi
167
+
168
+ # Collect user info
169
+ echo " 📋 Owner & agent"
170
+ echo " ─────────────────"
171
+ echo " (Press Enter to use the default when shown in brackets.)"
172
+ echo ""
173
+ read -p " Your email or ID [$DEFAULT_EMAIL]: " owner_id
174
+ owner_id=${owner_id:-"$DEFAULT_EMAIL"}
175
+
176
+ read -p " Owner type (user/org) [$DEFAULT_OWNER_TYPE]: " owner_type
177
+ owner_type=${owner_type:-"$DEFAULT_OWNER_TYPE"}
178
+
179
+ read -p " Agent name [$DEFAULT_AGENT_NAME]: " agent_name
180
+ agent_name=${agent_name:-"$DEFAULT_AGENT_NAME"}
181
+
182
+ read -p " Agent description [$DEFAULT_AGENT_DESC]: " agent_description
183
+ agent_description=${agent_description:-"$DEFAULT_AGENT_DESC"}
184
+
185
+ echo ""
186
+
187
+ # Choose capabilities
188
+ echo " 🔐 Capabilities"
189
+ echo " ───────────────"
190
+ echo " Choose what your agent can do (y/n). Defaults: PRs, exec, and messaging = yes (matches README/docs); data export = no."
191
+ echo ""
192
+ read -p " • Create and merge pull requests? [Y/n]: " pr_cap
193
+ pr_cap=${pr_cap:-y}
194
+
195
+ read -p " • Execute system commands (npm, git, etc.)? [Y/n]: " exec_cap
196
+ exec_cap=${exec_cap:-y}
197
+
198
+ read -p " • Send messages (email, SMS, etc.)? [Y/n]: " msg_cap
199
+ msg_cap=${msg_cap:-y}
200
+
201
+ read -p " • Export data (database, files, etc.)? [y/N]: " data_cap
202
+ data_cap=${data_cap:-n}
203
+
204
+ echo ""
205
+
206
+ # Configure limits
207
+ echo " ⚙️ Limits"
208
+ echo " ─────────"
209
+
210
+ if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
211
+ read -p " Max PR size (files) [500]: " max_pr_size
212
+ max_pr_size=${max_pr_size:-500}
213
+
214
+ read -p " Max PRs per day [10]: " max_prs_per_day
215
+ max_prs_per_day=${max_prs_per_day:-10}
216
+
217
+ read -p " Allowed repos (comma-separated, * for all) [*]: " allowed_repos_input
218
+ allowed_repos_input=${allowed_repos_input:-"*"}
219
+ fi
220
+
221
+ if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
222
+ echo " Shell commands: default is allow any (*); blocked patterns (rm -rf, sudo, etc.) still apply."
223
+ echo " Press Enter or type * for allow any; type 'list' for a fixed list (ls, mkdir, npm, …)."
224
+ read -p " [Enter or *=allow any / list=fixed list]: " exec_allow_scope
225
+ exec_allow_scope=${exec_allow_scope:-*}
226
+ fi
227
+
228
+ if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
229
+ read -p " Max messages per day [100]: " max_msgs_per_day
230
+ max_msgs_per_day=${max_msgs_per_day:-100}
231
+ fi
232
+
233
+ if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
234
+ read -p " Max export rows [10000]: " max_export_rows
235
+ max_export_rows=${max_export_rows:-10000}
236
+
237
+ read -p " Allow PII export? [y/N]: " allow_pii
238
+ allow_pii=${allow_pii:-n}
239
+ if [ "$allow_pii" = "y" ] || [ "$allow_pii" = "Y" ]; then
240
+ allow_pii_bool="true"
241
+ else
242
+ allow_pii_bool="false"
243
+ fi
244
+ fi
245
+
246
+ echo ""
247
+
248
+ # Generate passport ID
249
+ if command -v uuidgen &> /dev/null; then
250
+ passport_id=$(uuidgen)
251
+ else
252
+ passport_id="local-$(date +%s)-$(openssl rand -hex 4 2> /dev/null || echo $((RANDOM)))"
253
+ fi
254
+
255
+ # Ask about expiration
256
+ echo " 📅 Expiration"
257
+ echo " ─────────────"
258
+ read -p " Should this passport expire? [y/N]: " should_expire
259
+ should_expire=${should_expire:-n}
260
+
261
+ if [ "$should_expire" = "y" ] || [ "$should_expire" = "Y" ]; then
262
+ read -p " Days until expiration [30]: " expire_days
263
+ expire_days=${expire_days:-30}
264
+
265
+ # Calculate expiration date
266
+ if date -v+${expire_days}d &> /dev/null 2>&1; then
267
+ # BSD date (macOS)
268
+ expires_at=$(date -u -v+${expire_days}d +%Y-%m-%dT%H:%M:%SZ)
269
+ else
270
+ # GNU date (Linux)
271
+ expires_at=$(date -u -d "+${expire_days} days" +%Y-%m-%dT%H:%M:%SZ)
272
+ fi
273
+ never_expires="false"
274
+ else
275
+ expires_at=""
276
+ never_expires="true"
277
+ fi
278
+
279
+ fi
280
+ # End of interactive branch; non-interactive already set never_expires/expires_at above.
281
+
282
+ # Build capabilities array
283
+ capabilities_json="["
284
+ if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
285
+ capabilities_json="$capabilities_json{\"id\": \"repo.pr.create\"},"
286
+ capabilities_json="$capabilities_json{\"id\": \"repo.merge\"},"
287
+ fi
288
+ if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
289
+ capabilities_json="$capabilities_json{\"id\": \"system.command.execute\"},"
290
+ fi
291
+ # Capability IDs must match agent-passport policy requires_capabilities (e.g. messaging.message.send.v1 → messaging.send)
292
+ if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
293
+ capabilities_json="$capabilities_json{\"id\": \"messaging.send\"},"
294
+ fi
295
+ if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
296
+ capabilities_json="$capabilities_json{\"id\": \"data.export\"},"
297
+ fi
298
+ # Remove trailing comma
299
+ capabilities_json="${capabilities_json%,}]"
300
+
301
+ # Build limits object
302
+ limits_json="{"
303
+
304
+ if [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ]; then
305
+ # Parse allowed repos
306
+ IFS=',' read -ra REPOS <<< "$allowed_repos_input"
307
+ allowed_repos_json="["
308
+ for repo in "${REPOS[@]}"; do
309
+ repo=$(echo "$repo" | xargs) # trim whitespace
310
+ allowed_repos_json="$allowed_repos_json\"$repo\","
311
+ done
312
+ allowed_repos_json="${allowed_repos_json%,}]"
313
+
314
+ limits_json="$limits_json\"code.repository.merge\": {\"max_prs_per_day\": $max_prs_per_day, \"max_merges_per_day\": 5, \"max_pr_size_kb\": $max_pr_size, \"allowed_repos\": $allowed_repos_json, \"allowed_base_branches\": [\"*\"], \"require_review\": false},"
315
+ fi
316
+
317
+ if [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ]; then
318
+ exec_allow_lower=$(echo "$exec_allow_scope" | tr 'A-Z' 'a-z')
319
+ if [ "$exec_allow_lower" = "list" ]; then
320
+ allowed_commands_json="[\"npm\", \"yarn\", \"git\", \"node\", \"pnpm\", \"npx\", \"bash\", \"sh\", \"mkdir\", \"cp\", \"ls\", \"cat\", \"echo\", \"pwd\", \"mv\", \"touch\", \"which\", \"open\"]"
321
+ else
322
+ # default: allow any (*); blocked_patterns still apply
323
+ allowed_commands_json="[\"*\"]"
324
+ fi
325
+ limits_json="$limits_json\"system.command.execute\": {\"allowed_commands\": $allowed_commands_json, \"blocked_patterns\": [\"rm -rf\", \"sudo\", \"chmod 777\", \"dd if=\", \"mkfs\"], \"max_execution_time\": 300},"
326
+ fi
327
+
328
+ if [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ]; then
329
+ # API/verifier expect flat keys at top level of limits (not nested under messaging.message.send)
330
+ limits_json="$limits_json\"msgs_per_min\": 5, \"msgs_per_day\": $max_msgs_per_day, \"allowed_recipients\": [\"*\"], \"approval_required\": false,"
331
+ fi
332
+
333
+ if [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ]; then
334
+ limits_json="$limits_json\"data.export\": {\"max_rows\": $max_export_rows, \"allow_pii\": $allow_pii_bool, \"allowed_collections\": [\"*\"]},"
335
+ fi
336
+
337
+ # Remove trailing comma
338
+ limits_json="${limits_json%,}}"
339
+
340
+ # Build metadata object using jq
341
+ metadata_json=$(jq -n \
342
+ --arg name "$agent_name" \
343
+ --arg desc "$agent_description" \
344
+ '{
345
+ name: $name,
346
+ description: $desc,
347
+ version: "1.0.0",
348
+ created_by: "aport-create-passport.sh"
349
+ }')
350
+
351
+ # Create passport JSON (OAP v1.0 compliant)
352
+ mkdir -p "$(dirname "$PASSPORT_FILE")"
353
+ current_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
354
+
355
+ # API expects both agent_id and owner_id; use passport_id as agent_id for local passports
356
+ if [ "$never_expires" = "true" ]; then
357
+ # Passport without expiration (assurance_level and spec_version from external/aport-spec)
358
+ cat > "$PASSPORT_FILE.tmp" << EOF
359
+ {
360
+ "passport_id": "$passport_id",
361
+ "agent_id": "$passport_id",
362
+ "kind": "template",
363
+ "spec_version": "$DEFAULT_SPEC_VERSION",
364
+ "owner_id": "$owner_id",
365
+ "owner_type": "$owner_type",
366
+ "assurance_level": "$DEFAULT_ASSURANCE_LEVEL",
367
+ "status": "active",
368
+ "capabilities": $capabilities_json,
369
+ "limits": $limits_json,
370
+ "regions": ["US", "CA"],
371
+ "metadata": $metadata_json,
372
+ "never_expires": true,
373
+ "created_at": "$current_timestamp",
374
+ "updated_at": "$current_timestamp",
375
+ "version": "1.0.0"
376
+ }
377
+ EOF
378
+ else
379
+ # Passport with expiration (assurance_level and spec_version from external/aport-spec)
380
+ cat > "$PASSPORT_FILE.tmp" << EOF
381
+ {
382
+ "passport_id": "$passport_id",
383
+ "agent_id": "$passport_id",
384
+ "kind": "template",
385
+ "spec_version": "$DEFAULT_SPEC_VERSION",
386
+ "owner_id": "$owner_id",
387
+ "owner_type": "$owner_type",
388
+ "assurance_level": "$DEFAULT_ASSURANCE_LEVEL",
389
+ "status": "active",
390
+ "capabilities": $capabilities_json,
391
+ "limits": $limits_json,
392
+ "regions": ["US", "CA"],
393
+ "metadata": $metadata_json,
394
+ "expires_at": "$expires_at",
395
+ "created_at": "$current_timestamp",
396
+ "updated_at": "$current_timestamp",
397
+ "version": "1.0.0"
398
+ }
399
+ EOF
400
+ fi
401
+
402
+ # Format JSON with jq if available
403
+ if command -v jq &> /dev/null; then
404
+ jq . "$PASSPORT_FILE.tmp" > "$PASSPORT_FILE"
405
+ rm "$PASSPORT_FILE.tmp"
406
+ else
407
+ mv "$PASSPORT_FILE.tmp" "$PASSPORT_FILE"
408
+ echo " ⚠️ jq not found; passport JSON not pretty-printed."
409
+ fi
410
+ # Restrict permissions: passport holds allowlists and identity
411
+ chmod 600 "$PASSPORT_FILE" 2> /dev/null || true
412
+
413
+ echo ""
414
+ echo " ✅ Passport created successfully!"
415
+ echo ""
416
+ echo " 📋 Summary"
417
+ echo " ──────────"
418
+ echo " Location: $PASSPORT_FILE"
419
+ echo " Passport ID: $passport_id"
420
+ echo " Owner: $owner_id ($owner_type)"
421
+ echo " Agent: $agent_name"
422
+ echo " Status: active"
423
+ echo " Spec: oap/1.0"
424
+ if [ "$never_expires" = "true" ]; then
425
+ echo " Expiration: Never"
426
+ else
427
+ echo " Expires: $expires_at"
428
+ fi
429
+ echo ""
430
+ echo " 🔐 Capabilities:"
431
+ [ "$pr_cap" = "y" ] || [ "$pr_cap" = "Y" ] && echo " • Create and merge pull requests"
432
+ [ "$exec_cap" = "y" ] || [ "$exec_cap" = "Y" ] && echo " • Execute system commands"
433
+ [ "$msg_cap" = "y" ] || [ "$msg_cap" = "Y" ] && echo " • Send messages"
434
+ [ "$data_cap" = "y" ] || [ "$data_cap" = "Y" ] && echo " • Export data"
435
+ echo ""
436
+ echo " 📝 Next steps:"
437
+ echo " • Review limits: vim $PASSPORT_FILE"
438
+ echo " • Test guardrail: aport-guardrail.sh system.command.execute '{\"command\":\"node --version\"}'; echo \"Exit: \$? (0=ALLOW, 1=DENY)\""
439
+ echo " • View status: aport-status.sh"
440
+ echo ""
441
+ if [ "$never_expires" != "true" ]; then
442
+ echo " 💡 Passport expires in $expire_days days. Renew before then."
443
+ fi
444
+ echo ""
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # APort Cursor/Copilot/Claude Code hook: read JSON from stdin, call guardrail, return allow/deny; exit 2 = block.
3
+ # Compatible with Cursor (beforeShellExecution, preToolUse), VS Code Copilot, and Claude Code.
4
+ # Input: JSON with "command" and/or "tool"/"name"/"input" (host-dependent). We map to system.command.execute.
5
+ # Output: JSON with "permission": "allow"|"deny" (Cursor) and "allowed": true|false; optional "agentMessage"/"reason".
6
+ # Exit: 0 = allow, 2 = block (deny). Other exits = hook error (host may proceed or fail-open).
7
+
8
+ set -e
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
12
+ GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
13
+
14
+ # Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/langchain, etc. when OPENCLAW_CONFIG_DIR not set
15
+ # shellcheck source=bin/aport-resolve-paths.sh
16
+ . "$ROOT_DIR/bin/aport-resolve-paths.sh"
17
+
18
+ # Read stdin (single JSON object; Cursor sends one payload per invocation)
19
+ INPUT=""
20
+ if [ -t 0 ]; then
21
+ # No stdin (e.g. manual test): treat as allow to avoid blocking
22
+ INPUT='{}'
23
+ else
24
+ INPUT="$(cat)"
25
+ fi
26
+
27
+ # Empty or invalid JSON -> allow with warning (fail-open for bad input)
28
+ if [ -z "$INPUT" ]; then
29
+ echo '{"permission":"allow","allowed":true,"agentMessage":"APort: no input received"}'
30
+ exit 0
31
+ fi
32
+
33
+ # Parse and normalize to tool + context for guardrail
34
+ # Cursor beforeShellExecution: { "command": "..." }
35
+ # preToolUse / Copilot: { "tool": "runTerminalCommand", "input": { "command": "..." } } or similar
36
+ TOOL_NAME="exec.run"
37
+ CONTEXT_JSON="{}"
38
+ if command -v jq &> /dev/null; then
39
+ CMD=$(echo "$INPUT" | jq -r '.command // .input.command // .input.cmd // .args[0] // ""')
40
+ if [ -n "$CMD" ] && [ "$CMD" != "null" ]; then
41
+ CONTEXT_JSON=$(echo "$INPUT" | jq -c '{command: (.command // .input.command // .input.cmd), args: (.args // .input.args // [])}' 2> /dev/null || echo "{}")
42
+ if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
43
+ CONTEXT_JSON=$(jq -n -c --arg cmd "$CMD" '{command: $cmd}')
44
+ fi
45
+ fi
46
+ # If no command found, still pass through; guardrail may deny unknown
47
+ if [ -z "$CONTEXT_JSON" ] || [ "$CONTEXT_JSON" = "null" ]; then
48
+ CONTEXT_JSON="$INPUT"
49
+ fi
50
+ else
51
+ CONTEXT_JSON="$INPUT"
52
+ fi
53
+
54
+ # Call existing bash guardrail: exit 0 = allow, exit 1 = deny (forward config for subprocess)
55
+ set +e
56
+ OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" OPENCLAW_PASSPORT_FILE="${OPENCLAW_PASSPORT_FILE:-}" OPENCLAW_DECISION_FILE="${OPENCLAW_DECISION_FILE:-}" "$GUARDRAIL" "$TOOL_NAME" "$CONTEXT_JSON" 2> /dev/null
57
+ GUARDRAIL_EXIT=$?
58
+ set -e
59
+
60
+ if [ "$GUARDRAIL_EXIT" -eq 0 ]; then
61
+ echo '{"permission":"allow","allowed":true}'
62
+ exit 0
63
+ fi
64
+
65
+ # Deny: output reason from decision file if available (guardrail writes decision before exit 1)
66
+ REASON="Policy denied this action."
67
+ if [ -n "${OPENCLAW_DECISION_FILE:-}" ] && [ -f "$OPENCLAW_DECISION_FILE" ] && command -v jq &> /dev/null; then
68
+ R=$(jq -r '.reasons[0].message // empty' "$OPENCLAW_DECISION_FILE" 2> /dev/null)
69
+ if [ -n "$R" ]; then
70
+ REASON="$R"
71
+ fi
72
+ fi
73
+ # If no decision file was set, try common config dirs so we can show actual deny reason
74
+ if [ "$REASON" = "Policy denied this action." ] && command -v jq &> /dev/null; then
75
+ for DEC in "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}/aport/decision.json" "$HOME/.cursor/aport/decision.json" "$HOME/.openclaw/aport/decision.json"; do
76
+ if [ -f "$DEC" ]; then
77
+ R=$(jq -r '.reasons[0].message // empty' "$DEC" 2> /dev/null)
78
+ if [ -n "$R" ]; then
79
+ REASON="$R"
80
+ break
81
+ fi
82
+ fi
83
+ done
84
+ fi
85
+ # Fallback: help user debug guardrail/script errors
86
+ if [ "$REASON" = "Policy denied this action." ]; then
87
+ REASON="Policy denied or guardrail error. Check passport and guardrail script (see docs/frameworks/cursor.md)."
88
+ fi
89
+ echo "{\"permission\":\"deny\",\"allowed\":false,\"agentMessage\":$(echo "$REASON" | jq -Rs .),\"reason\":$(echo "$REASON" | jq -Rs .)}"
90
+ exit 2
@@ -0,0 +1,108 @@
1
+ #!/bin/bash
2
+ # APort API policy evaluator — calls APort API with agent_id (cloud) or passport (local)
3
+ # Supports: APORT_AGENT_ID (registry lookup) or local passport file (sent in request, not stored)
4
+ # Usage: aport-guardrail-api.sh <tool_name> '<context_json>'
5
+ #
6
+ # Endpoint (self-hosted / private instance):
7
+ # export APORT_API_URL="https://api.aport.io" # default; or your self-hosted API
8
+ # export APORT_API_URL="https://api.aport.io" # default cloud
9
+ # export APORT_API_URL="https://your-private.aport.example"
10
+
11
+ set -e
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
14
+ # Resolve paths: config_dir/aport/ (new) or config_dir (legacy); same as bash guardrail
15
+ # shellcheck source=bin/aport-resolve-paths.sh
16
+ . "${SCRIPT_DIR}/bin/aport-resolve-paths.sh"
17
+
18
+ NODE_EVALUATOR="$SCRIPT_DIR/src/evaluator.js"
19
+
20
+ TOOL_NAME="$1"
21
+ DEFAULT_CONTEXT='{}'
22
+ CONTEXT_JSON="${2:-$DEFAULT_CONTEXT}"
23
+
24
+ # DEBUG: Print received arguments
25
+ if [ -n "$DEBUG_APORT" ]; then
26
+ echo "DEBUG: TOOL_NAME=$TOOL_NAME" >&2
27
+ echo "DEBUG: CONTEXT_JSON=$CONTEXT_JSON" >&2
28
+ fi
29
+
30
+ # Ensure APort data dir exists (for decision, audit)
31
+ mkdir -p "$(dirname "$AUDIT_LOG")"
32
+
33
+ # Check if Node.js is available
34
+ if ! command -v node &> /dev/null; then
35
+ echo "Error: node is required but not installed. Install with: brew install node" >&2
36
+ exit 1
37
+ fi
38
+
39
+ # Check if evaluator exists
40
+ if [ ! -f "$NODE_EVALUATOR" ]; then
41
+ echo "Error: Node.js evaluator not found at $NODE_EVALUATOR" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # Passport required only for local mode (passport in request). Cloud mode uses APORT_AGENT_ID.
46
+ if [ -z "$APORT_AGENT_ID" ] && [ ! -f "$PASSPORT_FILE" ]; then
47
+ echo "Error: Passport file not found at $PASSPORT_FILE. Create one with aport-create-passport.sh, or set APORT_AGENT_ID for cloud mode." >&2
48
+ exit 1
49
+ fi
50
+
51
+ # Map tool to policy pack ID
52
+ POLICY_ID=""
53
+ case "$TOOL_NAME" in
54
+ git.create_pr | git.merge | git.push | git.*)
55
+ POLICY_ID="code.repository.merge.v1"
56
+ ;;
57
+ exec.run | exec.* | system.command.* | system.*)
58
+ POLICY_ID="system.command.execute.v1"
59
+ ;;
60
+ message.send | message.* | messaging.*)
61
+ POLICY_ID="messaging.message.send.v1"
62
+ ;;
63
+ mcp.tool.* | mcp.*)
64
+ POLICY_ID="mcp.tool.execute.v1"
65
+ ;;
66
+ agent.session.* | session.create | session.*)
67
+ POLICY_ID="agent.session.create.v1"
68
+ ;;
69
+ agent.tool.* | tool.register | tool.*)
70
+ POLICY_ID="agent.tool.register.v1"
71
+ ;;
72
+ payment.refund | payment.* | finance.payment.refund)
73
+ POLICY_ID="finance.payment.refund.v1"
74
+ ;;
75
+ payment.charge | finance.payment.charge)
76
+ POLICY_ID="finance.payment.charge.v1"
77
+ ;;
78
+ database.write | database.* | data.export)
79
+ POLICY_ID="data.export.create.v1"
80
+ ;;
81
+ *)
82
+ echo "Error: Tool '$TOOL_NAME' is not mapped to a policy pack" >&2
83
+ exit 1
84
+ ;;
85
+ esac
86
+
87
+ # Call Node.js evaluator with API
88
+ if [ -n "$DEBUG_APORT" ]; then
89
+ echo "DEBUG: Calling Node.js evaluator with policy $POLICY_ID" >&2
90
+ fi
91
+
92
+ # Export environment variables for evaluator (APORT_API_URL, APORT_AGENT_ID, APORT_API_KEY passed through)
93
+ export OPENCLAW_PASSPORT_FILE="$PASSPORT_FILE"
94
+ export OPENCLAW_DECISION_FILE="$DECISION_FILE"
95
+
96
+ # Call evaluator and capture exit code
97
+ node "$NODE_EVALUATOR" "$POLICY_ID" "$CONTEXT_JSON"
98
+ EXIT_CODE=$?
99
+
100
+ # Log to audit trail
101
+ if [ -f "$DECISION_FILE" ]; then
102
+ DECISION_ID=$(jq -r '.decision_id // "unknown"' "$DECISION_FILE")
103
+ ALLOW=$(jq -r '.allow // false' "$DECISION_FILE")
104
+ DENY_CODE=$(jq -r '.reasons[0].code // "unknown"' "$DECISION_FILE")
105
+ echo "[$(date -u +%Y-%m-%d\ %H:%M:%S)] tool=$TOOL_NAME decision_id=$DECISION_ID allow=$ALLOW policy=$POLICY_ID code=$DENY_CODE" >> "$AUDIT_LOG"
106
+ fi
107
+
108
+ exit $EXIT_CODE