@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,433 @@
1
+ # Implementing Your Own Local Evaluator
2
+
3
+ If you want to implement your own local policy evaluator instead of using the APort cloud API, you have everything you need in this repository!
4
+
5
+ ## What You Have
6
+
7
+ ### 1. Open Agent Passport (OAP) Specification
8
+
9
+ The complete OAP v1.0 specification is available at:
10
+ - **GitHub:** https://github.com/aporthq/aport-spec
11
+ - **Local:** `external/aport-spec/` (git submodule)
12
+
13
+ **Key documents:**
14
+ - `oap/oap-spec.md` - Complete specification
15
+ - `oap/passport-schema.json` - JSON schema for passports
16
+ - `oap/decision-schema.json` - JSON schema for decisions
17
+ - `oap/policy-schema.json` - JSON schema for policy packs
18
+
19
+ ### 2. Policy Pack Definitions
20
+
21
+ All policy pack JSON files are available at:
22
+ - **GitHub:** https://github.com/aporthq/aport-policies
23
+ - **Local:** `external/aport-policies/` (git submodule)
24
+
25
+ Each policy pack includes:
26
+ - `policy.json` - Complete policy definition with evaluation rules
27
+ - `examples/` - Example contexts showing expected input format
28
+ - `tests/` - Test cases showing allow/deny scenarios
29
+
30
+ **Example policy pack structure:**
31
+ ```
32
+ external/aport-policies/
33
+ system.command.execute.v1/
34
+ policy.json # Policy definition
35
+ examples/
36
+ allow-command.json # Example allowed context
37
+ deny-command.json # Example denied context
38
+ tests/
39
+ system-command.test.js # Test suite
40
+ ```
41
+
42
+ ### 3. Evaluation Rules Format
43
+
44
+ Each policy pack contains `evaluation_rules` that define the policy logic:
45
+
46
+ ```json
47
+ {
48
+ "id": "system.command.execute.v1",
49
+ "evaluation_rules_version": "1.0",
50
+ "evaluation_rules": [
51
+ {
52
+ "name": "passport_status_active",
53
+ "type": "expression",
54
+ "condition": "passport.status == 'active'",
55
+ "deny_code": "oap.passport_suspended",
56
+ "description": "Passport must be active"
57
+ },
58
+ {
59
+ "name": "command_allowed",
60
+ "type": "expression",
61
+ "condition": "context.command IN limits.allowed_commands",
62
+ "deny_code": "oap.command_not_allowed",
63
+ "description": "Command must be in allowed list"
64
+ },
65
+ {
66
+ "name": "blocked_pattern_check",
67
+ "type": "custom_validator",
68
+ "validator": "validateBlockedPatterns",
69
+ "deny_code": "oap.blocked_pattern",
70
+ "description": "Command must not contain blocked patterns"
71
+ }
72
+ ]
73
+ }
74
+ ```
75
+
76
+ ### 4. Required Context Schema
77
+
78
+ Each policy pack defines the expected context format in `required_context`:
79
+
80
+ ```json
81
+ {
82
+ "required_context": {
83
+ "command": {
84
+ "type": "string",
85
+ "description": "The command to execute",
86
+ "required": true
87
+ },
88
+ "args": {
89
+ "type": "array",
90
+ "description": "Command arguments",
91
+ "required": false
92
+ },
93
+ "cwd": {
94
+ "type": "string",
95
+ "description": "Working directory",
96
+ "required": false
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Implementation Guide
103
+
104
+ ### Option 1: Expression-Based Evaluator (Simple)
105
+
106
+ If you only need to support `type: "expression"` rules, you can implement a simple expression evaluator.
107
+
108
+ **Supported operators:**
109
+ - Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=`
110
+ - Logical: `AND`, `OR`, `NOT`
111
+ - Membership: `IN`, `NOT IN`
112
+ - String: `CONTAINS`, `STARTS_WITH`, `ENDS_WITH`
113
+
114
+ **Example implementation (pseudocode):**
115
+
116
+ ```python
117
+ def evaluate_expression(rule, passport, context, limits):
118
+ """
119
+ Evaluate an expression rule against passport + context
120
+
121
+ Returns: (allowed: bool, deny_code: str, message: str)
122
+ """
123
+ condition = rule["condition"]
124
+
125
+ # Replace variables with values
126
+ condition = condition.replace("passport.status", f'"{passport["status"]}"')
127
+ condition = condition.replace("context.command", f'"{context["command"]}"')
128
+ condition = condition.replace("limits.allowed_commands", str(limits["allowed_commands"]))
129
+
130
+ # Handle IN operator
131
+ if " IN " in condition:
132
+ # e.g., "ls" IN ["ls", "pwd", "git"]
133
+ value, list_str = condition.split(" IN ")
134
+ value = value.strip().strip('"')
135
+ list_items = eval(list_str) # Parse list (use proper parser in production!)
136
+ result = value in list_items
137
+ else:
138
+ # Simple comparison
139
+ result = eval(condition) # Use proper expression parser in production!
140
+
141
+ if result:
142
+ return (True, None, None)
143
+ else:
144
+ return (False, rule["deny_code"], rule["description"])
145
+ ```
146
+
147
+ **⚠️ Security Warning:** Don't use `eval()` in production! Use a proper expression parser that restricts operations.
148
+
149
+ ### Option 2: Full Evaluator (Advanced)
150
+
151
+ For a complete implementation with custom validators, you'll need:
152
+
153
+ 1. **Expression evaluator** - Handle all expression types safely
154
+ 2. **Custom validator registry** - Map validator names to functions
155
+ 3. **Standard checks** - Passport status, capabilities, assurance levels
156
+ 4. **Decision builder** - Create OAP v1.0 compliant decision objects
157
+
158
+ **Example implementation structure:**
159
+
160
+ ```python
161
+ class PolicyEvaluator:
162
+ def __init__(self):
163
+ self.custom_validators = {
164
+ "validateBlockedPatterns": self.validate_blocked_patterns,
165
+ "validateRateLimit": self.validate_rate_limit,
166
+ # ... more validators
167
+ }
168
+
169
+ def evaluate_policy(self, policy_pack, passport, context):
170
+ """
171
+ Evaluate a policy pack against passport + context
172
+
173
+ Returns: Decision (OAP v1.0 compliant)
174
+ """
175
+ # 1. Standard checks
176
+ if passport["status"] != "active":
177
+ return self.deny("oap.passport_suspended", "Passport is suspended")
178
+
179
+ # 2. Capability check
180
+ required_caps = policy_pack.get("requires_capabilities", [])
181
+ passport_caps = [c["id"] for c in passport.get("capabilities", [])]
182
+ for cap in required_caps:
183
+ if cap not in passport_caps:
184
+ return self.deny("oap.unknown_capability", f"Missing capability: {cap}")
185
+
186
+ # 3. Assurance level check
187
+ min_assurance = policy_pack.get("min_assurance", "L0")
188
+ if not self.check_assurance(passport["assurance_level"], min_assurance):
189
+ return self.deny("oap.assurance_insufficient", "Insufficient assurance level")
190
+
191
+ # 4. Evaluation rules
192
+ limits = passport.get("limits", {})
193
+ for rule in policy_pack.get("evaluation_rules", []):
194
+ if rule["type"] == "expression":
195
+ result = self.evaluate_expression(rule, passport, context, limits)
196
+ elif rule["type"] == "custom_validator":
197
+ validator_fn = self.custom_validators[rule["validator"]]
198
+ result = validator_fn(passport, context, limits)
199
+ else:
200
+ result = (False, "oap.policy_error", f"Unknown rule type: {rule['type']}")
201
+
202
+ if not result[0]: # If denied
203
+ return self.deny(result[1], result[2])
204
+
205
+ # 5. All checks passed
206
+ return self.allow()
207
+
208
+ def deny(self, code, message):
209
+ return {
210
+ "decision_id": f"local-{uuid.uuid4()}",
211
+ "policy_id": "system.command.execute.v1",
212
+ "passport_id": passport["passport_id"],
213
+ "owner_id": passport["owner_id"],
214
+ "assurance_level": passport["assurance_level"],
215
+ "allow": False,
216
+ "reasons": [{"code": code, "message": message}],
217
+ "issued_at": datetime.utcnow().isoformat() + "Z",
218
+ "expires_at": (datetime.utcnow() + timedelta(hours=1)).isoformat() + "Z",
219
+ "passport_digest": self.compute_digest(passport),
220
+ "signature": "ed25519:local-unsigned",
221
+ "kid": "oap:local:dev-key"
222
+ }
223
+
224
+ def allow(self):
225
+ # Similar to deny() but with allow=True
226
+ pass
227
+ ```
228
+
229
+ ### Option 3: Fork the APort Evaluator (Easiest)
230
+
231
+ The APort cloud API uses a generic evaluator that's designed to work with any policy pack. If you want the exact same implementation, you can:
232
+
233
+ 1. **Contact us** - We may open-source the core evaluator in the future
234
+ 2. **Request access** - We can provide the evaluator source code for enterprise customers
235
+ 3. **Use the API** - The easiest option (see `src/evaluator.js` in this repo)
236
+
237
+ ## Testing Your Implementation
238
+
239
+ ### Test Data
240
+
241
+ Use the test cases from the policy pack directories:
242
+
243
+ ```bash
244
+ # Get test passport
245
+ cat external/aport-policies/system.command.execute.v1/tests/fixtures/passport.json
246
+
247
+ # Get test contexts (allow case)
248
+ cat external/aport-policies/system.command.execute.v1/examples/allow-command.json
249
+
250
+ # Get test contexts (deny case)
251
+ cat external/aport-policies/system.command.execute.v1/examples/deny-command.json
252
+ ```
253
+
254
+ ### Test Cases
255
+
256
+ Each policy pack includes a test suite showing expected behavior:
257
+
258
+ ```javascript
259
+ // Example from system.command.execute.v1/tests/system-command.test.js
260
+ describe("system.command.execute.v1", () => {
261
+ it("should allow commands in allowed list", async () => {
262
+ const decision = await evaluatePolicy(policyPack, passport, {
263
+ command: "ls",
264
+ args: ["-la"]
265
+ });
266
+
267
+ expect(decision.allow).toBe(true);
268
+ });
269
+
270
+ it("should deny commands not in allowed list", async () => {
271
+ const decision = await evaluatePolicy(policyPack, passport, {
272
+ command: "rm",
273
+ args: ["-rf", "/"]
274
+ });
275
+
276
+ expect(decision.allow).toBe(false);
277
+ expect(decision.reasons[0].code).toBe("oap.command_not_allowed");
278
+ });
279
+
280
+ it("should deny blocked patterns", async () => {
281
+ const decision = await evaluatePolicy(policyPack, passport, {
282
+ command: "ls ; rm -rf /" // Command injection
283
+ });
284
+
285
+ expect(decision.allow).toBe(false);
286
+ expect(decision.reasons[0].code).toBe("oap.blocked_pattern");
287
+ });
288
+ });
289
+ ```
290
+
291
+ ### Compliance Validation
292
+
293
+ Your implementation should pass the OAP v1.0 compliance tests:
294
+
295
+ 1. **Passport validation** - Check status, spec_version, required fields
296
+ 2. **Capability validation** - Check passport has required capabilities
297
+ 3. **Assurance validation** - Check passport meets minimum assurance level
298
+ 4. **Context validation** - Check context has required fields
299
+ 5. **Decision format** - Check decision matches OAP v1.0 schema
300
+ 6. **Signature** - Check decision is properly signed (or marked unsigned)
301
+
302
+ ## Example Implementations
303
+
304
+ ### Bash (Simple)
305
+
306
+ See `bin/aport-guardrail.sh` in this repo for a basic bash implementation. **Note:** This only handles simple checks and is not production-ready.
307
+
308
+ ### Node.js (API Client)
309
+
310
+ See `src/evaluator.js` in this repo for a Node.js client that calls the APort cloud API.
311
+
312
+ ### Python (Full Implementation)
313
+
314
+ Coming soon! We're working on a reference implementation in Python.
315
+
316
+ ### Go (Full Implementation)
317
+
318
+ Coming soon! We're working on a reference implementation in Go.
319
+
320
+ ## Security Considerations
321
+
322
+ ### Expression Evaluation
323
+
324
+ ⚠️ **Never use `eval()` or `exec()` directly** - This allows arbitrary code execution
325
+
326
+ ✅ **Use a restricted expression parser** - Only allow specific operators and functions
327
+
328
+ Example of a safe expression parser (Python):
329
+ ```python
330
+ import ast
331
+ import operator
332
+
333
+ # Whitelist of allowed operations
334
+ ALLOWED_OPS = {
335
+ ast.Eq: operator.eq,
336
+ ast.NotEq: operator.ne,
337
+ ast.Lt: operator.lt,
338
+ ast.LtE: operator.le,
339
+ ast.Gt: operator.gt,
340
+ ast.GtE: operator.ge,
341
+ ast.And: operator.and_,
342
+ ast.Or: operator.or_,
343
+ ast.Not: operator.not_,
344
+ ast.In: lambda x, y: x in y,
345
+ ast.NotIn: lambda x, y: x not in y,
346
+ }
347
+
348
+ def safe_eval(expr, context):
349
+ """
350
+ Safely evaluate an expression with restricted operations
351
+ """
352
+ tree = ast.parse(expr, mode='eval')
353
+
354
+ def eval_node(node):
355
+ if isinstance(node, ast.Expression):
356
+ return eval_node(node.body)
357
+ elif isinstance(node, ast.Compare):
358
+ left = eval_node(node.left)
359
+ result = left
360
+ for op, comparator in zip(node.ops, node.comparators):
361
+ if type(op) not in ALLOWED_OPS:
362
+ raise ValueError(f"Operation {type(op).__name__} not allowed")
363
+ right = eval_node(comparator)
364
+ result = ALLOWED_OPS[type(op)](result, right)
365
+ return result
366
+ elif isinstance(node, ast.Name):
367
+ # Look up variable in context
368
+ return context.get(node.id)
369
+ elif isinstance(node, ast.Constant):
370
+ return node.value
371
+ else:
372
+ raise ValueError(f"Node type {type(node).__name__} not allowed")
373
+
374
+ return eval_node(tree)
375
+ ```
376
+
377
+ ### Custom Validators
378
+
379
+ Custom validators should:
380
+ - ✅ Be pure functions (no side effects)
381
+ - ✅ Have timeouts (don't block forever)
382
+ - ✅ Validate all inputs (don't trust context data)
383
+ - ✅ Return consistent formats (allow/deny, code, message)
384
+ - ⚠️ Be careful with DB lookups (rate limits, caching)
385
+
386
+ ### Decision Signing
387
+
388
+ For production use, decisions should be Ed25519 signed:
389
+
390
+ ```python
391
+ import nacl.signing
392
+ import nacl.encoding
393
+ import json
394
+
395
+ # Generate signing key (do this once, store securely)
396
+ signing_key = nacl.signing.SigningKey.generate()
397
+ verify_key = signing_key.verify_key
398
+
399
+ # Sign decision
400
+ def sign_decision(decision, signing_key):
401
+ # Create canonical JSON (sorted keys)
402
+ canonical = json.dumps(decision, sort_keys=True, separators=(',', ':'))
403
+
404
+ # Sign with Ed25519
405
+ signature = signing_key.sign(canonical.encode('utf-8'))
406
+
407
+ # Add signature to decision
408
+ decision["signature"] = f"ed25519:{signature.signature.hex()}"
409
+ decision["kid"] = "oap:local:your-key-id"
410
+
411
+ return decision
412
+ ```
413
+
414
+ ## Contributing
415
+
416
+ If you implement your own evaluator, we'd love to hear about it!
417
+
418
+ - Share your implementation: https://github.com/aporthq/aport-agent-guardrails/discussions
419
+ - Submit a PR: https://github.com/aporthq/aport-agent-guardrails/pulls
420
+ - Join Discord: https://discord.gg/aport
421
+
422
+ ## Resources
423
+
424
+ - **OAP Spec:** https://github.com/aporthq/aport-spec
425
+ - **Policy Packs:** https://github.com/aporthq/aport-policies
426
+ - **API Reference:** https://api.aport.io/docs
427
+ - **Discord:** https://discord.gg/aport
428
+
429
+ ## License
430
+
431
+ All OAP specifications and policy pack definitions are Apache 2.0 licensed. You're free to implement your own evaluator using these specifications.
432
+
433
+ The APort cloud API is proprietary software. The reference evaluator implementation (if/when open sourced) will be Apache 2.0 licensed.
@@ -0,0 +1,73 @@
1
+ # OpenClaw Compatibility
2
+
3
+ **Last reviewed:** February 2026 (OpenClaw CHANGELOG 2026.2.15 / 2026.2.14, [docs.openclaw.ai](https://docs.openclaw.ai), [architecture](https://docs.openclaw.ai/concepts/architecture))
4
+
5
+ ---
6
+
7
+ ## 1. Latest OpenClaw release (CHANGELOG)
8
+
9
+ - **Releases:** [github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
10
+ - **Docs:** [docs.openclaw.ai](https://docs.openclaw.ai)
11
+
12
+ ### Relevant to APort
13
+
14
+ | Area | Notes |
15
+ |------|--------|
16
+ | **Paths** | Config/state: `~/.openclaw`. Override: `OPENCLAW_HOME` / `OPENCLAW_STATE_DIR`. We default to `~/.openclaw` and respect `OPENCLAW_HOME` in `bin/openclaw`. |
17
+ | **Skills** | Managed skills: `~/.openclaw/skills/`. We install `skills/aport-guardrail/SKILL.md` there. OpenClaw watches `SKILL.md` when refreshing skills. |
18
+ | **Workspace** | `~/.openclaw/workspace` (AGENTS.md, TOOLS.md, SOUL.md, workspace/skills). Setup **auto-installs** the APort rule into `workspace/AGENTS.md` (create or append). No manual merge. |
19
+ | **Breaking (2026.2.13)** | Legacy `.moltbot` auto-detection removed; everything is `~/.openclaw`. No impact (we never used .moltbot). |
20
+ | **Messaging** | `openclaw message send` and cron use `target` (not just `to`/`channelId`). Our tool name `messaging.message.send` and context JSON are independent; no change needed. |
21
+ | **Hooks** | `before_tool_call` / `after_tool_call` exist. **Deterministic** enforcement requires hooks (or core integration); see below. |
22
+
23
+ ### Enforcement model: AGENTS.md is best-effort, not deterministic
24
+
25
+ **Current approach (AGENTS.md + skill):** The APort rule is written into `workspace/AGENTS.md` so the agent is *instructed* to call the guardrail script before effectful actions. That is **best-effort**: the LLM may skip it, forget it, or be prompted to bypass it. It is **not** a guarantee that every tool run is checked.
26
+
27
+ **Purpose of APort:** Pre-action **authorization** should be enforced by the **platform** (OpenClaw calling the guardrail before executing a tool), not by the model following a prompt. Same outcome every time = deterministic enforcement.
28
+
29
+ **Intended production approach:** Use a **hook** (`before_tool_call`) or core integration so OpenClaw invokes the guardrail **before** every tool execution, regardless of what the agent “decides.” The OpenClaw plugin in this repo provides that hook. Until the plugin is installed, the AGENTS.md rule is a **stopgap** to get policy and audit in place; it does not replace deterministic enforcement.
30
+
31
+ ### Running with a project-specific OpenClaw home
32
+
33
+ If you set `OPENCLAW_HOME` to a project dir (e.g. `.../valentine-openclaw`), OpenClaw uses that dir as `~/.openclaw` for that process: config, auth, workspace, and skills all live under that dir. The agent will use the APort passport and `.skills` from that project.
34
+
35
+ **Auth / model provider:** That home has its own auth store (e.g. `$OPENCLAW_HOME/.openclaw/agents/main/agent/auth-profiles.json`). It does **not** use your default `~/.openclaw` credentials. So if you only have OpenAI (or WhatsApp, Brave, ElevenLabs, etc.) set up in the default install, you must either:
36
+
37
+ 1. **Configure auth for the project home**
38
+ Run the wizard with that home set, then add your provider (e.g. OpenAI):
39
+ ```bash
40
+ OPENCLAW_HOME=/path/to/valentine-openclaw openclaw configure
41
+ # Add OpenAI (or your provider) when prompted; or
42
+ OPENCLAW_HOME=/path/to/valentine-openclaw openclaw models auth add
43
+ ```
44
+
45
+ 2. **Copy auth from your default install**
46
+ Copy the agent auth (and optionally credentials) from `~/.openclaw` into `$OPENCLAW_HOME/.openclaw` so the project home sees the same providers (e.g. OpenAI, WhatsApp). Only do this on a machine you control and keep the project dir private.
47
+
48
+ After that, run the agent with that home and a session target:
49
+ ```bash
50
+ OPENCLAW_HOME=/path/to/valentine-openclaw openclaw agent --local --session-id my-test --message "Run node --version and report. Use APort guardrail before any command per AGENTS.md."
51
+ ```
52
+ APort decisions will be written to `$OPENCLAW_HOME/decision.json` and `$OPENCLAW_HOME/audit.log`.
53
+
54
+ ### What we should add (optional)
55
+
56
+ - **Document OPENCLAW_HOME:** In README or QUICKSTART, mention that if users set `OPENCLAW_HOME` (e.g. to a project-specific dir), our script uses it as the default config dir.
57
+ - **OpenClaw version note:** In README or this doc, state we align with OpenClaw 2026.2.x layout (`~/.openclaw`, `~/.openclaw/skills`, `~/.openclaw/workspace`).
58
+
59
+ ### Blockers and risks
60
+
61
+ - **No blockers.** Our integration is script-based (passport + guardrail script + AGENTS.md + optional plugin). The plugin uses OpenClaw's `before_tool_call` hook for deterministic enforcement.
62
+ - **Low risk:** If OpenClaw changes the managed-skill path (e.g. from `~/.openclaw/skills` to another dir), we would update `bin/openclaw` install path; CHANGELOG shows no such change.
63
+ - **Low risk:** Our tool names (`system.command.execute`, `messaging.message.send`, etc.) are our own convention for policy mapping, not OpenClaw tool IDs; renames of OpenClaw’s internal tool names do not block us.
64
+
65
+ ---
66
+
67
+ ## 2. References
68
+
69
+ - OpenClaw CHANGELOG: https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
70
+ - OpenClaw docs: https://docs.openclaw.ai
71
+ - Gateway architecture: https://docs.openclaw.ai/concepts/architecture
72
+ - Integrations: https://openclaw.ai/integrations
73
+ - OpenClaw plugin: [extensions/openclaw-aport/README.md](../extensions/openclaw-aport/README.md)