@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,183 @@
1
+ const express = require("express");
2
+ const { requirePolicy } = require("@aporthq/middleware-express");
3
+
4
+ const app = express();
5
+ app.use(express.json());
6
+
7
+ // Apply messaging policy to all messaging routes
8
+ app.post(
9
+ "/messages",
10
+ requirePolicy("messaging.message.send.v1"),
11
+ async (req, res) => {
12
+ try {
13
+ const { channel, recipients, content, mentions } = req.body;
14
+ const passport = req.policyResult.passport;
15
+
16
+ // Check channel allowlist
17
+ const allowedChannels =
18
+ passport.capabilities.find((cap) => cap.id === "messaging.send")?.params
19
+ ?.channels_allowlist || [];
20
+
21
+ if (allowedChannels.length > 0 && !allowedChannels.includes(channel)) {
22
+ return res.status(403).json({
23
+ error: "Channel not allowed",
24
+ allowed_channels: allowedChannels,
25
+ upgrade_instructions:
26
+ "Add channel to your passport's channels_allowlist",
27
+ });
28
+ }
29
+
30
+ // Check mention policy
31
+ const mentionPolicy =
32
+ passport.capabilities.find((cap) => cap.id === "messaging.send")?.params
33
+ ?.mention_policy || "limited";
34
+
35
+ if (mentions && mentions.length > 0) {
36
+ if (mentionPolicy === "none") {
37
+ return res.status(403).json({
38
+ error: "Mentions not allowed",
39
+ mention_policy: mentionPolicy,
40
+ });
41
+ }
42
+ if (mentionPolicy === "limited" && mentions.includes("@everyone")) {
43
+ return res.status(403).json({
44
+ error: "@everyone mentions not allowed with limited mention policy",
45
+ });
46
+ }
47
+ }
48
+
49
+ // Rate limiting check (would integrate with actual rate limiter)
50
+ const rateLimitCheck = await checkMessageRateLimit(passport.agent_id);
51
+ if (!rateLimitCheck.allowed) {
52
+ return res.status(429).json({
53
+ error: "Rate limit exceeded",
54
+ retry_after: rateLimitCheck.retry_after,
55
+ limits: {
56
+ per_minute: passport.limits.msgs_per_min,
57
+ per_day: passport.limits.msgs_per_day,
58
+ },
59
+ });
60
+ }
61
+
62
+ // Send message using your messaging service
63
+ const message_id = await sendMessage({
64
+ channel,
65
+ recipients,
66
+ content,
67
+ mentions,
68
+ agent_id: passport.agent_id,
69
+ agent_name: passport.name,
70
+ });
71
+
72
+ // Record usage for rate limiting
73
+ await recordMessageUsage(passport.agent_id);
74
+
75
+ // Log the message
76
+ console.log(
77
+ `Message sent: ${message_id} to ${channel} by agent ${passport.agent_id}`
78
+ );
79
+
80
+ res.json({
81
+ success: true,
82
+ message_id,
83
+ channel,
84
+ recipients: recipients.length,
85
+ status: "sent",
86
+ });
87
+ } catch (error) {
88
+ console.error("Message sending error:", error);
89
+ res.status(500).json({ error: "Internal server error" });
90
+ }
91
+ }
92
+ );
93
+
94
+ // Broadcast message endpoint
95
+ app.post(
96
+ "/messages/broadcast",
97
+ requirePolicy("messaging.message.send.v1"),
98
+ async (req, res) => {
99
+ try {
100
+ const { channels, content, mentions } = req.body;
101
+ const passport = req.policyResult.passport;
102
+
103
+ // Check if broadcast is within daily limits
104
+ const estimatedMessages = channels.length;
105
+ const dailyUsage = await getDailyMessageUsage(passport.agent_id);
106
+
107
+ if (dailyUsage + estimatedMessages > passport.limits.msgs_per_day) {
108
+ return res.status(403).json({
109
+ error: "Broadcast would exceed daily message limit",
110
+ current_usage: dailyUsage,
111
+ limit: passport.limits.msgs_per_day,
112
+ requested: estimatedMessages,
113
+ });
114
+ }
115
+
116
+ // Process broadcast
117
+ const results = [];
118
+ for (const channel of channels) {
119
+ try {
120
+ const message_id = await sendMessage({
121
+ channel,
122
+ content,
123
+ mentions,
124
+ agent_id: passport.agent_id,
125
+ agent_name: passport.name,
126
+ });
127
+ results.push({ channel, message_id, status: "sent" });
128
+ } catch (error) {
129
+ results.push({ channel, error: error.message, status: "failed" });
130
+ }
131
+ }
132
+
133
+ // Record usage
134
+ await recordMessageUsage(passport.agent_id, channels.length);
135
+
136
+ res.json({
137
+ success: true,
138
+ results,
139
+ total_sent: results.filter((r) => r.status === "sent").length,
140
+ total_failed: results.filter((r) => r.status === "failed").length,
141
+ });
142
+ } catch (error) {
143
+ console.error("Broadcast error:", error);
144
+ res.status(500).json({ error: "Internal server error" });
145
+ }
146
+ }
147
+ );
148
+
149
+ // Mock functions (implement with your actual messaging service)
150
+ async function sendMessage({
151
+ channel,
152
+ recipients,
153
+ content,
154
+ mentions,
155
+ agent_id,
156
+ agent_name,
157
+ }) {
158
+ // Simulate message sending
159
+ await new Promise((resolve) => setTimeout(resolve, 100));
160
+ return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
161
+ }
162
+
163
+ async function checkMessageRateLimit(agent_id) {
164
+ // Implement with Redis or in-memory rate limiter
165
+ // For demo, always allow
166
+ return { allowed: true };
167
+ }
168
+
169
+ async function recordMessageUsage(agent_id, count = 1) {
170
+ // Record usage in your rate limiting system
171
+ console.log(`Recorded ${count} message(s) for agent ${agent_id}`);
172
+ }
173
+
174
+ async function getDailyMessageUsage(agent_id) {
175
+ // Get current daily usage from your tracking system
176
+ return 0; // Mock value
177
+ }
178
+
179
+ const PORT = process.env.PORT || 3000;
180
+ app.listen(PORT, () => {
181
+ console.log(`Messaging service running on port ${PORT}`);
182
+ console.log("Protected by APort messaging.message.send.v1 policy pack");
183
+ });
@@ -0,0 +1,193 @@
1
+ from fastapi import FastAPI, HTTPException, Request
2
+ from pydantic import BaseModel, Field
3
+ from typing import List, Optional
4
+ from aport.middleware import require_policy
5
+ import asyncio
6
+
7
+ app = FastAPI(title="Messaging Service", version="1.0.0")
8
+
9
+ class MessageRequest(BaseModel):
10
+ channel: str
11
+ recipients: List[str]
12
+ content: str
13
+ mentions: Optional[List[str]] = []
14
+
15
+ class BroadcastRequest(BaseModel):
16
+ channels: List[str]
17
+ content: str
18
+ mentions: Optional[List[str]] = []
19
+
20
+ @app.post("/messages")
21
+ @require_policy("messaging.message.send.v1")
22
+ async def send_message(request: Request, message_data: MessageRequest):
23
+ try:
24
+ passport = request.state.policy_result.passport
25
+
26
+ # Check channel allowlist
27
+ messaging_capability = next(
28
+ (cap for cap in passport.capabilities if cap.id == "messaging.send"),
29
+ None
30
+ )
31
+ allowed_channels = (
32
+ messaging_capability.params.get("channels_allowlist", [])
33
+ if messaging_capability and messaging_capability.params
34
+ else []
35
+ )
36
+
37
+ if allowed_channels and message_data.channel not in allowed_channels:
38
+ raise HTTPException(
39
+ status_code=403,
40
+ detail={
41
+ "error": "Channel not allowed",
42
+ "allowed_channels": allowed_channels,
43
+ "upgrade_instructions": "Add channel to your passport's channels_allowlist"
44
+ }
45
+ )
46
+
47
+ # Check mention policy
48
+ mention_policy = (
49
+ messaging_capability.params.get("mention_policy", "limited")
50
+ if messaging_capability and messaging_capability.params
51
+ else "limited"
52
+ )
53
+
54
+ if message_data.mentions:
55
+ if mention_policy == "none":
56
+ raise HTTPException(
57
+ status_code=403,
58
+ detail={
59
+ "error": "Mentions not allowed",
60
+ "mention_policy": mention_policy
61
+ }
62
+ )
63
+ if mention_policy == "limited" and "@everyone" in message_data.mentions:
64
+ raise HTTPException(
65
+ status_code=403,
66
+ detail={
67
+ "error": "@everyone mentions not allowed with limited mention policy"
68
+ }
69
+ )
70
+
71
+ # Rate limiting check (would integrate with actual rate limiter)
72
+ rate_limit_check = await check_message_rate_limit(passport.agent_id)
73
+ if not rate_limit_check["allowed"]:
74
+ raise HTTPException(
75
+ status_code=429,
76
+ detail={
77
+ "error": "Rate limit exceeded",
78
+ "retry_after": rate_limit_check.get("retry_after"),
79
+ "limits": {
80
+ "per_minute": passport.limits.get("msgs_per_min"),
81
+ "per_day": passport.limits.get("msgs_per_day")
82
+ }
83
+ }
84
+ )
85
+
86
+ # Send message using your messaging service
87
+ message_id = await send_message_service({
88
+ "channel": message_data.channel,
89
+ "recipients": message_data.recipients,
90
+ "content": message_data.content,
91
+ "mentions": message_data.mentions,
92
+ "agent_id": passport.agent_id,
93
+ "agent_name": passport.name,
94
+ })
95
+
96
+ # Record usage for rate limiting
97
+ await record_message_usage(passport.agent_id)
98
+
99
+ # Log the message
100
+ print(f"Message sent: {message_id} to {message_data.channel} by agent {passport.agent_id}")
101
+
102
+ return {
103
+ "success": True,
104
+ "message_id": message_id,
105
+ "channel": message_data.channel,
106
+ "recipients": len(message_data.recipients),
107
+ "status": "sent",
108
+ }
109
+
110
+ except HTTPException:
111
+ raise
112
+ except Exception as e:
113
+ print(f"Message sending error: {e}")
114
+ raise HTTPException(status_code=500, detail="Internal server error")
115
+
116
+ @app.post("/messages/broadcast")
117
+ @require_policy("messaging.message.send.v1")
118
+ async def broadcast_message(request: Request, broadcast_data: BroadcastRequest):
119
+ try:
120
+ passport = request.state.policy_result.passport
121
+
122
+ # Check if broadcast is within daily limits
123
+ estimated_messages = len(broadcast_data.channels)
124
+ daily_usage = await get_daily_message_usage(passport.agent_id)
125
+
126
+ daily_limit = passport.limits.get("msgs_per_day", float('inf'))
127
+ if daily_usage + estimated_messages > daily_limit:
128
+ raise HTTPException(
129
+ status_code=403,
130
+ detail={
131
+ "error": "Broadcast would exceed daily message limit",
132
+ "current_usage": daily_usage,
133
+ "limit": daily_limit,
134
+ "requested": estimated_messages
135
+ }
136
+ )
137
+
138
+ # Process broadcast
139
+ results = []
140
+ for channel in broadcast_data.channels:
141
+ try:
142
+ message_id = await send_message_service({
143
+ "channel": channel,
144
+ "content": broadcast_data.content,
145
+ "mentions": broadcast_data.mentions,
146
+ "agent_id": passport.agent_id,
147
+ "agent_name": passport.name,
148
+ })
149
+ results.append({"channel": channel, "message_id": message_id, "status": "sent"})
150
+ except Exception as error:
151
+ results.append({"channel": channel, "error": str(error), "status": "failed"})
152
+
153
+ # Record usage
154
+ await record_message_usage(passport.agent_id, len(broadcast_data.channels))
155
+
156
+ return {
157
+ "success": True,
158
+ "results": results,
159
+ "total_sent": len([r for r in results if r["status"] == "sent"]),
160
+ "total_failed": len([r for r in results if r["status"] == "failed"]),
161
+ }
162
+
163
+ except HTTPException:
164
+ raise
165
+ except Exception as e:
166
+ print(f"Broadcast error: {e}")
167
+ raise HTTPException(status_code=500, detail="Internal server error")
168
+
169
+ # Mock functions (implement with your actual messaging service)
170
+ async def send_message_service(message_data: dict) -> str:
171
+ """Mock message sending function"""
172
+ await asyncio.sleep(0.1) # Simulate API call
173
+ return f"msg_{asyncio.get_event_loop().time()}_{hash(str(message_data)) % 1000000}"
174
+
175
+ async def check_message_rate_limit(agent_id: str) -> dict:
176
+ """Mock rate limit checker"""
177
+ # Implement with Redis or in-memory rate limiter
178
+ # For demo, always allow
179
+ return {"allowed": True}
180
+
181
+ async def record_message_usage(agent_id: str, count: int = 1) -> None:
182
+ """Record message usage in your tracking system"""
183
+ print(f"Recorded {count} message(s) for agent {agent_id}")
184
+
185
+ async def get_daily_message_usage(agent_id: str) -> int:
186
+ """Get current daily usage from your tracking system"""
187
+ return 0 # Mock value
188
+
189
+ if __name__ == "__main__":
190
+ import uvicorn
191
+ print("Messaging service starting...")
192
+ print("Protected by APort messaging.message.send.v1 policy pack")
193
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1,144 @@
1
+ {
2
+ "id": "messaging.message.send.v1",
3
+ "name": "Messaging Protection Policy",
4
+ "description": "Pre-action governance for messaging operations. Enforces rate limits, channel restrictions, mention policies, and content validation for PLG on-ramp.",
5
+ "version": "1.0.0",
6
+ "status": "active",
7
+ "requires_capabilities": ["messaging.send"],
8
+ "min_assurance": "L0",
9
+ "limits_required": [
10
+ "msgs_per_min",
11
+ "msgs_per_day",
12
+ "allowed_recipients",
13
+ "approval_required"
14
+ ],
15
+ "required_fields": ["channel_id", "message", "message_type"],
16
+ "optional_fields": ["mentions", "attachments", "thread_id", "reply_to"],
17
+ "enforcement": {
18
+ "channels_allowlist_enforced": true,
19
+ "mention_policy_enforced": true,
20
+ "rate_limits_enforced": true
21
+ },
22
+ "mcp": {
23
+ "require_allowlisted_if_present": true
24
+ },
25
+ "advice": [
26
+ "Implement rate limiting per agent and per channel",
27
+ "Monitor for spam patterns and suspicious activity",
28
+ "Log all message attempts for Verifiable Attestation",
29
+ "Consider implementing channel-specific limits",
30
+ "Use mention policies to prevent @everyone abuse",
31
+ "Subscribe to status webhooks for instant suspend",
32
+ "Implement content filtering for inappropriate messages",
33
+ "Use progressive rate limiting for new agents"
34
+ ],
35
+ "required_context": {
36
+ "$schema": "http://json-schema.org/draft-07/schema#",
37
+ "type": "object",
38
+ "required": ["channel_id", "message", "message_type"],
39
+ "properties": {
40
+ "channel_id": {
41
+ "type": "string",
42
+ "minLength": 1,
43
+ "description": "Target channel identifier"
44
+ },
45
+ "message": {
46
+ "type": "string",
47
+ "minLength": 1,
48
+ "maxLength": 2000,
49
+ "description": "Message content"
50
+ },
51
+ "message_type": {
52
+ "type": "string",
53
+ "enum": ["text", "embed", "file", "reaction"],
54
+ "description": "Type of message"
55
+ },
56
+ "mentions": {
57
+ "type": "array",
58
+ "items": { "type": "string" },
59
+ "description": "User or role mentions in the message"
60
+ },
61
+ "attachments": {
62
+ "type": "array",
63
+ "items": {
64
+ "type": "object",
65
+ "properties": {
66
+ "url": { "type": "string" },
67
+ "filename": { "type": "string" },
68
+ "size": { "type": "integer" }
69
+ }
70
+ },
71
+ "description": "File attachments"
72
+ },
73
+ "thread_id": {
74
+ "type": "string",
75
+ "description": "Thread identifier for threaded messages"
76
+ },
77
+ "reply_to": {
78
+ "type": "string",
79
+ "description": "Message ID being replied to"
80
+ },
81
+ "mcp_servers": {
82
+ "type": "array",
83
+ "items": { "type": "string" },
84
+ "description": "MCP servers being used in this request (e.g., [\"https://mcp.slack.com\"])"
85
+ },
86
+ "mcp_tools": {
87
+ "type": "array",
88
+ "items": { "type": "string" },
89
+ "description": "MCP tools being used in this request (e.g., [\"slack.messages.send\"])"
90
+ },
91
+ "mcp_server": {
92
+ "type": "string",
93
+ "description": "Single MCP server being used (backward compatibility - use mcp_servers array for multiple)"
94
+ },
95
+ "mcp_tool": {
96
+ "type": "string",
97
+ "description": "Single MCP tool being used (backward compatibility - use mcp_tools array for multiple)"
98
+ },
99
+ "mcp_session": {
100
+ "type": "string",
101
+ "description": "MCP session identifier for audit trail (optional)"
102
+ }
103
+ }
104
+ },
105
+ "evaluation_rules": [
106
+ {
107
+ "name": "passport_status_active",
108
+ "condition": "passport.status == 'active'",
109
+ "deny_code": "oap.passport_suspended",
110
+ "description": "Passport must be active"
111
+ },
112
+ {
113
+ "name": "messaging_capability",
114
+ "condition": "'messaging.send' in passport.capabilities",
115
+ "deny_code": "oap.unknown_capability",
116
+ "description": "Agent must have messaging.send capability"
117
+ },
118
+ {
119
+ "name": "rate_limit_minute",
120
+ "condition": "minute_count < limits.msgs_per_min",
121
+ "deny_code": "oap.rate_limit_exceeded",
122
+ "description": "Per-minute rate limit must not be exceeded"
123
+ },
124
+ {
125
+ "name": "rate_limit_daily",
126
+ "condition": "daily_count < limits.msgs_per_day",
127
+ "deny_code": "oap.rate_limit_exceeded",
128
+ "description": "Daily rate limit must not be exceeded"
129
+ },
130
+ {
131
+ "name": "content_validation",
132
+ "condition": "message.length <= 2000",
133
+ "deny_code": "oap.content_too_long",
134
+ "description": "Message must not exceed length limit"
135
+ }
136
+ ],
137
+ "cache": {
138
+ "default_ttl_seconds": 60,
139
+ "suspend_invalidate_seconds": 30
140
+ },
141
+ "deprecation": null,
142
+ "created_at": "2025-01-16T00:00:00Z",
143
+ "updated_at": "2025-01-30T00:00:00Z"
144
+ }
@@ -0,0 +1,107 @@
1
+ {
2
+ "id": "policy.{operation}.v1",
3
+ "name": "{Operation} Policy",
4
+ "description": "Pre-action governance for {operation} operations. {Brief description of what this policy protects and enforces}.",
5
+ "version": "1.0.0",
6
+ "status": "active",
7
+ "requires_capabilities": ["{capability.name}"],
8
+ "min_assurance": "L2",
9
+ "limits_required": ["require_assurance_at_least", "idempotency_required"],
10
+ "required_fields": ["field1", "field2"],
11
+ "optional_fields": ["optional_field1", "optional_field2"],
12
+ "enforcement": {
13
+ "assurance_required": "limits.{capability}.require_assurance_at_least",
14
+ "idempotency_required": true
15
+ },
16
+ "mcp": {
17
+ "require_allowlisted_if_present": true
18
+ },
19
+ "advice": [
20
+ "Cache /verify with ETag; 60s TTL",
21
+ "Subscribe to status webhooks for instant suspend",
22
+ "Log all {operation} attempts for Verifiable Attestation",
23
+ "Implement appropriate rate limiting",
24
+ "Use idempotency keys to prevent duplicate operations",
25
+ "Provide clear error messages to help agents self-remediate"
26
+ ],
27
+ "required_context": {
28
+ "$schema": "http://json-schema.org/draft-07/schema#",
29
+ "type": "object",
30
+ "required": ["field1", "field2"],
31
+ "properties": {
32
+ "field1": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "description": "Description of field1"
36
+ },
37
+ "field2": {
38
+ "type": "string",
39
+ "description": "Description of field2"
40
+ },
41
+ "optional_field1": {
42
+ "type": "string",
43
+ "description": "Description of optional field1"
44
+ },
45
+ "optional_field2": {
46
+ "type": "integer",
47
+ "minimum": 0,
48
+ "description": "Description of optional field2"
49
+ },
50
+ "mcp_servers": {
51
+ "type": "array",
52
+ "items": { "type": "string" },
53
+ "description": "MCP servers being used in this request (e.g., [\"https://mcp.stripe.com\", \"https://mcp.notion.com\"])"
54
+ },
55
+ "mcp_tools": {
56
+ "type": "array",
57
+ "items": { "type": "string" },
58
+ "description": "MCP tools being used in this request (e.g., [\"stripe.refunds.create\", \"notion.pages.export\"])"
59
+ },
60
+ "mcp_server": {
61
+ "type": "string",
62
+ "description": "Single MCP server being used (backward compatibility - use mcp_servers array for multiple)"
63
+ },
64
+ "mcp_tool": {
65
+ "type": "string",
66
+ "description": "Single MCP tool being used (backward compatibility - use mcp_tools array for multiple)"
67
+ },
68
+ "mcp_session": {
69
+ "type": "string",
70
+ "description": "MCP session identifier for audit trail (optional)"
71
+ }
72
+ }
73
+ },
74
+ "evaluation_rules": [
75
+ {
76
+ "name": "passport_status_active",
77
+ "condition": "passport.status == 'active'",
78
+ "deny_code": "oap.passport_suspended",
79
+ "description": "Passport must be active"
80
+ },
81
+ {
82
+ "name": "assurance_minimum",
83
+ "condition": "passport.assurance_level >= limits.{capability}.require_assurance_at_least",
84
+ "deny_code": "oap.assurance_insufficient",
85
+ "description": "Assurance level must meet minimum requirement"
86
+ },
87
+ {
88
+ "name": "capability_check",
89
+ "condition": "'{capability.name}' in passport.capabilities",
90
+ "deny_code": "oap.unknown_capability",
91
+ "description": "Agent must have required capability"
92
+ },
93
+ {
94
+ "name": "idempotency_check",
95
+ "condition": "idempotency_key not in recent_keys",
96
+ "deny_code": "oap.idempotency_conflict",
97
+ "description": "Idempotency key must be unique"
98
+ }
99
+ ],
100
+ "cache": {
101
+ "default_ttl_seconds": 60,
102
+ "suspend_invalidate_seconds": 30
103
+ },
104
+ "deprecation": null,
105
+ "created_at": "2025-01-30T00:00:00Z",
106
+ "updated_at": "2025-01-30T00:00:00Z"
107
+ }