@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,148 @@
1
+ /**
2
+ * Test suite for OAP VC conversion tools
3
+ */
4
+
5
+ import {
6
+ exportPassportToVC,
7
+ exportDecisionToVC,
8
+ importVCToPassport,
9
+ importVCToDecision,
10
+ OAPPassport,
11
+ OAPDecision,
12
+ RegistryKey,
13
+ } from "./index.js";
14
+
15
+ // Sample OAP Passport
16
+ const samplePassport: OAPPassport = {
17
+ agent_id: "550e8400-e29b-41d4-a716-446655440000",
18
+ kind: "template",
19
+ spec_version: "oap/1.0",
20
+ owner_id: "org_12345678",
21
+ owner_type: "org",
22
+ assurance_level: "L2",
23
+ status: "active",
24
+ capabilities: [
25
+ {
26
+ id: "finance.payment.refund",
27
+ params: { currency_limits: { USD: { max_per_tx: 5000 } } },
28
+ },
29
+ { id: "data.export", params: { max_rows: 100000 } },
30
+ ],
31
+ limits: {
32
+ "finance.payment.refund": {
33
+ currency_limits: {
34
+ USD: { max_per_tx: 5000, daily_cap: 50000 },
35
+ },
36
+ reason_codes: ["customer_request", "defective_product"],
37
+ idempotency_required: true,
38
+ },
39
+ },
40
+ regions: ["US", "CA"],
41
+ metadata: {
42
+ name: "Customer Support AI",
43
+ description: "AI agent for customer support operations",
44
+ },
45
+ created_at: "2024-01-01T00:00:00Z",
46
+ updated_at: "2024-01-15T10:30:00Z",
47
+ version: "1.0.0",
48
+ };
49
+
50
+ // Sample OAP Decision
51
+ const sampleDecision: OAPDecision = {
52
+ decision_id: "550e8400-e29b-41d4-a716-446655440002",
53
+ policy_id: "finance.payment.refund.v1",
54
+ agent_id: "550e8400-e29b-41d4-a716-446655440000",
55
+ owner_id: "org_12345678",
56
+ assurance_level: "L2",
57
+ allow: true,
58
+ reasons: [{ code: "oap.allowed", message: "Transaction within limits" }],
59
+ created_at: "2024-01-15T10:30:00Z",
60
+ expires_in: 3600,
61
+ passport_digest:
62
+ "sha256:abcd1234efgh5678ijkl9012mnop3456qrst7890uvwx1234yzab5678cdef",
63
+ signature: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
64
+ kid: "oap:registry:key-2025-01",
65
+ decision_token: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
66
+ };
67
+
68
+ // Sample Registry Key
69
+ const sampleRegistryKey: RegistryKey = {
70
+ issuer: "https://aport.io",
71
+ kid: "key-2025-01",
72
+ publicKey: "placeholder-public-key",
73
+ privateKey: "placeholder-private-key",
74
+ };
75
+
76
+ async function runTests() {
77
+ console.log("🧪 Running OAP VC Conversion Tests\n");
78
+
79
+ try {
80
+ // Test 1: Export Passport to VC
81
+ console.log("1. Testing Passport → VC conversion...");
82
+ const passportVC = await exportPassportToVC(samplePassport, sampleRegistryKey);
83
+ console.log(" ✅ Passport exported to VC successfully");
84
+ console.log(` 📄 VC Type: ${passportVC.type.join(", ")}`);
85
+ console.log(` 🏢 Issuer: ${passportVC.issuer}`);
86
+ console.log(` 📅 Issuance Date: ${passportVC.issuanceDate}`);
87
+ console.log(` ⏰ Expiration Date: ${passportVC.expirationDate}\n`);
88
+
89
+ // Test 2: Export Decision to VC
90
+ console.log("2. Testing Decision → VC conversion...");
91
+ const decisionVC = await exportDecisionToVC(sampleDecision, sampleRegistryKey);
92
+ console.log(" ✅ Decision exported to VC successfully");
93
+ console.log(` 📄 VC Type: ${decisionVC.type.join(", ")}`);
94
+ console.log(` 🏢 Issuer: ${decisionVC.issuer}`);
95
+ console.log(` 📅 Issuance Date: ${decisionVC.issuanceDate}`);
96
+ console.log(` ⏰ Expiration Date: ${decisionVC.expirationDate}\n`);
97
+
98
+ // Test 3: Import VC to Passport
99
+ console.log("3. Testing VC → Passport conversion...");
100
+ const importedPassport = await importVCToPassport(passportVC);
101
+ console.log(" ✅ VC imported to Passport successfully");
102
+ console.log(` 🆔 Agent ID: ${importedPassport.agent_id}`);
103
+ console.log(` 📋 Kind: ${importedPassport.kind}`);
104
+ console.log(` 🔐 Assurance Level: ${importedPassport.assurance_level}`);
105
+ console.log(
106
+ ` 📊 Capabilities: ${importedPassport.capabilities.length} capabilities\n`
107
+ );
108
+
109
+ // Test 4: Import VC to Decision
110
+ console.log("4. Testing VC → Decision conversion...");
111
+ const importedDecision = await importVCToDecision(decisionVC);
112
+ console.log(" ✅ VC imported to Decision successfully");
113
+ console.log(` 🆔 Decision ID: ${importedDecision.decision_id}`);
114
+ console.log(` 📋 Policy ID: ${importedDecision.policy_id}`);
115
+ console.log(` ✅ Allow: ${importedDecision.allow}`);
116
+ console.log(` 📝 Reasons: ${importedDecision.reasons.length} reasons\n`);
117
+
118
+ // Test 5: Round-trip conversion
119
+ console.log(
120
+ "5. Testing round-trip conversion (Passport → VC → Passport)..."
121
+ );
122
+ const roundTripPassport = await importVCToPassport(
123
+ await exportPassportToVC(samplePassport, sampleRegistryKey)
124
+ );
125
+ const isEqual =
126
+ JSON.stringify(samplePassport) === JSON.stringify(roundTripPassport);
127
+ console.log(
128
+ ` ${isEqual ? "✅" : "❌"} Round-trip conversion ${
129
+ isEqual ? "preserved" : "modified"
130
+ } data`
131
+ );
132
+ console.log(` 🆔 Original Agent ID: ${samplePassport.agent_id}`);
133
+ console.log(` 🆔 Round-trip Agent ID: ${roundTripPassport.agent_id}\n`);
134
+
135
+ console.log(
136
+ "🎉 All tests passed! OAP VC conversion tools are working correctly."
137
+ );
138
+ } catch (error) {
139
+ console.error(
140
+ "❌ Test failed:",
141
+ error instanceof Error ? error.message : "Unknown error"
142
+ );
143
+ process.exit(1);
144
+ }
145
+ }
146
+
147
+ // Run tests
148
+ runTests();
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Verifiable Presentation (VP) Support
3
+ *
4
+ * A Verifiable Presentation is a package of one or more Verifiable Credentials
5
+ * that is presented to a verifier. The holder signs the VP to prove they control
6
+ * the credentials.
7
+ *
8
+ * W3C VC Data Model 1.1 Compliance:
9
+ * - @context: Required, must include https://www.w3.org/2018/credentials/v1
10
+ * - type: Required, must include "VerifiablePresentation"
11
+ * - verifiableCredential: Required, one or more VCs
12
+ * - holder: Optional, DID of the holder
13
+ * - proof: Required, includes challenge (required) and domain (optional) for replay prevention
14
+ *
15
+ * @see https://www.w3.org/TR/vc-data-model/#presentations
16
+ */
17
+
18
+ import { VerifiableCredential } from "./index";
19
+ import {
20
+ signEd25519,
21
+ verifyEd25519,
22
+ canonicalizeJsonLd,
23
+ privateKeyToBytes as cryptoPrivateKeyToBytes,
24
+ bytesToBase64url,
25
+ } from "./crypto-utils";
26
+
27
+ export interface VerifiablePresentation {
28
+ "@context": string[];
29
+ type: string[];
30
+ verifiableCredential: VerifiableCredential | VerifiableCredential[];
31
+ holder?: string; // DID of the holder
32
+ proof: {
33
+ type: string;
34
+ created: string;
35
+ verificationMethod: string;
36
+ proofPurpose: string;
37
+ challenge: string; // Required: Nonce to prevent replay attacks
38
+ domain?: string; // Optional: Domain of the verifier
39
+ jws: string;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Options for creating a Verifiable Presentation
45
+ *
46
+ * @property holderDid - Optional DID of the holder (will be derived from VC if not provided)
47
+ * @property holderPrivateKey - Required private key for signing the VP
48
+ * @property challenge - Required nonce to prevent replay attacks (should come from verifier)
49
+ * @property domain - Optional domain binding for additional security
50
+ */
51
+ export interface PresentationOptions {
52
+ holderDid?: string;
53
+ holderPrivateKey: string; // Required: Private key for signing
54
+ challenge: string; // Required: Nonce to prevent replay attacks
55
+ domain?: string; // Optional: Domain of the verifier
56
+ }
57
+
58
+ /**
59
+ * Create a Verifiable Presentation from one or more Verifiable Credentials
60
+ *
61
+ * @param credentials - One or more Verifiable Credentials to include
62
+ * @param options - Presentation options including holder DID, private key, challenge, and domain
63
+ * @returns Verifiable Presentation compliant with W3C VC Data Model 1.1
64
+ *
65
+ * @throws Error if credentials are invalid or required options are missing
66
+ */
67
+ export async function createVerifiablePresentation(
68
+ credentials: VerifiableCredential | VerifiableCredential[],
69
+ options: PresentationOptions
70
+ ): Promise<VerifiablePresentation> {
71
+ const vcArray = Array.isArray(credentials) ? credentials : [credentials];
72
+
73
+ // Validate all credentials
74
+ for (const vc of vcArray) {
75
+ if (!vc["@context"] || !vc.type || !vc.credentialSubject || !vc.proof) {
76
+ throw new Error("Invalid Verifiable Credential in presentation");
77
+ }
78
+ }
79
+
80
+ // Validate required options
81
+ if (!options.holderPrivateKey) {
82
+ throw new Error("holderPrivateKey is required for signing");
83
+ }
84
+ if (!options.challenge) {
85
+ throw new Error("challenge is required to prevent replay attacks");
86
+ }
87
+
88
+ // Use holder DID or derive from first credential's subject
89
+ const holder =
90
+ options.holderDid ||
91
+ (vcArray[0].credentialSubject as any)?.did ||
92
+ (vcArray[0].credentialSubject as any)?.agent_id;
93
+
94
+ if (!holder) {
95
+ throw new Error(
96
+ "holder DID is required. Provide holderDid or ensure VC has did/agent_id in credentialSubject"
97
+ );
98
+ }
99
+
100
+ // Create VP structure (without proof first, proof is added after signing)
101
+ const vpWithoutProof = {
102
+ "@context": [
103
+ "https://www.w3.org/2018/credentials/v1",
104
+ "https://w3id.org/security/suites/ed25519-2020/v1",
105
+ ],
106
+ type: ["VerifiablePresentation", "OAPPassportPresentation"],
107
+ verifiableCredential: vcArray.length === 1 ? vcArray[0] : vcArray,
108
+ holder: holder,
109
+ };
110
+
111
+ // Sign the presentation
112
+ const created = new Date().toISOString();
113
+ const verificationMethod = `${holder}#key-1`;
114
+ const jws = await signPresentation(
115
+ vpWithoutProof,
116
+ options.holderPrivateKey,
117
+ options,
118
+ created,
119
+ verificationMethod
120
+ );
121
+
122
+ // Add proof to complete the VP
123
+ const vp: VerifiablePresentation = {
124
+ ...vpWithoutProof,
125
+ proof: {
126
+ type: "Ed25519Signature2020",
127
+ created: created,
128
+ verificationMethod: verificationMethod,
129
+ proofPurpose: "authentication",
130
+ challenge: options.challenge, // Required for replay prevention
131
+ ...(options.domain && { domain: options.domain }), // Optional domain
132
+ jws: jws,
133
+ },
134
+ };
135
+
136
+ return vp;
137
+ }
138
+
139
+ /**
140
+ * Sign a Verifiable Presentation using Ed25519
141
+ *
142
+ * Signs the VP structure (without proof) along with challenge and domain
143
+ * to prevent replay attacks.
144
+ *
145
+ * @param vpWithoutProof - VP structure without proof
146
+ * @param privateKey - Holder's private key (base64 or multibase format)
147
+ * @param options - Presentation options including challenge and domain
148
+ * @param created - ISO 8601 timestamp when proof was created
149
+ * @param verificationMethod - DID URL of the verification method
150
+ * @returns JWS signature in compact format: {header}..{signature}
151
+ */
152
+ async function signPresentation(
153
+ vpWithoutProof: any,
154
+ privateKey: string,
155
+ options: PresentationOptions,
156
+ created: string,
157
+ verificationMethod: string
158
+ ): Promise<string> {
159
+ // Build the payload to sign (VP without proof + challenge + domain)
160
+ // This ensures replay attack prevention
161
+ const payloadToSign = {
162
+ ...vpWithoutProof,
163
+ challenge: options.challenge,
164
+ ...(options.domain && { domain: options.domain }),
165
+ created: created,
166
+ verificationMethod: verificationMethod,
167
+ };
168
+
169
+ // Canonicalize the JSON-LD representation
170
+ const canonicalMessage = await canonicalizeJsonLd(payloadToSign);
171
+
172
+ // Convert private key to bytes
173
+ const privateKeyBytes = privateKeyToBytes(privateKey);
174
+
175
+ // Sign using Ed25519
176
+ const signatureBytes = await signEd25519(canonicalMessage, privateKeyBytes);
177
+
178
+ // Create JWS header
179
+ const header = {
180
+ alg: "Ed25519",
181
+ typ: "JWT",
182
+ };
183
+
184
+ // Encode header and signature as base64url
185
+ const headerB64 = bytesToBase64url(
186
+ new TextEncoder().encode(JSON.stringify(header))
187
+ );
188
+ const signatureB64 = bytesToBase64url(signatureBytes);
189
+
190
+ // Return compact JWS format: {header}..{signature}
191
+ // Note: Detached payload (payload is not included in JWS)
192
+ return `${headerB64}..${signatureB64}`;
193
+ }
194
+
195
+ // Use the privateKeyToBytes function from crypto-utils
196
+ const privateKeyToBytes = cryptoPrivateKeyToBytes;
197
+
198
+ /**
199
+ * Verify a Verifiable Presentation
200
+ *
201
+ * Validates VP structure and verifies the cryptographic signature.
202
+ * Also checks challenge and domain to prevent replay attacks.
203
+ *
204
+ * @param vp - Verifiable Presentation to verify
205
+ * @param expectedChallenge - Expected challenge value (must match proof.challenge)
206
+ * @param expectedDomain - Expected domain value (optional, must match if provided)
207
+ * @param publicKey - Public key for signature verification (optional, will resolve from verificationMethod if not provided)
208
+ * @returns Verification result with valid flag and optional error message
209
+ */
210
+ export async function verifyVerifiablePresentation(
211
+ vp: VerifiablePresentation,
212
+ expectedChallenge: string,
213
+ expectedDomain?: string,
214
+ publicKey?: string | Uint8Array
215
+ ): Promise<{ valid: boolean; error?: string }> {
216
+ // Validate VP structure
217
+ if (!vp["@context"] || !vp.type || !vp.verifiableCredential || !vp.proof) {
218
+ return { valid: false, error: "Invalid VP structure" };
219
+ }
220
+
221
+ // Check if type includes VerifiablePresentation
222
+ if (!vp.type.includes("VerifiablePresentation")) {
223
+ return { valid: false, error: "VP must have type VerifiablePresentation" };
224
+ }
225
+
226
+ // Validate challenge (required for replay prevention)
227
+ if (!vp.proof.challenge) {
228
+ return { valid: false, error: "VP proof must include challenge" };
229
+ }
230
+
231
+ if (vp.proof.challenge !== expectedChallenge) {
232
+ return {
233
+ valid: false,
234
+ error: "Challenge mismatch - possible replay attack",
235
+ };
236
+ }
237
+
238
+ // Validate domain if provided
239
+ if (expectedDomain) {
240
+ if (vp.proof.domain !== expectedDomain) {
241
+ return {
242
+ valid: false,
243
+ error: "Domain mismatch - presentation not intended for this verifier",
244
+ };
245
+ }
246
+ }
247
+
248
+ // Validate credentials
249
+ const vcs = Array.isArray(vp.verifiableCredential)
250
+ ? vp.verifiableCredential
251
+ : [vp.verifiableCredential];
252
+
253
+ for (const vc of vcs) {
254
+ if (!vc["@context"] || !vc.type || !vc.credentialSubject || !vc.proof) {
255
+ return { valid: false, error: "Invalid VC in presentation" };
256
+ }
257
+ }
258
+
259
+ // Verify signature
260
+ try {
261
+ // Recreate VP without proof for signing
262
+ const vpWithoutProof = {
263
+ "@context": vp["@context"],
264
+ type: vp.type,
265
+ verifiableCredential: vp.verifiableCredential,
266
+ holder: vp.holder,
267
+ challenge: vp.proof.challenge,
268
+ ...(vp.proof.domain && { domain: vp.proof.domain }),
269
+ created: vp.proof.created,
270
+ verificationMethod: vp.proof.verificationMethod,
271
+ };
272
+
273
+ // Canonicalize
274
+ const canonicalMessage = await canonicalizeJsonLd(vpWithoutProof);
275
+
276
+ // Extract signature from JWS
277
+ const jwsParts = vp.proof.jws.split(".");
278
+ if (jwsParts.length !== 3) {
279
+ return { valid: false, error: "Invalid JWS format" };
280
+ }
281
+
282
+ const signatureB64 = jwsParts[2];
283
+ const signatureBytes = base64urlToBytes(signatureB64);
284
+
285
+ // Get public key
286
+ if (!publicKey) {
287
+ // Public key resolution from verificationMethod is not yet implemented
288
+ // This would require DID Document resolution, which is environment-dependent
289
+ return {
290
+ valid: false,
291
+ error:
292
+ "Public key required for verification. Provide it explicitly or implement DID resolution to fetch from verificationMethod.",
293
+ };
294
+ }
295
+
296
+ const publicKeyBytes = publicKeyToBytes(publicKey);
297
+
298
+ // Verify signature
299
+ const isValid = await verifyEd25519(
300
+ canonicalMessage,
301
+ signatureBytes,
302
+ publicKeyBytes
303
+ );
304
+
305
+ if (!isValid) {
306
+ return { valid: false, error: "Invalid signature" };
307
+ }
308
+
309
+ return { valid: true };
310
+ } catch (error) {
311
+ return {
312
+ valid: false,
313
+ error: `Verification error: ${
314
+ error instanceof Error ? error.message : "Unknown error"
315
+ }`,
316
+ };
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Convert base64url string to Uint8Array
322
+ */
323
+ function base64urlToBytes(base64url: string): Uint8Array {
324
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
325
+ // Handle padding
326
+ const padLength = (4 - (base64.length % 4)) % 4;
327
+ const paddedBase64 = base64 + "=".repeat(padLength);
328
+
329
+ try {
330
+ const binaryString = atob(paddedBase64);
331
+ const bytes = new Uint8Array(binaryString.length);
332
+ for (let i = 0; i < binaryString.length; i++) {
333
+ bytes[i] = binaryString.charCodeAt(i);
334
+ }
335
+ return bytes;
336
+ } catch (e) {
337
+ throw new Error(
338
+ `Invalid base64url string: ${
339
+ e instanceof Error ? e.message : "Unknown error"
340
+ }`
341
+ );
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Convert public key string to Uint8Array
347
+ *
348
+ * @param publicKey - Public key in various formats (base64url, base64, multibase, or Uint8Array)
349
+ * @returns Decoded bytes
350
+ * @throws Error if the key format is invalid
351
+ * @internal
352
+ */
353
+ function publicKeyToBytes(publicKey: string | Uint8Array): Uint8Array {
354
+ if (publicKey instanceof Uint8Array) {
355
+ return publicKey;
356
+ }
357
+
358
+ // Remove multibase prefix if present
359
+ if (publicKey.startsWith("z")) {
360
+ publicKey = publicKey.slice(1);
361
+ }
362
+
363
+ // Convert base64/base64url to bytes
364
+ const base64 = publicKey.replace(/-/g, "+").replace(/_/g, "/");
365
+ const padLength = (4 - (base64.length % 4)) % 4;
366
+ const paddedBase64 = base64 + "=".repeat(padLength);
367
+
368
+ try {
369
+ const binaryString = atob(paddedBase64);
370
+ const bytes = new Uint8Array(binaryString.length);
371
+ for (let i = 0; i < binaryString.length; i++) {
372
+ bytes[i] = binaryString.charCodeAt(i);
373
+ }
374
+ return bytes;
375
+ } catch (e) {
376
+ throw new Error(
377
+ `Invalid public key format: ${
378
+ e instanceof Error ? e.message : "Unknown error"
379
+ }`
380
+ );
381
+ }
382
+ }