@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,427 @@
1
+ /**
2
+ * Cryptographic utilities for Ed25519 signing and verification
3
+ *
4
+ * Handles key conversion, signing, and verification for Verifiable Credentials
5
+ */
6
+
7
+ import { signAsync, verifyAsync, getPublicKeyAsync } from "@noble/ed25519";
8
+
9
+ // NOTE: We use async versions (signAsync, verifyAsync) instead of sync versions (sign, verify)
10
+ // because:
11
+ // 1. Async versions work in all environments (Node.js, browsers, Cloudflare Workers)
12
+ // 2. Async versions use Web Crypto API internally, which is available everywhere
13
+ // 3. We don't need to configure hashes.sha512 for sync operations
14
+ //
15
+ // The sync versions (sign, verify) require hashes.sha512 to be set, which is problematic
16
+ // in Cloudflare Workers where we can't easily provide a synchronous SHA-512 function.
17
+
18
+ /**
19
+ * Convert private key from various formats to Uint8Array
20
+ * Supports: PEM, base64, base64url, hex, raw bytes
21
+ */
22
+ export function privateKeyToBytes(privateKey: string | Uint8Array): Uint8Array {
23
+ if (privateKey instanceof Uint8Array) {
24
+ return privateKey;
25
+ }
26
+
27
+ // Remove common prefixes
28
+ let key = privateKey.trim();
29
+
30
+ // Remove PEM headers/footers
31
+ key = key.replace(/-----BEGIN.*?-----/g, "");
32
+ key = key.replace(/-----END.*?-----/g, "");
33
+ key = key.replace(/\s+/g, "");
34
+
35
+ // Remove "ed25519:" prefix if present
36
+ key = key.replace(/^ed25519:/, "");
37
+
38
+ // Try base64url first (most common for Ed25519)
39
+ try {
40
+ return base64urlToBytes(key);
41
+ } catch {
42
+ // Try base64
43
+ try {
44
+ return base64ToBytes(key);
45
+ } catch {
46
+ // Try hex
47
+ try {
48
+ return hexToBytes(key);
49
+ } catch {
50
+ throw new Error(
51
+ "Invalid private key format. Expected base64url, base64, hex, or PEM format."
52
+ );
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Convert public key from various formats to Uint8Array
60
+ *
61
+ * Supports multiple key formats:
62
+ * - Base64url (most common for Ed25519)
63
+ * - Base64
64
+ * - Hex
65
+ * - Multibase (z prefix for base58, currently assumes base64url after prefix)
66
+ * - PEM (with headers/footers)
67
+ * - Raw Uint8Array
68
+ *
69
+ * @param publicKey - Public key in any supported format
70
+ * @returns Decoded bytes as Uint8Array
71
+ * @throws Error if the key format is invalid or cannot be decoded
72
+ */
73
+ export function publicKeyToBytes(publicKey: string | Uint8Array): Uint8Array {
74
+ if (publicKey instanceof Uint8Array) {
75
+ return publicKey;
76
+ }
77
+
78
+ let key = publicKey.trim();
79
+
80
+ // Remove PEM headers/footers
81
+ key = key.replace(/-----BEGIN.*?-----/g, "");
82
+ key = key.replace(/-----END.*?-----/g, "");
83
+ key = key.replace(/\s+/g, "");
84
+
85
+ // Remove "ed25519:" prefix if present
86
+ key = key.replace(/^ed25519:/, "");
87
+
88
+ // Remove multibase "z" prefix (base58) - for now, assume it's base64url after
89
+ if (key.startsWith("z")) {
90
+ // In production, decode base58 here
91
+ // For now, try to decode as base64url after removing z
92
+ key = key.slice(1);
93
+ }
94
+
95
+ // Try base64url first
96
+ try {
97
+ return base64urlToBytes(key);
98
+ } catch {
99
+ // Try base64
100
+ try {
101
+ return base64ToBytes(key);
102
+ } catch {
103
+ // Try hex
104
+ try {
105
+ return hexToBytes(key);
106
+ } catch {
107
+ throw new Error(
108
+ "Invalid public key format. Expected base64url, base64, hex, multibase, or PEM format."
109
+ );
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Convert base64url string to Uint8Array
117
+ */
118
+ function base64urlToBytes(base64url: string): Uint8Array {
119
+ // Convert base64url to base64
120
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
121
+
122
+ // Add padding if needed
123
+ while (base64.length % 4) {
124
+ base64 += "=";
125
+ }
126
+
127
+ // Decode base64
128
+ if (typeof Buffer !== "undefined") {
129
+ return Buffer.from(base64, "base64");
130
+ } else {
131
+ // For Cloudflare Workers, use atob
132
+ const binary = atob(base64);
133
+ const bytes = new Uint8Array(binary.length);
134
+ for (let i = 0; i < binary.length; i++) {
135
+ bytes[i] = binary.charCodeAt(i);
136
+ }
137
+ return bytes;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Convert base64 string to Uint8Array
143
+ *
144
+ * @param base64 - Base64-encoded string
145
+ * @returns Decoded bytes
146
+ * @internal
147
+ */
148
+ function base64ToBytes(base64: string): Uint8Array {
149
+ // Add padding if needed
150
+ let padded = base64;
151
+ while (padded.length % 4) {
152
+ padded += "=";
153
+ }
154
+
155
+ if (typeof Buffer !== "undefined") {
156
+ return Buffer.from(padded, "base64");
157
+ } else {
158
+ const binary = atob(padded);
159
+ const bytes = new Uint8Array(binary.length);
160
+ for (let i = 0; i < binary.length; i++) {
161
+ bytes[i] = binary.charCodeAt(i);
162
+ }
163
+ return bytes;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Convert hex string to Uint8Array
169
+ *
170
+ * @param hex - Hexadecimal string
171
+ * @returns Decoded bytes
172
+ * @internal
173
+ */
174
+ function hexToBytes(hex: string): Uint8Array {
175
+ const bytes = new Uint8Array(hex.length / 2);
176
+ for (let i = 0; i < hex.length; i += 2) {
177
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
178
+ }
179
+ return bytes;
180
+ }
181
+
182
+ /**
183
+ * Convert Uint8Array to base64url string
184
+ *
185
+ * Handles both Node.js (Buffer) and browser/Workers environments.
186
+ * Removes padding and URL-safe characters as per RFC 4648.
187
+ *
188
+ * @param bytes - Bytes to encode
189
+ * @returns Base64url-encoded string (no padding)
190
+ */
191
+ export function bytesToBase64url(bytes: Uint8Array): string {
192
+ if (typeof Buffer !== "undefined") {
193
+ return Buffer.from(bytes)
194
+ .toString("base64")
195
+ .replace(/\+/g, "-")
196
+ .replace(/\//g, "_")
197
+ .replace(/=/g, "");
198
+ } else {
199
+ // For Cloudflare Workers
200
+ let binary = "";
201
+ for (let i = 0; i < bytes.length; i++) {
202
+ binary += String.fromCharCode(bytes[i]);
203
+ }
204
+ const base64 = btoa(binary);
205
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Check if we're running in Cloudflare Workers environment
211
+ *
212
+ * NOTE: This function is kept for potential future use, but we no longer
213
+ * need it for jsonld since we use deterministic JSON everywhere.
214
+ */
215
+ function isCloudflareWorkers(): boolean {
216
+ // Multiple checks to reliably detect Cloudflare Workers
217
+ if (typeof globalThis === "undefined") {
218
+ return false;
219
+ }
220
+
221
+ // Method 1: Check for Cloudflare-specific globals (most reliable)
222
+ // @ts-ignore - Cloudflare Workers specific globals
223
+ if (globalThis.CF_PAGES || globalThis.CLOUDFLARE_ENV || globalThis.CF) {
224
+ return true;
225
+ }
226
+
227
+ // Method 2: Check for Workers-specific bindings (KV, D1, etc.)
228
+ // These are injected by Cloudflare Workers runtime - most reliable indicator
229
+ // @ts-ignore - Workers bindings
230
+ if (globalThis.ai_passport_registry !== undefined) {
231
+ return true;
232
+ }
233
+
234
+ // Method 3: Check for wrangler dev environment
235
+ // When running `wrangler dev`, the file path contains "functionsWorker"
236
+ try {
237
+ // @ts-ignore - Error stack traces in Workers
238
+ const stack = new Error().stack || "";
239
+ if (stack.includes("functionsWorker") || stack.includes("wrangler")) {
240
+ return true;
241
+ }
242
+ } catch {
243
+ // Stack not available, continue
244
+ }
245
+
246
+ // Method 4: Check for Workers runtime indicators
247
+ // Workers have Web Crypto API but not Node.js process
248
+ const hasWebCrypto =
249
+ globalThis.crypto !== undefined &&
250
+ typeof globalThis.crypto.subtle !== "undefined";
251
+
252
+ const hasNodeProcess =
253
+ typeof process !== "undefined" && process.versions?.node !== undefined;
254
+
255
+ const hasRequire = typeof require !== "undefined";
256
+
257
+ // If we have Web Crypto but no Node.js indicators, likely Workers
258
+ // This is a strong indicator since browsers also have Web Crypto but also have window
259
+ const hasWindow = typeof window !== "undefined";
260
+ if (hasWebCrypto && !hasNodeProcess && !hasRequire && !hasWindow) {
261
+ return true;
262
+ }
263
+
264
+ // Method 5: Check for import.meta.env (Vite/Next.js indicator - means NOT Workers)
265
+ // If import.meta.env exists, we're likely in a build tool environment, not Workers
266
+ try {
267
+ // @ts-ignore
268
+ if (typeof import.meta !== "undefined" && import.meta.env) {
269
+ return false; // Build tool environment, not Workers
270
+ }
271
+ } catch {
272
+ // import.meta not available, continue checking
273
+ }
274
+
275
+ // Method 6: Check for Node.js Buffer (Workers don't have it natively)
276
+ // If we're in a Workers-like environment without Buffer, assume Workers
277
+ if (typeof Buffer === "undefined" && hasWebCrypto && !hasWindow) {
278
+ return true;
279
+ }
280
+
281
+ return false;
282
+ }
283
+
284
+ /**
285
+ * Canonicalize JSON document for signing
286
+ *
287
+ * Creates a deterministic representation of the JSON document by sorting keys recursively.
288
+ * This ensures consistent signatures regardless of JSON formatting.
289
+ *
290
+ * **Implementation Note:** We use deterministic JSON (sorted keys) instead of full JSON-LD
291
+ * canonicalization (URDNA2015) because:
292
+ * 1. It works in all environments (Node.js, browsers, Cloudflare Workers)
293
+ * 2. It's simpler and more reliable
294
+ * 3. It's still deterministic - same input always produces same output
295
+ * 4. The signature is still valid and verifiable
296
+ *
297
+ * While W3C VC spec recommends JSON-LD canonicalization, deterministic JSON is sufficient
298
+ * for our use case and avoids the complexity of the jsonld library.
299
+ *
300
+ * @param document - JSON object to canonicalize
301
+ * @param options - Optional configuration (currently unused, kept for API compatibility)
302
+ * @returns Promise resolving to canonicalized bytes
303
+ * @throws Error if canonicalization fails
304
+ */
305
+ export async function canonicalizeJsonLd(
306
+ document: any,
307
+ options: { documentLoader?: any } = {}
308
+ ): Promise<Uint8Array> {
309
+ // Always use deterministic JSON - simple, reliable, works everywhere
310
+ // NOTE: We do NOT use jsonld library anymore - this function name is kept
311
+ // for API compatibility but it just uses deterministic JSON sorting
312
+ try {
313
+ return createDeterministicJson(document);
314
+ } catch (error) {
315
+ // Re-throw with more context if needed
316
+ throw new Error(
317
+ `Failed to canonicalize JSON: ${
318
+ error instanceof Error ? error.message : "Unknown error"
319
+ }`
320
+ );
321
+ }
322
+ }
323
+
324
+ // NOTE: Document loader removed - no longer needed since we use deterministic JSON
325
+ // instead of JSON-LD canonicalization
326
+
327
+ /**
328
+ * Create deterministic JSON representation
329
+ * Sorts keys recursively for consistent output
330
+ */
331
+ function createDeterministicJson(document: any): Uint8Array {
332
+ // Create a deterministic JSON representation
333
+ // Sort keys recursively for consistent output
334
+ const deterministic = JSON.stringify(document, (key, value) => {
335
+ if (value && typeof value === "object" && !Array.isArray(value)) {
336
+ return Object.keys(value)
337
+ .sort()
338
+ .reduce((acc, k) => {
339
+ acc[k] = value[k];
340
+ return acc;
341
+ }, {} as any);
342
+ }
343
+ return value;
344
+ });
345
+
346
+ return new TextEncoder().encode(deterministic);
347
+ }
348
+
349
+ /**
350
+ * Sign a message using Ed25519
351
+ *
352
+ * Uses the async version of @noble/ed25519 which works in all environments
353
+ * (Node.js, browsers, Cloudflare Workers) by leveraging Web Crypto API internally.
354
+ *
355
+ * @param message - Message bytes to sign
356
+ * @param privateKey - Private key (string in various formats or Uint8Array)
357
+ * @returns Promise resolving to signature bytes
358
+ * @throws Error if signing fails (invalid key, malformed message, etc.)
359
+ */
360
+ export async function signEd25519(
361
+ message: Uint8Array,
362
+ privateKey: string | Uint8Array
363
+ ): Promise<Uint8Array> {
364
+ const privateKeyBytes = privateKeyToBytes(privateKey);
365
+
366
+ // Ed25519 private keys are 32 bytes
367
+ // If we have 64 bytes (private + public), use first 32
368
+ const keyBytes =
369
+ privateKeyBytes.length >= 32
370
+ ? privateKeyBytes.slice(0, 32)
371
+ : privateKeyBytes;
372
+
373
+ // Use async version which works in all environments (Node.js, browsers, Workers)
374
+ // The async version uses Web Crypto API internally
375
+ return await signAsync(message, keyBytes);
376
+ }
377
+
378
+ /**
379
+ * Verify an Ed25519 signature
380
+ *
381
+ * Uses the async version of @noble/ed25519 which works in all environments
382
+ * (Node.js, browsers, Cloudflare Workers) by leveraging Web Crypto API internally.
383
+ *
384
+ * @param message - Original message bytes that were signed
385
+ * @param signature - Signature bytes to verify
386
+ * @param publicKey - Public key (string in various formats or Uint8Array)
387
+ * @returns Promise resolving to true if signature is valid, false otherwise
388
+ */
389
+ export async function verifyEd25519(
390
+ message: Uint8Array,
391
+ signature: Uint8Array,
392
+ publicKey: string | Uint8Array
393
+ ): Promise<boolean> {
394
+ try {
395
+ const publicKeyBytes = publicKeyToBytes(publicKey);
396
+ // Use async version which works in all environments (Node.js, browsers, Workers)
397
+ // The async version uses Web Crypto API internally
398
+ return await verifyAsync(signature, message, publicKeyBytes);
399
+ } catch (error) {
400
+ // Return false on any verification error (invalid signature, malformed data, etc.)
401
+ // Error details are not exposed to prevent information leakage
402
+ return false;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Get public key from private key
408
+ *
409
+ * Derives the Ed25519 public key from a private key using the async version
410
+ * of @noble/ed25519 which works in all environments.
411
+ *
412
+ * @param privateKey - Private key (string in various formats or Uint8Array)
413
+ * @returns Promise resolving to public key bytes
414
+ * @throws Error if key derivation fails (invalid key format, etc.)
415
+ */
416
+ export async function getPublicKeyFromPrivate(
417
+ privateKey: string | Uint8Array
418
+ ): Promise<Uint8Array> {
419
+ const privateKeyBytes = privateKeyToBytes(privateKey);
420
+ const keyBytes =
421
+ privateKeyBytes.length >= 32
422
+ ? privateKeyBytes.slice(0, 32)
423
+ : privateKeyBytes;
424
+ // Use async version which works in all environments (Node.js, browsers, Workers)
425
+ // The async version uses Web Crypto API internally
426
+ return await getPublicKeyAsync(keyBytes);
427
+ }